diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 837d80f28153c..c0dad860b212b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,7 +25,6 @@ /src/plugins/charts/ @elastic/kibana-vis-editors /src/plugins/management/ @elastic/kibana-vis-editors /src/plugins/kibana_legacy/ @elastic/kibana-vis-editors -/src/plugins/timelion/ @elastic/kibana-vis-editors /src/plugins/vis_default_editor/ @elastic/kibana-vis-editors /src/plugins/vis_types/metric/ @elastic/kibana-vis-editors /src/plugins/vis_type_table/ @elastic/kibana-vis-editors diff --git a/.gitignore b/.gitignore index 4f77a6e450c4b..32c77b20ef204 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /.chromium .DS_Store .node_binaries +/.beats .native_modules node_modules !/src/dev/npm/integration_tests/__fixtures__/fixture1/node_modules diff --git a/.i18nrc.json b/.i18nrc.json index 77c57ded8242b..11d31be8f891c 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -56,7 +56,7 @@ "server": "src/legacy/server", "statusPage": "src/legacy/core_plugins/status_page", "telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"], - "timelion": ["src/plugins/timelion", "src/plugins/vis_type_timelion"], + "timelion": ["src/plugins/vis_type_timelion"], "uiActions": "src/plugins/ui_actions", "visDefaultEditor": "src/plugins/vis_default_editor", "visTypeMarkdown": "src/plugins/vis_type_markdown", diff --git a/api_docs/core_application.json b/api_docs/core_application.json index 72a11803976c9..35c12330898fb 100644 --- a/api_docs/core_application.json +++ b/api_docs/core_application.json @@ -1393,10 +1393,6 @@ "plugin": "kibanaOverview", "path": "src/plugins/kibana_overview/public/application.tsx" }, - { - "plugin": "timelion", - "path": "src/plugins/timelion/public/application.ts" - }, { "plugin": "management", "path": "src/plugins/management/target/types/public/application.d.ts" diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index e1328b249ce7a..82caea2104067 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -13,7 +13,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Deprecated API | Referencing plugin(s) | Remove By | | ---------------|-----------|-----------| -| | discover, visualizations, dashboard, lens, observability, maps, dashboardEnhanced, discoverEnhanced, securitySolution, visualize, timelion, presentationUtil | 8.1 | +| | discover, visualizations, dashboard, lens, observability, maps, dashboardEnhanced, discoverEnhanced, securitySolution, visualize, presentationUtil | 8.1 | | | lens, timelines, infra, securitySolution, stackAlerts, transform, indexPatternManagement, visTypeTimelion, visTypeVega | 8.1 | | | discover, visualizations, dashboard, lens, observability, timelines, maps, infra, dashboardEnhanced, discoverEnhanced, securitySolution, urlDrilldown, inputControlVis, visTypeTimelion, visualize, visTypeVega, presentationUtil, ml, visTypeTimeseries | 8.1 | | | discover, visualizations, dashboard, lens, observability, timelines, maps, infra, dashboardEnhanced, discoverEnhanced, securitySolution, urlDrilldown, inputControlVis, visTypeTimelion, visualize, visTypeVega, presentationUtil, ml, visTypeTimeseries | 8.1 | @@ -86,16 +86,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | discover | - | | | discover, ml, transform, canvas | - | | | embeddable, discover, presentationUtil, dashboard, graph | - | -| | visualizations, discover, dashboard, savedObjectsManagement, timelion | - | +| | visualizations, discover, dashboard, savedObjectsManagement | - | | | discover, savedObjectsTaggingOss, visualizations, dashboard, visualize, visDefaultEditor | - | -| | discover, visualizations, dashboard, timelion | - | +| | discover, visualizations, dashboard | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | | | advancedSettings, discover | - | | | security | - | | | security | - | | | security, licenseManagement, ml, fleet, apm, reporting, crossClusterReplication, logstash, painlessLab, searchprofiler, watcher | - | -| | management, fleet, security, kibanaOverview, timelion | - | +| | management, fleet, security, kibanaOverview | - | | | visualizations, dashboard | - | | | visualizations, dashboard | - | | | visualizations, dashboard | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 8d070428376b4..75eac66df04a1 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -811,17 +811,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex -## timelion - -| Deprecated API | Reference location(s) | Remove By | -| ---------------|-----------|-----------| -| | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/timelion/public/plugin.ts#:~:text=esFilters), [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/timelion/public/plugin.ts#:~:text=esFilters) | 8.1 | -| | [saved_sheets.ts](https://github.com/elastic/kibana/tree/master/src/plugins/timelion/public/services/saved_sheets.ts#:~:text=SavedObjectLoader), [saved_sheets.ts](https://github.com/elastic/kibana/tree/master/src/plugins/timelion/public/services/saved_sheets.ts#:~:text=SavedObjectLoader) | - | -| | [_saved_sheet.ts](https://github.com/elastic/kibana/tree/master/src/plugins/timelion/public/services/_saved_sheet.ts#:~:text=SavedObjectClass) | - | -| | [application.ts](https://github.com/elastic/kibana/tree/master/src/plugins/timelion/public/application.ts#:~:text=appBasePath) | - | - - - ## transform | Deprecated API | Reference location(s) | Remove By | diff --git a/api_docs/saved_objects.json b/api_docs/saved_objects.json index a0e14bae47ba2..d400068df4f83 100644 --- a/api_docs/saved_objects.json +++ b/api_docs/saved_objects.json @@ -679,14 +679,6 @@ { "plugin": "savedObjectsManagement", "path": "src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx" - }, - { - "plugin": "timelion", - "path": "src/plugins/timelion/public/services/saved_sheets.ts" - }, - { - "plugin": "timelion", - "path": "src/plugins/timelion/public/services/saved_sheets.ts" } ], "children": [ @@ -3860,10 +3852,6 @@ { "plugin": "dashboard", "path": "src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts" - }, - { - "plugin": "timelion", - "path": "src/plugins/timelion/public/services/_saved_sheet.ts" } ] }, diff --git a/dev_docs/troubleshooting.mdx b/dev_docs/troubleshooting.mdx new file mode 100644 index 0000000000000..f624a8cd77507 --- /dev/null +++ b/dev_docs/troubleshooting.mdx @@ -0,0 +1,28 @@ +--- +id: kibTroubleshooting +slug: /kibana-dev-docs/troubleshooting +title: Troubleshooting +summary: A collection of tips for working around strange issues. +date: 2021-09-08 +tags: ['kibana', 'onboarding', 'dev', 'troubleshooting'] +--- + +### Typescript issues + +When switching branches, sometimes the TypeScript cache can get mixed up and show some invalid errors. If you run into TypeScript issues (invalid errors, or if it's taking too long to build types), here a few things to try. + +1. Build TypeScript references with the clean command. + +``` +node scripts/build_ts_refs --clean +``` + +2. Restore your repository to a totally fresh state by running `git clean` + +``` +# dry-run the clean to see what will be deleted +git clean -fdxn -e /config -e /.vscode + +# review the files which will be deleted, consider adding some more excludes (-e) +# re-run without the dry-run (-n) flag to actually delete the files +``` diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 96a7e57ef3e4f..5c409e22b9912 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -231,6 +231,18 @@ The `logging.useUTC` setting has been removed. For more information, refer to {k The default timezone is UTC. To change the timezone, set `logging.timezone: false` in kibana.yml. Change the timezone when the system, such as a docker container, is configured for a nonlocal timezone. ==== +[discrete] +[[breaking-32049]] +.Removed environment variables `CONFIG_PATH` and `DATA_PATH` +[%collapsible] +==== +*Details* + +The environment variables `CONFIG_PATH` and `DATA_PATH` have been removed. For more information, refer to {kibana-pull}32049[#32049] + +*Impact* + +Use the environment variable `KBN_PATH_CONF` instead of `CONFIG_PATH`. Use the setting `path.data` instead of `DATA_PATH`. +==== + // end::notable-breaking-changes[] [float] diff --git a/docs/api/features.asciidoc b/docs/api/features.asciidoc index dad3ef75c8117..69f0effb80023 100644 --- a/docs/api/features.asciidoc +++ b/docs/api/features.asciidoc @@ -134,7 +134,6 @@ The API returns the following: "index-pattern", "search", "visualization", - "timelion-sheet", "canvas-workpad" ] }, @@ -152,7 +151,6 @@ The API returns the following: "index-pattern", "search", "visualization", - "timelion-sheet", "canvas-workpad", "dashboard" ] diff --git a/docs/api/role-management/get.asciidoc b/docs/api/role-management/get.asciidoc index d1e9d1e6afa83..b18b2e231774a 100644 --- a/docs/api/role-management/get.asciidoc +++ b/docs/api/role-management/get.asciidoc @@ -73,9 +73,6 @@ The API returns the following: "indexPatterns": [ "read" ], - "timelion": [ - "all" - ], "graph": [ "all" ], diff --git a/docs/api/role-management/put.asciidoc b/docs/api/role-management/put.asciidoc index be46178100095..92750840aca10 100644 --- a/docs/api/role-management/put.asciidoc +++ b/docs/api/role-management/put.asciidoc @@ -94,9 +94,6 @@ $ curl -X PUT api/security/role/my_kibana_role "indexPatterns": [ "read" ], - "timelion": [ - "all" - ], "graph": [ "all" ], diff --git a/docs/api/saved-objects/bulk_create.asciidoc b/docs/api/saved-objects/bulk_create.asciidoc index 5bd3a7587dde9..a935907ef3f11 100644 --- a/docs/api/saved-objects/bulk_create.asciidoc +++ b/docs/api/saved-objects/bulk_create.asciidoc @@ -30,7 +30,7 @@ experimental[] Create multiple {kib} saved objects. ==== Request body `type`:: - (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. `id`:: (Optional, string) Specifies an ID instead of using a randomly generated ID. diff --git a/docs/api/saved-objects/bulk_get.asciidoc b/docs/api/saved-objects/bulk_get.asciidoc index 4c6bf4c19a76c..1bcdf7ba33cf4 100644 --- a/docs/api/saved-objects/bulk_get.asciidoc +++ b/docs/api/saved-objects/bulk_get.asciidoc @@ -23,7 +23,7 @@ experimental[] Retrieve multiple {kib} saved objects by ID. ==== Request Body `type`:: - (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. `id`:: (Required, string) ID of the retrieved object. The ID includes the {kib} unique identifier or a custom identifier. diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index e7e25c7d3bba6..437bdb497da26 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -24,7 +24,7 @@ experimental[] Create {kib} saved objects. (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. ``:: - (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. ``:: (Optional, string) Specifies an ID instead of using a randomly generated ID. diff --git a/docs/api/saved-objects/delete.asciidoc b/docs/api/saved-objects/delete.asciidoc index 9c342cb4d843e..ab50fd6e37eac 100644 --- a/docs/api/saved-objects/delete.asciidoc +++ b/docs/api/saved-objects/delete.asciidoc @@ -22,7 +22,7 @@ WARNING: Once you delete a saved object, _it cannot be recovered_. (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. `type`:: - (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. `id`:: (Required, string) The object ID that you want to remove. diff --git a/docs/api/saved-objects/get.asciidoc b/docs/api/saved-objects/get.asciidoc index 4c8cd020e0286..cfc591d811227 100644 --- a/docs/api/saved-objects/get.asciidoc +++ b/docs/api/saved-objects/get.asciidoc @@ -21,7 +21,7 @@ experimental[] Retrieve a single {kib} saved object by ID. `type`:: - (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. `id`:: (Required, string) The ID of the object to retrieve. diff --git a/docs/api/saved-objects/resolve.asciidoc b/docs/api/saved-objects/resolve.asciidoc index abfad6a0a8150..aa18538975f5f 100644 --- a/docs/api/saved-objects/resolve.asciidoc +++ b/docs/api/saved-objects/resolve.asciidoc @@ -25,7 +25,7 @@ object can be retrieved via the Resolve API using either its new ID or its old I `type`:: - (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. `id`:: (Required, string) The ID of the object to retrieve. diff --git a/docs/api/saved-objects/update.asciidoc b/docs/api/saved-objects/update.asciidoc index d237ced8b52d1..2bd95df1adf30 100644 --- a/docs/api/saved-objects/update.asciidoc +++ b/docs/api/saved-objects/update.asciidoc @@ -20,7 +20,7 @@ experimental[] Update the attributes for existing {kib} saved objects. (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. `type`:: - (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. `id`:: (Required, string) The object ID to update. diff --git a/docs/api/spaces-management/get_all.asciidoc b/docs/api/spaces-management/get_all.asciidoc index e76848da80efb..3c95b1b904441 100644 --- a/docs/api/spaces-management/get_all.asciidoc +++ b/docs/api/spaces-management/get_all.asciidoc @@ -70,7 +70,7 @@ The API returns the following: "id": "sales", "name": "Sales", "initials": "MK", - "disabledFeatures": ["discover", "timelion"], + "disabledFeatures": ["discover"], "imageUrl": "" } ] @@ -124,7 +124,7 @@ The API returns the following: "id": "sales", "name": "Sales", "initials": "MK", - "disabledFeatures": ["discover", "timelion"], + "disabledFeatures": ["discover"], "imageUrl": "", "authorizedPurposes": { "any": true, diff --git a/docs/api/spaces-management/post.asciidoc b/docs/api/spaces-management/post.asciidoc index 1abfffd1c736f..28d60caa0d333 100644 --- a/docs/api/spaces-management/post.asciidoc +++ b/docs/api/spaces-management/post.asciidoc @@ -54,7 +54,7 @@ $ curl -X POST api/spaces/space "description" : "This is the Marketing Space", "color": "#aabbcc", "initials": "MK", - "disabledFeatures": ["timelion"], + "disabledFeatures": [], "imageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABACAYAAABC6cT1AAAGf0lEQVRoQ+3abYydRRUH8N882xYo0IqagEVjokQJKAiKBjXExC9G/aCkGowCIghCkRcrVSSKIu/FEiqgGL6gBIlAYrAqUTH6hZgQFVEMKlQFfItWoQWhZe8z5uzMLdvbfbkLxb13d+fbvfe588x/zpn/+Z9zJpmnI81T3BaAzzfLL1h8weLzZAcWXH2eGHo7zAWLL1h8nuzAjFw9G1N6Kzq8HnuM36MR8iibF3Fv4q+7cv8yDV6K13bYq2furSP8Ag8ncr/vnSnwRViJT2GfCV7yL1yHGxLb+l3EdM9lluNEnIC9xz+f2ZL4Er6Z2DrdXN3fZwp8CU7OfDHxggle8lTLbQ1nJ/7Z7yKmey5zYGZt4h2IzR8/trRc2PDlxJPTzfVcgJ+CC0wMPOa9F6cm7up3EVM9V9386MxliVdM8GwAv6hh/awCz/w7lY25OtF5ruBz4ZLP42NYNrDAFbC3YPWuILnMAfgq3oaRQQYea/stViV+sgssvjKzLvGySeaaNVfP4d7Btokgvxj/bblgpueuF1hmWcyTCmfE3J3M1lTcv0vMswM88zR+jpw4osu6me8kzkpsfLZWzxyRuabO22buxxOJ12FxnXfWgEe83pB5sOE47BsLymzscOoi7nw2JJfZreUjiUsTyzKPZm5NvBDvSuw268AzNzV8H5/Am+qCnsAXgpgSW2Zq9cyKlksbPlTd+te4quWNieMHBfiNDdciYnwsdI/MaOaWhnMTf54J8CqNj8x8JXFIZltYu+HqlmNT8YSBsHgAPw/vxvlVV4du/s0oaxbxg0TbL/jMni0nNcVjQq7+HZfgtpbzBg342TgQ63AkmsymxBW4IjE6A+D7Vzd/fyWxIM/VuCe+HzTgZ2Jpy/kNJ2FJLmLm24mPJ/42A+Bvrxt4SISwlhsaPodH26LZB8rVA3inwwebsrixJCZzX+KMxI/7AV61eVh3DV6Mx3EOvh4kN6jAg8nfUCXm4d1wE66OyxNPTQc+s3/o/MoXizL3JE5O3F3P/uBZPPF4Zr+Wi5uSO48ZPRdyCwn7YB/A35m5KhWNHox4fcNnIs0ddOCRSBxf8+cQG+Huf0l8NJVYP+nI7NXy2ar4QqIGm69JfKPOE2w/mBavCzwM11R2D+ChsUO7hyUfmwx55qDM1xJvqZ7y08TpifuGBfjeURVJnNIVGpkNiXNS0ds7jcySDitDCCWW56LJ10fRo8sNA+3qXUSZD2CtQlZh9T+1rB7h9oliembflnMbzqgSNZKbKGHdPm7OwXb1CvQ1metSETMpszmzvikCJNh/h5E5PHNl4qga/+/cxqrdeWDYgIe7X5L4cGJPJX2940lOX8pD41FnFnc4riluvQKbK0dcHJFi2IBHNTQSlguru4d2/wPOTNzRA3x5y+U1E1uqWDkETOT026XuUJzx6u7ReLhSYenQ7uHua0fKZmwfmcPqsQjxE5WVONcRxn7X89zgn/EKPMRMxOVQXmP18Mx3q3b/Y/0cQE/IhFtHESMsHFlZ1Ml3CH3DZPHImY+pxcKumNmYirtvqMBfhMuU6s3iqOQkTsMPe1tCQwO8Ajs0lxr7W+vnp1MJc9EgCNd/cy6x+9D4veXmprj5wxMw/3C4egW6zzgZOlYZzfwo3F2J7ael0pJamvlPKgWNKFft1AAcKotXoFEbD7kaoSoQPVKB35+5KHF0lai/rJo+up87jWEE/qqqwY+qrL21LWLm95lPJ16ppKw31XC3PXYPJauPEx7B6BHCgrSizRs18qiaRp8tlN3ueCTYPHH9RNaunjI8Z7wLYpT3jZSCYXQ8e9vTsRE/q+no3XMKeObgGtaintbb/AvXj4JDkNw/5hrwYPfIvlZFUbLn7G5q+eQIN09Vnho6cqvnM/Lt99RixH49wO8K0ZL41WTWHoQzvsNVkOheZqKhEGpsp3SzB+BBtZAYve7uOR9tuTaaB6l0XScdYfEQPpkTUyHEGP+XqyDBzu+NBCITUjNWHynkrbWKOuWFn1xKzqsyx0bdvS78odp0+N503Zao0uCsWuSIDku8/7EO60b41vN5+Ses9BKlTdvd8bhp9EBvJjWJAIn/vxwHe6b3tSk6JFPV4nq85oAOrx555v/x/rh3E6Lo+bnuNS4uB4Cuq0ZfvO8X1rM6q/+vnjLVqZq7v83onttc2oYF4HPJmv1gWbB4P7s0l55ZsPhcsmY/WBYs3s8uzaVn5q3F/wf70mRuBCtbjQAAAABJRU5ErkJggg==" } -------------------------------------------------- diff --git a/docs/apm/correlations.asciidoc b/docs/apm/correlations.asciidoc index c0c18433c9021..2165994b8372f 100644 --- a/docs/apm/correlations.asciidoc +++ b/docs/apm/correlations.asciidoc @@ -58,68 +58,34 @@ out, you can begin viewing sample traces to continue your investigation. [[correlations-error-rate]] ==== Find failed transaction correlations -The correlations on the *Error rate* tab help you discover which fields are -contributing to failed transactions. - -By default, a number of attributes commonly known to cause performance issues, -like version, infrastructure, and location, are included, but all are completely -customizable to your APM data. Find something interesting? A quick click of a -button will auto-query your data as you work to resolve the underlying issue. - -The error rate over time chart visualizes the change in error rate over the selected time frame. -Correlated attributes are sorted by _Impact_–a visual representation of the -{ref}/search-aggregations-bucket-significantterms-aggregation.html[significant terms aggregation] -score that powers correlations. -Attributes with a high impact, or attributes present in a large percentage of failed transactions, -may contribute to increased error rates. - -To find error rate correlations, hover over each potentially correlated attribute to -compare the error rate distribution of transactions with and without the selected attribute. - -For example, in the screenshot below, the field `url.original` and value `http://localhost:3100...` -existed in 100% of failed transactions between 6:00 and 10:30. +beta::[] + +The correlations on the *Failed transaction correlations* tab help you discover +which attributes are most influential in distinguishing between transaction +failures and successes. In this context, the success or failure of a transaction +is determined by its {ecs-ref}/ecs-event.html#field-event-outcome[event.outcome] +value. For example, APM agents set the `event.outcome` to `failure` when an HTTP +transaction returns a `5xx` status code. + +// The chart highlights the failed transactions in the overall latency distribution for the transaction group. +If there are attributes that have a statistically significant correlation with +failed transactions, they are listed in a table. The table is sorted by scores, +which are mapped to high, medium, or low impact levels. Attributes with high +impact levels are more likely to contribute to failed transactions. +// By default, the attribute with the highest score is added to the chart. To see a different attribute in the chart, hover over its row in the table. + +For example, in the screenshot below, the field +`kubernetes.pod.name` and value `frontend-node-59dff47885-fl5lb` has a medium +impact level and existed in 19% of the failed transactions. [role="screenshot"] -image::apm/images/error-rate-hover.png[Correlations errors hover effect] - -Select the `+` filter to create a new query in the {apm-app} for transactions with -`url.original: http://localhost:3100...`. With the "noise" now filtered out, -you can begin viewing sample traces to continue your investigation. - -As you sift through erroneous transactions, you'll likely notice other interesting attributes. -Return to the correlations fly-out and select *Customize fields* to search on these new attributes. -You may need to do this a few times–each time filtering out more and more noise and bringing you -closer to a diagnosis. - -[discrete] -[[correlations-customize-fields]] -===== Customize fields - -By default, a handful of attributes commonly known to cause performance issues -are included in the analysis on the *Error rate* tab. You can add and remove -fields under the **Customize fields** dropdown. - -The following fields are selected by default. To keep the default list -manageable, only the first six matching fields with wildcards are used. - -**Frontend (RUM) agent:** - -* `labels.*` -* `user.*` -* `user_agent.name` -* `user_agent.os.name` -* `url.original` - -**Backend agents:** +image::apm/images/correlations-failed-transactions.png[Failed transaction correlations] -* `labels.*` -* `host.ip` -* `service.node.name` -* `service.version` +TIP: Some details, such as the failure and success percentages, are available +only when the +<> +advanced setting is enabled. -[TIP] -==== -* Want to start over? Select **reset** to clear your customizations. -* The *Latency* tab does not have a **Customize fields** dropdown, since it -automatically considers all relevant fields in the transactions. -==== +Select the `+` filter to create a new query in the {apm-app} for transactions +with this attribute. You might do his for multiple attributes--each time +filtering out more and more noise and bringing you closer to a diagnosis. \ No newline at end of file diff --git a/docs/apm/images/correlations-failed-transactions.png b/docs/apm/images/correlations-failed-transactions.png new file mode 100644 index 0000000000000..3258b44f7097b Binary files /dev/null and b/docs/apm/images/correlations-failed-transactions.png differ diff --git a/docs/apm/images/error-rate-hover.png b/docs/apm/images/error-rate-hover.png deleted file mode 100644 index 69f0009309318..0000000000000 Binary files a/docs/apm/images/error-rate-hover.png and /dev/null differ diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 319ac1e8476fe..84e6668830edc 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -235,11 +235,6 @@ generating deep links to other apps, and creating short URLs. |This plugin adds the Advanced Settings section for the Usage and Security Data collection (aka Telemetry). -|{kib-repo}blob/{branch}/src/plugins/timelion/README.md[timelion] -|Contains the deprecated timelion application. For the timelion visualization, -which also contains the timelion APIs and backend, look at the vis_type_timelion plugin. - - |<> |UI Actions plugins provides API to manage *triggers* and *actions*. diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 7e4ed7ec2dd1b..f40f52db55de9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -190,6 +190,9 @@ readonly links: { }>; readonly observability: Readonly<{ guide: string; + infrastructureThreshold: string; + logsThreshold: string; + metricsThreshold: string; monitorStatus: string; monitorUptime: string; tlsCertificate: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 89156501d476e..2499227d20ad4 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
infrastructureThreshold: string;
logsThreshold: string;
metricsThreshold: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md index eb0dbb59e6c12..00e5da4a9a9f9 100644 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md @@ -25,29 +25,29 @@ import { i18n } from '@kbn/i18n'; async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { const deprecations: DeprecationsDetails[] = []; - const count = await getTimelionSheetsCount(savedObjectsClient); + // Example of an api correctiveAction + const count = await getFooCount(savedObjectsClient); if (count > 0) { - // Example of a manual correctiveAction deprecations.push({ - title: i18n.translate('xpack.timelion.deprecations.worksheetsTitle', { - defaultMessage: 'Timelion worksheets are deprecated' + title: i18n.translate('xpack.foo.deprecations.title', { + defaultMessage: `Foo's are deprecated` }), - message: i18n.translate('xpack.timelion.deprecations.worksheetsMessage', { - defaultMessage: 'You have {count} Timelion worksheets. Migrate your Timelion worksheets to a dashboard to continue using them.', + message: i18n.translate('xpack.foo.deprecations.message', { + defaultMessage: `You have {count} Foo's. Migrate your Foo's to a dashboard to continue using them.`, values: { count }, }), documentationUrl: - 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + 'https://www.elastic.co/guide/en/kibana/current/foo.html', level: 'warning', correctiveActions: { manualSteps: [ - i18n.translate('xpack.timelion.deprecations.worksheets.manualStepOneMessage', { - defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".', - }), - i18n.translate('xpack.timelion.deprecations.worksheets.manualStepTwoMessage', { - defaultMessage: 'Select Timelion from the "New Visualization" window.', - }), + i18n.translate('xpack.foo.deprecations.manualStepOneMessage', { + defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".', + }), + i18n.translate('xpack.foo.deprecations.manualStepTwoMessage', { + defaultMessage: 'Select Foo from the "New Visualization" window.', + }), ], api: { path: '/internal/security/users/test_dashboard_user', @@ -68,6 +68,7 @@ async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecations }, }); } + return deprecations; } diff --git a/docs/management/action-types.asciidoc b/docs/management/action-types.asciidoc index 3d3d7aeb2d777..92adbaf97d8c5 100644 --- a/docs/management/action-types.asciidoc +++ b/docs/management/action-types.asciidoc @@ -135,5 +135,14 @@ image::images/connectors-with-missing-secrets.png[Connectors with missing secret For out-of-the-box and standardized connectors, you can <> before {kib} starts. +[float] +[[montoring-connectors]] +=== Monitoring connectors + +The <> helps you understand the performance of all tasks in your environment. +However, if connectors fail to execute, they will report as successful to Task Manager. The failure stats will not +accurately depict the performance of connectors. + +For more information on connector successes and failures, refer to the <>. include::connectors/index.asciidoc[] diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index cbf32c35c55fd..6bf9ddb365290 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -470,13 +470,6 @@ The default period of time in the Security time filter. [[kibana-timelion-settings]] ==== Timelion -[horizontal] -[[timelion-defaultcolumns]]`timelion:default_columns`:: -The default number of columns to use on a Timelion sheet. - -[[timelion-defaultrows]]`timelion:default_rows`:: -The default number of rows to use on a Timelion sheet. - [[timelion-esdefaultindex]]`timelion:es.default_index`:: The default index when using the `.es()` query. @@ -502,9 +495,6 @@ experimental:[] Used with quandl queries, this is your API key from https://www.quandl.com/[www.quandl.com]. -[[timelion-showtutorial]]`timelion:showTutorial`:: -Shows the Timelion tutorial to users when they first open the Timelion app. - [[timelion-targetbuckets]]`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations, this is the number of buckets to try to represent. diff --git a/docs/management/images/stackManagement-indexPatterns-pinRuntimeField-7.15.png b/docs/management/images/stackManagement-indexPatterns-pinRuntimeField-7.15.png new file mode 100644 index 0000000000000..49d0d4caec00f Binary files /dev/null and b/docs/management/images/stackManagement-indexPatterns-pinRuntimeField-7.15.png differ diff --git a/docs/management/manage-index-patterns.asciidoc b/docs/management/manage-index-patterns.asciidoc index ac07aa833c3b9..08527ffa75d4a 100644 --- a/docs/management/manage-index-patterns.asciidoc +++ b/docs/management/manage-index-patterns.asciidoc @@ -7,60 +7,60 @@ To customize the data fields in your index pattern, you can add runtime fields t [[runtime-fields]] === Explore your data with runtime fields -Runtime fields are fields that you add to documents after you've ingested, and are evaluated at query time. With runtime fields, you allow for a smaller index and faster ingest time so that you can use less resources and reduce your operating costs. You can use runtime fields anywhere index patterns are used. +Runtime fields are fields that you add to documents after you've ingested your data, and are evaluated at query time. With runtime fields, you allow for a smaller index and faster ingest time so that you can use less resources and reduce your operating costs. You can use runtime fields anywhere index patterns are used, for example, you can explore runtime fields in *Discover* and create visualizations with runtime fields for your dashboard. -When you use runtime fields, you can: +With runtime fields, you can: -* Define fields for a specific use without modifying the underlying schema. +* Define fields for a specific use case without modifying the underlying schema. * Override the returned values from index fields. -* Start working on your data without first understanding the structure. +* Start working on your data without understanding the structure. * Add fields to existing documents without reindexing your data. -* Explore runtime field data in *Discover*. - -* Create visualizations with runtime field data using *Lens*, *Maps*, and *TSVB*. - WARNING: Runtime fields can impact {kib} performance. When you run a query, {es} uses the fields you index first to shorten the response time. Index the fields that you commonly search for and filter on, such as `timestamp`, then use runtime fields to limit the number of fields {es} uses to calculate values. -For more information, refer to {ref}/runtime.html[Runtime fields]. +For detailed information on how to use runtime fields with {es}, refer to {ref}/runtime.html[Runtime fields]. [float] [[create-runtime-fields]] -==== Create runtime fields +==== Add runtime fields -Create runtime fields in your index patterns, or create runtime fields in *Discover* and *Lens*. +To add runtime fields to your index patterns, open the index pattern you want to change, then define the field values by emitting a single value using the {ref}/modules-scripting-painless.html[Painless scripting language]. You can also add runtime fields in <> and <>. . Open the main menu, then click *Stack Management > Index Patterns*. . Select the index pattern you want to add the runtime field to, then click *Add field*. -. Enter a *Name* for the runtime field, then select the field *Type*. +. Enter the field *Name*, then select the *Type*. + +. Select *Set custom label*, then enter the label you want to display where the index pattern is used, such as *Discover*. + +. Select *Set value*, then define the script. The script must match the *Type*, or the index pattern fails anywhere it is used. + +. To help you define the script, use the *Preview*: + +* To view the other available fields, use the *Document ID* arrows. + +* To filter the fields list, enter the keyword in *Filter fields*. -. Select *Set value*, then define the field value by emitting a single value using the {ref}/modules-scripting-painless.html[Painless scripting language]. -+ -The script must match the field *Type*, or the script fails. +* To pin frequently used fields to the top of the list, hover over the field, then click image:images/stackManagement-indexPatterns-pinRuntimeField-7.15.png[Icon to pin field to the top of the list]. . Click *Create field*. -+ -For information on how to create runtime fields in *Discover*, refer to <>. -+ -For information on how to create runtime fields in *Lens*, refer to <>. [float] [[runtime-field-examples]] ==== Runtime field examples -Try the runtime field examples on your own using the *Sample web logs* data index pattern. +Try the runtime field examples on your own using the <> data index pattern. [float] [[simple-hello-world-example]] ==== Return a keyword value -To return `Hello World!` value: +Return `Hello World!`: [source,text] ---- @@ -101,7 +101,7 @@ emit(""); [[replace-nulls-with-blanks]] ===== Replace nulls with blanks -Replace null values with none values: +Replace `null` values with `None`: [source,text] ---- @@ -115,7 +115,7 @@ else { } ---- -Specify operating system condition: +Specify the operating system condition: [source,text] ---- diff --git a/docs/user/dashboard/timelion.asciidoc b/docs/user/dashboard/timelion.asciidoc index 85c8bbd436d25..ae92ed4259b65 100644 --- a/docs/user/dashboard/timelion.asciidoc +++ b/docs/user/dashboard/timelion.asciidoc @@ -13,8 +13,6 @@ The syntax enables some features that classical point series charts don't offer, [role="screenshot"] image:dashboard/images/timelion.png[Timelion] -deprecated::[7.0.0,"*Timelion* is still supported. The *Timelion app* is deprecated in 7.0, replaced by dashboard features. In 7.16 and later, the *Timelion app* is removed from {kib}. To prepare for the removal of *Timelion app*, you must migrate *Timelion app* worksheets to a dashboard. For information on how to migrate *Timelion app* worksheets, refer to the link:https://www.elastic.co/guide/en/kibana/7.10/release-notes-7.10.0.html#deprecation-v7.10.0[7.10.0 Release Notes]."] - [float] ==== Timelion expressions diff --git a/docs/user/production-considerations/task-manager-health-monitoring.asciidoc b/docs/user/production-considerations/task-manager-health-monitoring.asciidoc index 3321a9d0c02a1..b07a01906b895 100644 --- a/docs/user/production-considerations/task-manager-health-monitoring.asciidoc +++ b/docs/user/production-considerations/task-manager-health-monitoring.asciidoc @@ -111,6 +111,7 @@ a| Runtime | This section tracks excution performance of Task Manager, tracking task _drift_, worker _load_, and execution stats broken down by type, including duration and execution results. + a| Capacity Estimation | This section provides a rough estimate about the sufficiency of its capacity. As the name suggests, these are estimates based on historical data and should not be used as predictions. Use these estimations when following the Task Manager <>. @@ -123,6 +124,14 @@ The root `status` indicates the `status` of the system overall. The Runtime `status` indicates whether task executions have exceeded any of the <>. An `OK` status means none of the threshold have been exceeded. A `Warning` status means that at least one warning threshold has been exceeded. An `Error` status means that at least one error threshold has been exceeded. +[IMPORTANT] +============================================== +Some tasks (such as <>) will incorrectly report their status as successful even if the task failed. +The runtime and workload block will return data about success and failures and will not take this into consideration. + +To get a better sense of action failures, please refer to the <> for more accurate context into failures and successes. +============================================== + The Capacity Estimation `status` indicates the sufficiency of the observed capacity. An `OK` status means capacity is sufficient. A `Warning` status means that capacity is sufficient for the scheduled recurring tasks, but non-recurring tasks often cause the cluster to exceed capacity. An `Error` status means that there is insufficient capacity across all types of tasks. By monitoring the `status` of the system overall, and the `status` of specific task types of interest, you can evaluate the health of the {kib} Task Management system. diff --git a/package.json b/package.json index e3f65f7c50853..06755da56451a 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "**/pdfkit/crypto-js": "4.0.0", "**/react-syntax-highlighter": "^15.3.1", "**/react-syntax-highlighter/**/highlight.js": "^10.4.1", - "**/request": "^2.88.2", "**/trim": "1.0.1", "**/typescript": "4.1.3", "**/underscore": "^1.13.1" @@ -100,7 +99,7 @@ "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.19", "@elastic/ems-client": "7.15.0", - "@elastic/eui": "37.3.1", + "@elastic/eui": "37.6.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/maki": "6.3.0", @@ -184,15 +183,11 @@ "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "abortcontroller-polyfill": "^1.4.0", - "ajv": "^6.12.4", "angular": "^1.8.0", "angular-aria": "^1.8.0", - "angular-elastic": "^2.5.1", "angular-recursion": "^1.0.5", - "angular-resource": "1.8.0", "angular-route": "^1.8.0", "angular-sanitize": "^1.8.0", - "angular-sortable-view": "^0.0.17", "antlr4ts": "^0.5.0-alpha.3", "archiver": "^5.2.0", "axios": "^0.21.1", @@ -201,7 +196,6 @@ "brace": "0.11.1", "broadcast-channel": "^3.0.3", "chalk": "^4.1.0", - "check-disk-space": "^2.1.0", "cheerio": "^1.0.0-rc.9", "chokidar": "^3.4.3", "chroma-js": "^1.4.1", @@ -237,15 +231,12 @@ "fflate": "^0.6.9", "file-saver": "^1.3.8", "file-type": "^10.9.0", - "focus-trap-react": "^3.1.1", "font-awesome": "4.7.0", - "formsy-react": "^1.1.5", "fp-ts": "^2.3.1", "geojson-vt": "^3.2.1", "get-port": "^5.0.0", "getopts": "^2.2.5", "getos": "^3.1.0", - "github-markdown-css": "^2.10.0", "glob": "^7.1.2", "glob-all": "^3.2.1", "globby": "^11.0.3", @@ -323,9 +314,7 @@ "pngjs": "^3.4.0", "polished": "^3.7.2", "prop-types": "^15.7.2", - "proper-lockfile": "^3.2.0", "proxy-from-env": "1.0.0", - "proxyquire": "1.8.0", "puid": "1.0.7", "puppeteer": "^8.0.0", "query-string": "^6.13.2", @@ -341,7 +330,6 @@ "react-dropzone": "^4.2.9", "react-fast-compare": "^2.0.4", "react-grid-layout": "^0.16.2", - "react-input-range": "^1.3.0", "react-intl": "^2.8.0", "react-is": "^16.8.0", "react-markdown": "^4.3.1", @@ -379,7 +367,6 @@ "regenerator-runtime": "^0.13.3", "remark-parse": "^8.0.3", "remark-stringify": "^9.0.0", - "request": "^2.88.0", "require-in-the-middle": "^5.0.2", "reselect": "^4.0.0", "resize-observer-polyfill": "^1.5.0", @@ -397,7 +384,7 @@ "suricata-sid-db": "^1.0.2", "symbol-observable": "^1.2.0", "tabbable": "1.1.3", - "tar": "4.4.13", + "tar": "^6.1.11", "tinycolor2": "1.4.1", "tinygradient": "0.4.3", "tree-kill": "^1.2.2", @@ -406,10 +393,9 @@ "type-detect": "^4.0.8", "typescript-fsa": "^3.0.0", "typescript-fsa-reducers": "^1.2.2", - "ui-select": "0.19.8", "unified": "^9.2.1", - "unstated": "^2.1.1", "use-resize-observer": "^6.0.0", + "usng.js": "^0.4.5", "utility-types": "^3.10.0", "uuid": "3.3.2", "vega": "^5.19.1", @@ -601,7 +587,6 @@ "@types/prettier": "^2.1.5", "@types/pretty-ms": "^5.0.0", "@types/prop-types": "^15.7.3", - "@types/proper-lockfile": "^3.0.1", "@types/rbush": "^3.0.0", "@types/reach__router": "^1.2.6", "@types/react": "^16.9.36", @@ -619,7 +604,6 @@ "@types/recompose": "^0.30.6", "@types/reduce-reducers": "^1.0.0", "@types/redux-actions": "^2.6.1", - "@types/request": "^2.48.2", "@types/seedrandom": ">=2.0.0 <4.0.0", "@types/selenium-webdriver": "^4.0.9", "@types/semver": "^7", @@ -632,7 +616,7 @@ "@types/styled-components": "^5.1.0", "@types/supertest": "^2.0.5", "@types/tapable": "^1.0.6", - "@types/tar": "^4.0.3", + "@types/tar": "^4.0.5", "@types/tar-fs": "^1.16.1", "@types/tempy": "^0.2.0", "@types/testing-library__jest-dom": "^5.9.5", @@ -696,7 +680,6 @@ "cypress-promise": "^1.1.0", "cypress-real-events": "^1.4.0", "debug": "^2.6.9", - "del-cli": "^3.0.1", "delete-empty": "^2.0.0", "dependency-check": "^4.1.0", "diff": "^4.0.1", @@ -830,7 +813,6 @@ "tape": "^5.0.1", "tar-fs": "^2.1.0", "tempy": "^0.3.0", - "terminal-link": "^2.1.1", "terser": "^5.7.1", "terser-webpack-plugin": "^2.1.2", "tough-cookie": "^4.0.0", diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts index 007c3ec54113b..0e1f36121e50e 100644 --- a/packages/kbn-config/src/deprecation/types.ts +++ b/packages/kbn-config/src/deprecation/types.ts @@ -23,6 +23,12 @@ export interface DeprecatedConfigDetails { title?: string; /* The message to be displayed for the deprecation. */ message: string; + /** + * levels: + * - warning: will not break deployment upon upgrade + * - critical: needs to be addressed before upgrade. + */ + level?: 'warning' | 'critical'; /* (optional) set false to prevent the config service from logging the deprecation message. */ silent?: boolean; /* (optional) link to the documentation for more details on the deprecation. */ diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index 32709fc608617..ac4380da88be0 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -278,7 +278,8 @@ exports.Cluster = class Cluster { // especially because we currently run many instances of ES on the same machine during CI // inital and max must be the same, so we only need to check the max if (!esJavaOpts.includes('Xmx')) { - esJavaOpts += ' -Xms1g -Xmx1g'; + // 1536m === 1.5g + esJavaOpts += ' -Xms1536m -Xmx1536m'; } this._log.debug('ES_JAVA_OPTS: %s', esJavaOpts.trim()); diff --git a/packages/kbn-es/src/integration_tests/cluster.test.js b/packages/kbn-es/src/integration_tests/cluster.test.js index 34220b08d2120..c196a89a6b090 100644 --- a/packages/kbn-es/src/integration_tests/cluster.test.js +++ b/packages/kbn-es/src/integration_tests/cluster.test.js @@ -368,7 +368,7 @@ describe('#run()', () => { const cluster = new Cluster({ log }); await cluster.run(); - expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toEqual('-Xms1g -Xmx1g'); + expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toMatchInlineSnapshot(`"-Xms1536m -Xmx1536m"`); }); it('allows Java heap to be overwritten', async () => { @@ -378,7 +378,7 @@ describe('#run()', () => { const cluster = new Cluster({ log }); await cluster.run(); - expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toEqual('-Xms5g -Xmx5g'); + expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toMatchInlineSnapshot(`"-Xms5g -Xmx5g"`); }); }); diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 282abe1e1741a..69cfffe1f08f0 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -70,7 +70,6 @@ pageLoadAssetSize: spaces: 57868 telemetry: 51957 telemetryManagementSection: 38586 - timelion: 29920 transform: 41007 triggersActionsUi: 100000 uiActions: 97717 diff --git a/packages/kbn-spec-to-console/README.md b/packages/kbn-spec-to-console/README.md index a0e654713f61b..be0ada5e38850 100644 --- a/packages/kbn-spec-to-console/README.md +++ b/packages/kbn-spec-to-console/README.md @@ -29,3 +29,8 @@ yarn spec_to_console -g "/rest-api-spec/src/main/reso * Request bodies * Data fetched at runtime: indices, fields, snapshots, etc * Ad hoc additions + +### Updating the script +When converting query params defined in the REST API specs to console autocompletion definitions, the script relies on a set of known conversion rules specified in [lib/convert/params.js](https://github.com/elastic/kibana/blob/master/packages/kbn-spec-to-console/lib/convert/params.js). +For example, `"keep_on_completion":{"type":"boolean"}` from REST API specs is converted to `"keep_on_completion": "__flag__"` in console autocomplete definitions. +When an unknown parameter type is encountered in REST API specs, the script will throw an `Unexpected type error` and the file [lib/convert/params.js](https://github.com/elastic/kibana/blob/master/packages/kbn-spec-to-console/lib/convert/params.js) needs to be updated by adding a new conversion rule. \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/src/entry.js b/packages/kbn-ui-shared-deps/src/entry.js index 013d5f894c013..7544e6953f3e9 100644 --- a/packages/kbn-ui-shared-deps/src/entry.js +++ b/packages/kbn-ui-shared-deps/src/entry.js @@ -56,3 +56,4 @@ export const KbnStd = require('@kbn/std'); export const SaferLodashSet = require('@elastic/safer-lodash-set'); export const RisonNode = require('rison-node'); export const History = require('history'); +export const Classnames = require('classnames'); diff --git a/packages/kbn-ui-shared-deps/src/index.js b/packages/kbn-ui-shared-deps/src/index.js index 3d3553ba23546..31e5e2c3b1e8e 100644 --- a/packages/kbn-ui-shared-deps/src/index.js +++ b/packages/kbn-ui-shared-deps/src/index.js @@ -101,6 +101,7 @@ exports.externals = { '@elastic/safer-lodash-set': '__kbnSharedDeps__.SaferLodashSet', 'rison-node': '__kbnSharedDeps__.RisonNode', history: '__kbnSharedDeps__.History', + classnames: '__kbnSharedDeps__.Classnames', }; /** diff --git a/packages/kbn-utils/src/path/index.ts b/packages/kbn-utils/src/path/index.ts index 9835179a61e9d..9ee699c22c30c 100644 --- a/packages/kbn-utils/src/path/index.ts +++ b/packages/kbn-utils/src/path/index.ts @@ -15,24 +15,19 @@ const isString = (v: any): v is string => typeof v === 'string'; const CONFIG_PATHS = [ process.env.KBN_PATH_CONF && join(process.env.KBN_PATH_CONF, 'kibana.yml'), - process.env.KIBANA_PATH_CONF && join(process.env.KIBANA_PATH_CONF, 'kibana.yml'), - process.env.CONFIG_PATH, // deprecated + process.env.KIBANA_PATH_CONF && join(process.env.KIBANA_PATH_CONF, 'kibana.yml'), // deprecated join(REPO_ROOT, 'config/kibana.yml'), '/etc/kibana/kibana.yml', ].filter(isString); const CONFIG_DIRECTORIES = [ process.env.KBN_PATH_CONF, - process.env.KIBANA_PATH_CONF, + process.env.KIBANA_PATH_CONF, // deprecated join(REPO_ROOT, 'config'), '/etc/kibana', ].filter(isString); -const DATA_PATHS = [ - process.env.DATA_PATH, // deprecated - join(REPO_ROOT, 'data'), - '/var/lib/kibana', -].filter(isString); +const DATA_PATHS = [join(REPO_ROOT, 'data'), '/var/lib/kibana'].filter(isString); function findFile(paths: string[]) { const availablePath = paths.find((configPath) => { diff --git a/rfcs/text/0007_lifecycle_unblocked.md b/rfcs/text/0007_lifecycle_unblocked.md index cb978d3dcd7ba..3f347891b2ba8 100644 --- a/rfcs/text/0007_lifecycle_unblocked.md +++ b/rfcs/text/0007_lifecycle_unblocked.md @@ -342,7 +342,6 @@ functions and will be impacted: 2. [tile_map](https://github.com/elastic/kibana/blob/6039709929caf0090a4130b8235f3a53bd04ed84/src/legacy/core_plugins/tile_map/public/plugin.ts#L62) 3. [vis_type_table](https://github.com/elastic/kibana/blob/6039709929caf0090a4130b8235f3a53bd04ed84/src/legacy/core_plugins/vis_type_table/public/plugin.ts#L61) 4. [vis_type_vega](https://github.com/elastic/kibana/blob/6039709929caf0090a4130b8235f3a53bd04ed84/src/legacy/core_plugins/vis_type_vega/public/plugin.ts#L59) -5. [timelion](https://github.com/elastic/kibana/blob/9d69b72a5f200e58220231035b19da852fc6b0a5/src/plugins/timelion/server/plugin.ts#L40) 6. [code](https://github.com/elastic/kibana/blob/5049b460b47d4ae3432e1d9219263bb4be441392/x-pack/legacy/plugins/code/server/plugin.ts#L129-L149) 7. [spaces](https://github.com/elastic/kibana/blob/096c7ee51136327f778845c636d7c4f1188e5db2/x-pack/legacy/plugins/spaces/server/new_platform/plugin.ts#L95) 8. [licensing](https://github.com/elastic/kibana/blob/4667c46caef26f8f47714504879197708debae32/x-pack/plugins/licensing/server/plugin.ts) diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 6b03581a7765b..72fa6c5553f77 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -35,9 +35,9 @@ export class DocLinksService { kibanaSettings: `${KIBANA_DOCS}apm-settings-in-kibana.html`, supportedServiceMaps: `${KIBANA_DOCS}service-maps.html#service-maps-supported`, customLinks: `${KIBANA_DOCS}custom-links.html`, - droppedTransactionSpans: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/transaction-spans.html#dropped-spans`, - upgrading: `${APM_DOCS}server/${DOC_LINK_VERSION}/upgrading.html`, - metaData: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/metadata.html`, + droppedTransactionSpans: `${APM_DOCS}get-started/master/transaction-spans.html#dropped-spans`, + upgrading: `${APM_DOCS}server/master/upgrading.html`, + metaData: `${APM_DOCS}get-started/master/metadata.html`, }, canvas: { guide: `${KIBANA_DOCS}canvas.html`, @@ -273,7 +273,6 @@ export class DocLinksService { }, visualize: { guide: `${KIBANA_DOCS}dashboard.html`, - timelionDeprecation: `${KIBANA_DOCS}timelion.html`, lens: `${ELASTIC_WEBSITE_URL}what-is/kibana-lens`, lensPanels: `${KIBANA_DOCS}lens.html`, maps: `${ELASTIC_WEBSITE_URL}maps`, @@ -282,6 +281,9 @@ export class DocLinksService { }, observability: { guide: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/index.html`, + infrastructureThreshold: `{ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/infrastructure-threshold-alert.html`, + logsThreshold: `{ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/logs-threshold-alert.html`, + metricsThreshold: `{ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/metrics-threshold-alert.html`, monitorStatus: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/monitor-status-alert.html`, monitorUptime: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/monitor-uptime.html`, tlsCertificate: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/tls-certificate-alert.html`, @@ -645,6 +647,9 @@ export interface DocLinksStart { }>; readonly observability: Readonly<{ guide: string; + infrastructureThreshold: string; + logsThreshold: string; + metricsThreshold: string; monitorStatus: string; monitorUptime: string; tlsCertificate: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 0135526bb6385..8d3291d590476 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -655,6 +655,9 @@ export interface DocLinksStart { }>; readonly observability: Readonly<{ guide: string; + infrastructureThreshold: string; + logsThreshold: string; + metricsThreshold: string; monitorStatus: string; monitorUptime: string; tlsCertificate: string; diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index 759e2375ce987..d3a4d7f997062 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -18,37 +18,19 @@ describe('core deprecations', () => { process.env = { ...initialEnv }; }); - describe('configPath', () => { - it('logs a warning if CONFIG_PATH environ variable is set', () => { - process.env.CONFIG_PATH = 'somepath'; + describe('kibanaPathConf', () => { + it('logs a warning if KIBANA_PATH_CONF environ variable is set', () => { + process.env.KIBANA_PATH_CONF = 'somepath'; const { messages } = applyCoreDeprecations(); expect(messages).toMatchInlineSnapshot(` Array [ - "Environment variable \\"CONFIG_PATH\\" is deprecated. It has been replaced with \\"KBN_PATH_CONF\\" pointing to a config folder", + "Environment variable \\"KIBANA_PATH_CONF\\" is deprecated. It has been replaced with \\"KBN_PATH_CONF\\" pointing to a config folder", ] `); }); - it('does not log a warning if CONFIG_PATH environ variable is unset', () => { - delete process.env.CONFIG_PATH; - const { messages } = applyCoreDeprecations(); - expect(messages).toHaveLength(0); - }); - }); - - describe('dataPath', () => { - it('logs a warning if DATA_PATH environ variable is set', () => { - process.env.DATA_PATH = 'somepath'; - const { messages } = applyCoreDeprecations(); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Environment variable \\"DATA_PATH\\" will be removed. It has been replaced with kibana.yml setting \\"path.data\\"", - ] - `); - }); - - it('does not log a warning if DATA_PATH environ variable is unset', () => { - delete process.env.DATA_PATH; + it('does not log a warning if KIBANA_PATH_CONF environ variable is unset', () => { + delete process.env.KIBANA_PATH_CONF; const { messages } = applyCoreDeprecations(); expect(messages).toHaveLength(0); }); diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 222f92321d917..a929a937988e8 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -8,24 +8,13 @@ import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config'; -const configPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (process.env?.CONFIG_PATH) { +const kibanaPathConf: ConfigDeprecation = (settings, fromPath, addDeprecation) => { + if (process.env?.KIBANA_PATH_CONF) { addDeprecation({ - message: `Environment variable "CONFIG_PATH" is deprecated. It has been replaced with "KBN_PATH_CONF" pointing to a config folder`, - correctiveActions: { - manualSteps: ['Use "KBN_PATH_CONF" instead of "CONFIG_PATH" to point to a config folder.'], - }, - }); - } -}; - -const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (process.env?.DATA_PATH) { - addDeprecation({ - message: `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`, + message: `Environment variable "KIBANA_PATH_CONF" is deprecated. It has been replaced with "KBN_PATH_CONF" pointing to a config folder`, correctiveActions: { manualSteps: [ - `Set 'path.data' in the config file or CLI flag with the value of the environment variable "DATA_PATH".`, + 'Use "KBN_PATH_CONF" instead of "KIBANA_PATH_CONF" to point to a config folder.', ], }, }); @@ -124,28 +113,6 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati } }; -const mapManifestServiceUrlDeprecation: ConfigDeprecation = ( - settings, - fromPath, - addDeprecation -) => { - if (settings.map?.manifestServiceUrl) { - addDeprecation({ - message: - 'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' + - 'of the Elastic Maps Service settings. These settings have moved to the "map.emsTileApiUrl" and ' + - '"map.emsFileApiUrl" settings instead. These settings are for development use only and should not be ' + - 'modified for use in production environments.', - correctiveActions: { - manualSteps: [ - `Use "map.emsTileApiUrl" and "map.emsFileApiUrl" config instead of "map.manifestServiceUrl".`, - `These settings are for development use only and should not be modified for use in production environments.`, - ], - }, - }); - } -}; - const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (settings.logging?.events?.ops) { addDeprecation({ @@ -388,7 +355,6 @@ const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecat export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [ unusedFromRoot('savedObjects.indexCheckTimeout'), unusedFromRoot('server.xsrf.token'), - unusedFromRoot('maps.manifestServiceUrl'), unusedFromRoot('optimize.lazy'), unusedFromRoot('optimize.lazyPort'), unusedFromRoot('optimize.lazyHost'), @@ -414,11 +380,9 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unu rename('cpuacct.cgroup.path.override', 'ops.cGroupOverrides.cpuAcctPath'), rename('server.xsrf.whitelist', 'server.xsrf.allowlist'), rewriteCorsSettings, - configPathDeprecation, - dataPathDeprecation, + kibanaPathConf, rewriteBasePathDeprecation, cspRulesDeprecation, - mapManifestServiceUrlDeprecation, opsLoggingEventDeprecation, requestLoggingEventDeprecation, timezoneLoggingDeprecation, diff --git a/src/core/server/deprecations/README.mdx b/src/core/server/deprecations/README.mdx index 197b25ac909c3..533f607c3d4c2 100644 --- a/src/core/server/deprecations/README.mdx +++ b/src/core/server/deprecations/README.mdx @@ -139,7 +139,6 @@ Plugins are responsible for registering any deprecations during the `setup` life the deprecations service. Examples of non-config deprecations include things like -- timelion sheets - kibana_user security roles This service is not intended to be used for non-user facing deprecations or cases where the deprecation diff --git a/src/core/server/deprecations/deprecations_service.test.ts b/src/core/server/deprecations/deprecations_service.test.ts index 0e8aaf3de49c9..bc0dbcef4a5b6 100644 --- a/src/core/server/deprecations/deprecations_service.test.ts +++ b/src/core/server/deprecations/deprecations_service.test.ts @@ -66,7 +66,7 @@ describe('DeprecationsService', () => { const deprecationsRegistry = mockDeprecationsRegistry.create(); const getDeprecationsContext = mockDeprecationsRegistry.createGetDeprecationsContext(); - it('registers config deprecations', () => { + it('registers config deprecations', async () => { const deprecationsService = new DeprecationsService(coreContext); coreContext.configService.getHandledDeprecatedConfigs.mockReturnValue([ [ @@ -93,7 +93,7 @@ describe('DeprecationsService', () => { expect(deprecationsFactory.getRegistry).toBeCalledTimes(1); expect(deprecationsFactory.getRegistry).toBeCalledWith('testDomain'); expect(deprecationsRegistry.registerDeprecations).toBeCalledTimes(1); - const configDeprecations = deprecationsRegistry.registerDeprecations.mock.calls[0][0].getDeprecations( + const configDeprecations = await deprecationsRegistry.registerDeprecations.mock.calls[0][0].getDeprecations( getDeprecationsContext ); expect(configDeprecations).toMatchInlineSnapshot(` @@ -115,5 +115,31 @@ describe('DeprecationsService', () => { ] `); }); + + it('accepts `level` field overrides', async () => { + const deprecationsService = new DeprecationsService(coreContext); + coreContext.configService.getHandledDeprecatedConfigs.mockReturnValue([ + [ + 'testDomain', + [ + { + message: 'testMessage', + level: 'warning', + correctiveActions: { + manualSteps: ['step a'], + }, + }, + ], + ], + ]); + + deprecationsFactory.getRegistry.mockReturnValue(deprecationsRegistry); + deprecationsService['registerConfigDeprecationsInfo'](deprecationsFactory); + + const configDeprecations = await deprecationsRegistry.registerDeprecations.mock.calls[0][0].getDeprecations( + getDeprecationsContext + ); + expect(configDeprecations[0].level).toBe('warning'); + }); }); }); diff --git a/src/core/server/deprecations/deprecations_service.ts b/src/core/server/deprecations/deprecations_service.ts index c41567d88a2aa..b8a134fbf8cd2 100644 --- a/src/core/server/deprecations/deprecations_service.ts +++ b/src/core/server/deprecations/deprecations_service.ts @@ -37,28 +37,27 @@ import { SavedObjectsClientContract } from '../saved_objects/types'; * * async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { * const deprecations: DeprecationsDetails[] = []; - * const count = await getTimelionSheetsCount(savedObjectsClient); - * + * const count = await getFooCount(savedObjectsClient); * if (count > 0) { * // Example of a manual correctiveAction * deprecations.push({ - * title: i18n.translate('xpack.timelion.deprecations.worksheetsTitle', { - * defaultMessage: 'Timelion worksheets are deprecated' + * title: i18n.translate('xpack.foo.deprecations.title', { + * defaultMessage: `Foo's are deprecated` * }), - * message: i18n.translate('xpack.timelion.deprecations.worksheetsMessage', { - * defaultMessage: 'You have {count} Timelion worksheets. Migrate your Timelion worksheets to a dashboard to continue using them.', + * message: i18n.translate('xpack.foo.deprecations.message', { + * defaultMessage: `You have {count} Foo's. Migrate your Foo's to a dashboard to continue using them.`, * values: { count }, * }), * documentationUrl: - * 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + * 'https://www.elastic.co/guide/en/kibana/current/foo.html', * level: 'warning', * correctiveActions: { * manualSteps: [ - * i18n.translate('xpack.timelion.deprecations.worksheets.manualStepOneMessage', { + * i18n.translate('xpack.foo.deprecations.manualStepOneMessage', { * defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".', * }), - * i18n.translate('xpack.timelion.deprecations.worksheets.manualStepTwoMessage', { - * defaultMessage: 'Select Timelion from the "New Visualization" window.', + * i18n.translate('xpack.foo.deprecations.manualStepTwoMessage', { + * defaultMessage: 'Select Foo from the "New Visualization" window.', * }), * ], * api: { @@ -186,17 +185,21 @@ export class DeprecationsService deprecationsRegistry.registerDeprecations({ getDeprecations: () => { return deprecationsContexts.map( - ({ title, message, correctiveActions, documentationUrl }) => { - return { - title: title || `${domainId} has a deprecated setting`, - level: 'critical', - deprecationType: 'config', - message, - correctiveActions, - documentationUrl, - requireRestart: true, - }; - } + ({ + title = `${domainId} has a deprecated setting`, + level = 'critical', + message, + correctiveActions, + documentationUrl, + }) => ({ + title, + level, + message, + correctiveActions, + documentationUrl, + deprecationType: 'config', + requireRestart: true, + }) ); }, }); diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index f473b3ed02526..5068c24df3414 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -45,6 +45,8 @@ export const REMOVED_TYPES: string[] = [ 'tsvb-validation-telemetry', // replaced by osquery-manager-usage-metric 'osquery-usage-metric', + // Was removed in 7.16 + 'timelion-sheet', ].sort(); // When migrating from the outdated index we use a read query which excludes diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index d3ff822f9a3a3..64d89a650e62e 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -29,6 +29,7 @@ it('build default and oss dist for current platform, without packages, by defaul "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, + "createDockerCloud": false, "createDockerContexts": true, "createDockerUBI": false, "createExamplePlugins": false, @@ -55,6 +56,7 @@ it('builds packages if --all-platforms is passed', () => { "createArchives": true, "createDebPackage": true, "createDockerCentOS": true, + "createDockerCloud": false, "createDockerContexts": true, "createDockerUBI": true, "createExamplePlugins": false, @@ -81,6 +83,7 @@ it('limits packages if --rpm passed with --all-platforms', () => { "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, + "createDockerCloud": false, "createDockerContexts": true, "createDockerUBI": false, "createExamplePlugins": false, @@ -107,6 +110,7 @@ it('limits packages if --deb passed with --all-platforms', () => { "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, + "createDockerCloud": false, "createDockerContexts": true, "createDockerUBI": false, "createExamplePlugins": false, @@ -134,6 +138,7 @@ it('limits packages if --docker passed with --all-platforms', () => { "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, + "createDockerCloud": false, "createDockerContexts": true, "createDockerUBI": true, "createExamplePlugins": false, @@ -168,6 +173,7 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, + "createDockerCloud": false, "createDockerContexts": true, "createDockerUBI": false, "createExamplePlugins": false, @@ -195,6 +201,7 @@ it('limits packages if --all-platforms passed with --skip-docker-centos', () => "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, + "createDockerCloud": false, "createDockerContexts": true, "createDockerUBI": true, "createExamplePlugins": false, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index 9ee375e33d38f..1124d90be89c6 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -26,6 +26,7 @@ export function readCliArgs(argv: string[]) { 'skip-docker-contexts', 'skip-docker-ubi', 'skip-docker-centos', + 'docker-cloud', 'release', 'skip-node-download', 'verbose', @@ -103,6 +104,7 @@ export function readCliArgs(argv: string[]) { createDebPackage: isOsPackageDesired('deb'), createDockerCentOS: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-centos']), + createDockerCloud: isOsPackageDesired('docker-images') && Boolean(flags['docker-cloud']), createDockerUBI: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-ubi']), createDockerContexts: !Boolean(flags['skip-docker-contexts']), targetAllPlatforms: Boolean(flags['all-platforms']), diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index 1042cdc484c12..39a62c1fd35dc 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -22,6 +22,7 @@ export interface BuildOptions { createDebPackage: boolean; createDockerUBI: boolean; createDockerCentOS: boolean; + createDockerCloud: boolean; createDockerContexts: boolean; versionQualifier: string | undefined; targetAllPlatforms: boolean; @@ -127,6 +128,11 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions await run(Tasks.CreateDockerCentOS); } + if (options.createDockerCloud) { + // control w/ --docker-images and --docker-cloud + await run(Tasks.CreateDockerCloud); + } + if (options.createDockerContexts) { // control w/ --skip-docker-contexts await run(Tasks.CreateDockerContexts); diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 67a9e86ee2073..ab9a7ce65cbc6 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -91,6 +91,25 @@ export const CreateDockerUBI: Task = { }, }; +export const CreateDockerCloud: Task = { + description: 'Creating Docker Cloud image', + + async run(config, log, build) { + await runDockerGenerator(config, log, build, { + architecture: 'x64', + context: false, + cloud: true, + image: true, + }); + await runDockerGenerator(config, log, build, { + architecture: 'aarch64', + context: false, + cloud: true, + image: true, + }); + }, +}; + export const CreateDockerContexts: Task = { description: 'Creating Docker build contexts', @@ -111,5 +130,10 @@ export const CreateDockerContexts: Task = { context: true, image: false, }); + await runDockerGenerator(config, log, build, { + cloud: true, + context: true, + image: false, + }); }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index c9b6fa3d9dda5..cee43fd85c90f 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -200,7 +200,6 @@ kibana_vars=( tilemap.options.minZoom tilemap.options.subdomains tilemap.url - timelion.enabled url_drilldown.enabled vega.enableExternalUrls vis_type_vega.enableExternalUrls diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index cac02cae20c42..c5a4ff64d2188 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -7,7 +7,7 @@ */ import { access, link, unlink, chmod } from 'fs'; -import { resolve } from 'path'; +import { resolve, basename } from 'path'; import { promisify } from 'util'; import { ToolingLog, kibanaPackageJson } from '@kbn/dev-utils'; @@ -32,6 +32,7 @@ export async function runDockerGenerator( image: boolean; ubi?: boolean; ironbank?: boolean; + cloud?: boolean; dockerBuildDate?: string; } ) { @@ -42,6 +43,7 @@ export async function runDockerGenerator( let imageFlavor = ''; if (flags.ubi) imageFlavor += `-${ubiVersionTag}`; if (flags.ironbank) imageFlavor += '-ironbank'; + if (flags.cloud) imageFlavor += '-cloud'; // General docker var config const license = 'Elastic License'; @@ -50,7 +52,10 @@ export async function runDockerGenerator( const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; const artifactPrefix = `kibana-${version}-linux`; const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; + const metricbeatTarball = `metricbeat-${version}-linux-${artifactArchitecture}.tar.gz`; + const filebeatTarball = `filebeat-${version}-linux-${artifactArchitecture}.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); + const beatsDir = config.resolveFromRepo('.beats'); const dockerBuildDate = flags.dockerBuildDate || new Date().toISOString(); // That would produce oss, default and default-ubi7 const dockerBuildDir = config.resolveFromRepo('build', 'kibana-docker', `default${imageFlavor}`); @@ -58,6 +63,13 @@ export async function runDockerGenerator( const dockerTargetFilename = config.resolveFromTarget( `kibana${imageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz` ); + const dependencies = [ + resolve(artifactsDir, artifactTarball), + ...(flags.cloud + ? [resolve(beatsDir, metricbeatTarball), resolve(beatsDir, filebeatTarball)] + : []), + ]; + const scope: TemplateContext = { artifactPrefix, artifactTarball, @@ -72,6 +84,9 @@ export async function runDockerGenerator( baseOSImage, dockerBuildDate, ubi: flags.ubi, + cloud: flags.cloud, + metricbeatTarball, + filebeatTarball, ironbank: flags.ironbank, architecture: flags.architecture, revision: config.getBuildSha(), @@ -87,26 +102,8 @@ export async function runDockerGenerator( return; } - // Verify if we have the needed kibana target in order - // to build the kibana docker image. - // Also create the docker build target folder - // and delete the current linked target into the - // kibana docker build folder if we have one. - try { - await accessAsync(resolve(artifactsDir, artifactTarball)); - await mkdirp(dockerBuildDir); - await unlinkAsync(resolve(dockerBuildDir, artifactTarball)); - } catch (e) { - if (e && e.code === 'ENOENT' && e.syscall === 'access') { - throw new Error( - `Kibana linux target (${artifactTarball}) is needed in order to build ${''}the docker image. None was found at ${artifactsDir}` - ); - } - } - - // Create the kibana linux target inside the - // Kibana docker build - await linkAsync(resolve(artifactsDir, artifactTarball), resolve(dockerBuildDir, artifactTarball)); + // Create the docker build target folder + await mkdirp(dockerBuildDir); // Write all the needed docker config files // into kibana-docker folder @@ -137,6 +134,21 @@ export async function runDockerGenerator( // Only build images on native targets if (flags.image) { + // Link dependencies + for (const src of dependencies) { + const file = basename(src); + const dest = resolve(dockerBuildDir, file); + try { + await accessAsync(src); + await unlinkAsync(dest); + } catch (e) { + if (e && e.code === 'ENOENT' && e.syscall === 'access') { + throw new Error(`${src} is needed in order to build the docker image.`); + } + } + await linkAsync(src, dest); + } + await exec(log, `./build_docker.sh`, [], { cwd: dockerBuildDir, level: 'info', diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts index 9c9949c9f57ea..075a3a8808e73 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts @@ -21,6 +21,9 @@ export interface TemplateContext { dockerBuildDate: string; usePublicArtifact?: boolean; ubi?: boolean; + cloud?: boolean; + metricbeatTarball?: string; + filebeatTarball?: string; ironbank?: boolean; revision: string; architecture?: string; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index 60dabbffc6312..078741a0d0f6c 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -24,18 +24,27 @@ RUN cd /opt && \ {{/usePublicArtifact}} {{^usePublicArtifact}} -COPY {{artifactTarball}} /opt/kibana.tar.gz +COPY {{artifactTarball}} /tmp/kibana.tar.gz {{/usePublicArtifact}} RUN mkdir /usr/share/kibana WORKDIR /usr/share/kibana -RUN tar --strip-components=1 -zxf /opt/kibana.tar.gz +RUN tar --strip-components=1 -zxf /tmp/kibana.tar.gz # Ensure that group permissions are the same as user permissions. # This will help when relying on GID-0 to run Kibana, rather than UID-1000. # OpenShift does this, for example. # REF: https://docs.openshift.org/latest/creating_images/guidelines.html RUN chmod -R g=u /usr/share/kibana +{{#cloud}} +COPY {{filebeatTarball}} /tmp/filebeat.tar.gz +COPY {{metricbeatTarball}} /tmp/metricbeat.tar.gz + +RUN mkdir -p /opt/filebeat /opt/metricbeat && \ + tar xf /tmp/filebeat.tar.gz -C /opt/filebeat --strip-components=1 && \ + tar xf /tmp/metricbeat.tar.gz -C /opt/metricbeat --strip-components=1 +{{/cloud}} + ################################################################################ # Build stage 1 (the actual Kibana image): # @@ -86,6 +95,9 @@ RUN fc-cache -v # Bring in Kibana from the initial stage. COPY --from=builder --chown=1000:0 /usr/share/kibana /usr/share/kibana +{{#cloud}} +COPY --from=builder --chown=0:0 /opt /opt +{{/cloud}} WORKDIR /usr/share/kibana RUN ln -s /usr/share/kibana /opt/kibana @@ -146,8 +158,19 @@ RUN mkdir /licenses && \ cp LICENSE.txt /licenses/LICENSE {{/ubi}} -USER kibana - ENTRYPOINT ["/bin/tini", "--"] +{{#cloud}} +CMD ["/app/kibana.sh"] +# Generate a stub command that will be overwritten at runtime +RUN mkdir /app && \ + echo -e '#!/bin/sh\nexec /usr/local/bin/kibana-docker' > /app/kibana.sh && \ + chmod 0555 /app/kibana.sh +{{/cloud}} + +{{^cloud}} CMD ["/usr/local/bin/kibana-docker"] +{{/cloud}} + + +USER kibana diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index ee355d6a9811b..addedbb97f55a 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -75,7 +75,7 @@ export const LICENSE_OVERRIDES = { '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint 'node-sql-parser@3.6.1': ['(GPL-2.0 OR MIT)'], // GPL-2.0* https://github.com/taozhi8833998/node-sql-parser '@elastic/ems-client@7.15.0': ['Elastic License 2.0'], - '@elastic/eui@37.3.1': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@37.6.0': ['SSPL-1.0 OR Elastic License 2.0'], // TODO can be removed if the https://github.com/jindw/xmldom/issues/239 is released 'xmldom@0.1.27': ['MIT'], diff --git a/src/plugins/console/README.md b/src/plugins/console/README.md index e158e82c3702f..3bb58d37d1684 100644 --- a/src/plugins/console/README.md +++ b/src/plugins/console/README.md @@ -8,7 +8,7 @@ Console provides the user with tools for storing and executing requests against ### `load_from` query parameter -The `load_from` query parameter enables opening Console with prepopulated reuqests in two ways: from the elastic.co docs and from within other parts of Kibana. +The `load_from` query parameter enables opening Console with prepopulated requests in two ways: from the elastic.co docs and from within other parts of Kibana. Plugins can open requests in Kibana by assigning this parameter a `data:text/plain` [lz-string](https://pieroxy.net/blog/pages/lz-string/index.html) encoded value. For example, navigating to `/dev_tools#/console?load_from=data:text/plain,OIUQKgBA+gzgpgQwE4GMAWAoA3gIgI4CucSAnjgFy4C2CALulAgDZMVYC+nQA` will prepopulate Console with the following request: @@ -16,3 +16,221 @@ Plugins can open requests in Kibana by assigning this parameter a `data:text/pla GET _search {"query":{"match_all":{}}} ``` + +## Architecture +Console uses Ace editor that is wrapped with [`CoreEditor`](https://github.com/elastic/kibana/blob/master/src/plugins/console/public/types/core_editor.ts), so that if needed it can easily be replaced with another editor, for example Monaco. +The autocomplete logic is located in [`autocomplete`](https://github.com/elastic/kibana/blob/master/src/plugins/console/public/lib/autocomplete) folder. Autocomplete rules are computed by classes in `components` sub-folder. + +## Autocomplete definitions +Kibana users benefit greatly from autocomplete suggestions since not all Elasticsearch APIs can be provided with a corresponding UI. Autocomplete suggestions improve usability of Console for any Elasticsearch API endpoint. +Autocomplete definitions are all created in the form of javascript objects loaded from `json` and `js` files. + +### Creating definitions +The [`generated`](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/json/generated) folder contains definitions created automatically from Elasticsearch REST API specifications. See this [README](https://github.com/elastic/kibana/blob/master/packages/kbn-spec-to-console/README.md) file for more information on the `spec-to-console` script. + +Manually created override files in the [`overrides`](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/json/overrides) folder contain fixes for generated files and additions for request body parameters. + +### Top level keys +Use following top level keys in the definitions objects. + +#### `documentation` +Url to Elasticsearch REST API documentation for the endpoint (If the url contains `master` or `current` strings in the path, Console automatically replaces them with the `docLinkVersion` to always redirect the user to the correct version of the documentation). + +#### `methods` +Allowed http methods (`GET`, `POST` etc) + +#### `patterns` +Array of API endpoints that contain variables like `{indices}` or `{fields}`. For example, `{indices}/_rollup/{rollup_index}`. See the [Variables](#variables) section below for more info. + +#### `url_params` +Query url parameters and their values. See the [Query url parameters](#query-url-parameters) section below for more info. An example: +```json +{ + "url_params": { + "format": "", + "local": "__flag__", + "h": [], + "expand_wildcards": [ + "open", + "closed", + "hidden", + "none", + "all" + ] + } +} +``` + +#### `priority` +Value for selecting one autocomplete definition, if several configurations are loaded from the files. The highest number takes precedence. + +#### `data_autocomplete_rules` +Request body parameters and their values. Only used in `overrides` files because REST API specs don't contain any information about body request parameters. +Refer to Elasticsearch REST API documentation when configuring this object. See the [Request body parameters](#request-body-parameters) section below for more info. An example: +```json +{ + "data_autocomplete_rules": { + "text": [], + "field": "{field}", + "analyzer": "", + "explain": { "__one_of": [false, true] } + } +} +``` + +### Query url parameters +Query url parameters are configured in form of an object, for example: +```json +{ + "url_params": { + "local": "__flag__", + "scroll": "", + "expand_wildcards": [ + "open", + "closed", + "hidden", + "none", + "all" + ] + } +} +``` +This object specifies 3 query parameters: `local` (boolean value), `scroll` (no default value) and `expand_wildcards` (with a list of accepted values). + +When the user types in the url path into Console and at least 2 characters after `?`, all matching url parameters are displayed as autocomplete suggestions. In this example, after typing +``` +GET /_some_endpoint?ca +``` +"local" and "expand_wildcards" are displayed as suggestions. +When the user types at least 2 characters after `=`, all matching values for this parameter are displayed as autocomplete suggestions. In this example, after typing +``` +GET /_some_endpoint?expand_wildcards=hi +``` +"hidden" is displayed for autocompletion. + +Variables such as `{indices}` or `{fields}` are accepted both as an url parameter and its value in the configuration object. See the [Variables](#variables) section below for more information. + +### Request body parameters +Request body parameters are configured in form of an object, for example: +```json +{ + "data_autocomplete_rules": { + "index_patterns": [], + "mappings": { "__scope_link": "put_mapping" }, + "version": 0, + "aliases": { + "__template": { + "NAME": {} + } + } + } +} +``` +Object's keys are parameters that will be displayed as autocomplete suggestions when the user starts typing request body. In this example, after typing +``` +PUT /_some_endpoint +{ + " +``` +"index_patterns", "mappings", "version" and "aliases" are displayed as autocomplete suggestions. +Object's values provide default or accepted values of request body parameters. For example, if "version" is selected from the suggestions list, value `0` is automatically filled, resulting in the following request: +``` +PUT /_some_endpoint +{ + "version": 0 +} +``` +Object's values can contain objects for nested configuration because the engine can work recursively while searching for autocomplete suggestions. + +Following values can be used in the configuration object: +#### One value from the list (`__one_of: [..., ...]`) +Use this configuration for a parameter with a list of allowed values, for example types of snapshot repository: +``` +"type": {"__one_of": ["fs", "url", "s3", "hdfs", "azure"]} +``` +The first value in the list will be automatically filled as parameter value. For example, when "type" is selected from the suggestions list, the request body is autofilled as following: +``` +PUT /_some_endpoint +{ + "type": "fs" +} +``` +But if the value `fs` is deleted, all suggestions will be displayed: "fs", "url", "s3", "hdfs" and "azure". +Use `__one_of: [true, false]` for boolean values. + +#### Array of values (`[..., ... ]` or `__any_of: [..., ...]`) +Use this configuration for parameters which accept an array of values, for example actions parameter: +``` +"actions": { "__any_of": [ "add", "remove"]} +``` +When "actions" is selected from the suggestions list, it will be autocompleted with an empty array: +``` +POST /_some_endpoint +{ + "actions": [] +} +``` +All values in the array are displayed as suggestions for parameter values inside the array. + + +#### Default object structure (`__template: {...}`) +Use this configuration to insert an object with default values into the request body when the corresponding key is typed in. +For example, in this configuration +```json +{ + "terms": { + "__template": { + "field": "", + "size": 10 + }, + "field": "{field}", + "size": 10, + "shard_size": 10, + "min_doc_count": 10 + } +} +``` +the `terms` parameter has several properties, but only `field` and `size` are autocompleted in the request body when "terms" is selected from the suggestions list. +``` +POST /_some_endpoint +{ + terms: { + field: '', + size: 10, + } +} +``` +The rest of the properties are displayed as autocomplete suggestions, when the `terms` object is being edited. + +#### Scope link (`__scope_link`) +Use this type to copy a configuration object specified in a different endpoint definition. For example, the `put_settings` endpoint definition contains a configuration object that can be reused for `settings` parameter in a different endpoint: +```json +{ + "data_autocomplete_rules": { + "settings": { + "__scope_link": "put_settings" + } + } +} +``` +#### Global scope (`GLOBAL`) +Use `GLOBAL` keyword with `__scope_link` to refer to a reusable set of definitions created in the [`globals`](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/js/globals.ts) file. +For example: +```json +{ + "data_autocomplete_rules": { + "query": { + "__scope_link": "GLOBAL.query" + } + } +} +``` +#### Conditional definition (`__condition: { lines_regex: ... }`) +To provide a different set of autocomplete suggestions based on the value configured in the request. For example, when creating a snapshot repository of different types (`fs`, `url` etc) different properties are displayed in the suggestions list based on the type. See [snapshot.create_repository.json](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/json/overrides/snapshot.create_repository.json) for an example. + + +### Variables +Some autocomplete definitions need to be configured with dynamic values that can't be hard coded into a json or js file, for example a list of indices in the cluster. +A list of variables is defined in the `parametrizedComponentFactories` function in [`kb.js`](https://github.com/elastic/kibana/blob/master/src/plugins/console/public/lib/kb/kb.js) file. The values of these variables are assigned dynamically for every cluster. +Use these variables with curly braces, for example `{indices}`, `{types}`, `{id}`, `{username}`, `{template}`, `{nodes}` etc. + diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/__snapshots__/table_cell.test.tsx.snap b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/__snapshots__/table_cell.test.tsx.snap index 5f3564174adf8..7f7a63bf139b0 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/__snapshots__/table_cell.test.tsx.snap +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/__snapshots__/table_cell.test.tsx.snap @@ -31,11 +31,11 @@ exports[`Doc table cell component renders a cell with filter buttons if it is fi className="kbnDocTableCell__filterButton" content="Filter for value" delay="regular" + display="inlineBlock" position="bottom" > @@ -65,11 +65,11 @@ exports[`Doc table cell component renders a cell with filter buttons if it is fi className="kbnDocTableCell__filterButton" content="Filter out value" delay="regular" + display="inlineBlock" position="bottom" > diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_embeddable.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_embeddable.tsx index 04902af692b74..c01f661eb116a 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_embeddable.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_embeddable.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { memo, useCallback, useMemo } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react'; import './index.scss'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; @@ -24,30 +24,61 @@ export interface DocTableEmbeddableProps extends DocTableProps { const DocTableWrapperMemoized = memo(DocTableWrapper); export const DocTableEmbeddable = (props: DocTableEmbeddableProps) => { - const pager = usePager({ totalItems: props.rows.length }); + const tableWrapperRef = useRef(null); + const { + currentPage, + pageSize, + totalPages, + startIndex, + hasNextPage, + changePage, + changePageSize, + } = usePager({ + totalItems: props.rows.length, + }); + const showPagination = totalPages !== 0; - const pageOfItems = useMemo( - () => props.rows.slice(pager.startIndex, pager.pageSize + pager.startIndex), - [pager.pageSize, pager.startIndex, props.rows] - ); + const scrollTop = useCallback(() => { + if (tableWrapperRef.current) { + tableWrapperRef.current.scrollTo(0, 0); + } + }, []); + + const pageOfItems = useMemo(() => props.rows.slice(startIndex, pageSize + startIndex), [ + pageSize, + startIndex, + props.rows, + ]); - const shouldShowLimitedResultsWarning = () => - !pager.hasNextPage && props.rows.length < props.totalHitCount; + const onPageChange = useCallback( + (page: number) => { + scrollTop(); + changePage(page); + }, + [changePage, scrollTop] + ); - const scrollTop = () => { - const scrollDiv = document.querySelector('.kbnDocTableWrapper') as HTMLElement; - scrollDiv.scrollTo(0, 0); - }; + const onPageSizeChange = useCallback( + (size: number) => { + scrollTop(); + changePageSize(size); + }, + [changePageSize, scrollTop] + ); - const onPageChange = (page: number) => { - scrollTop(); - pager.onPageChange(page); - }; + /** + * Go to the first page if the current is no longer available + */ + useEffect(() => { + if (totalPages < currentPage + 1) { + onPageChange(0); + } + }, [currentPage, totalPages, onPageChange]); - const onPageSizeChange = (size: number) => { - scrollTop(); - pager.onPageSizeChange(size); - }; + const shouldShowLimitedResultsWarning = useMemo( + () => !hasNextPage && props.rows.length < props.totalHitCount, + [hasNextPage, props.rows.length, props.totalHitCount] + ); const sampleSize = useMemo(() => { return getServices().uiSettings.get(SAMPLE_SIZE_SETTING, 500); @@ -77,7 +108,7 @@ export const DocTableEmbeddable = (props: DocTableEmbeddableProps) => { responsive={false} wrap={true} > - {shouldShowLimitedResultsWarning() && ( + {shouldShowLimitedResultsWarning && ( { - + - - - + {showPagination && ( + + + + )} ); }; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx index 5f0825d9cbd15..dddfefa906962 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { Fragment, memo, useCallback, useEffect, useState } from 'react'; +import React, { Fragment, memo, useCallback, useEffect, useRef, useState } from 'react'; import './index.scss'; import { FormattedMessage } from '@kbn/i18n/react'; import { debounce } from 'lodash'; @@ -17,24 +17,88 @@ import { shouldLoadNextDocPatch } from './lib/should_load_next_doc_patch'; const FOOTER_PADDING = { padding: 0 }; -const DocTableInfiniteContent = (props: DocTableRenderProps) => { - const [limit, setLimit] = useState(props.minimumVisibleRows); +const DocTableWrapperMemoized = memo(DocTableWrapper); - // Reset infinite scroll limit - useEffect(() => { - setLimit(props.minimumVisibleRows); - }, [props.rows, props.minimumVisibleRows]); +interface DocTableInfiniteContentProps extends DocTableRenderProps { + limit: number; + onSetMaxLimit: () => void; + onBackToTop: () => void; +} + +const DocTableInfiniteContent = ({ + rows, + columnLength, + sampleSize, + limit, + onSkipBottomButtonClick, + renderHeader, + renderRows, + onSetMaxLimit, + onBackToTop, +}: DocTableInfiniteContentProps) => { + const onSkipBottomButton = useCallback(() => { + onSetMaxLimit(); + onSkipBottomButtonClick(); + }, [onSetMaxLimit, onSkipBottomButtonClick]); + + return ( + + + + {renderHeader()} + {renderRows(rows.slice(0, limit))} + + + + + +
+ {rows.length === sampleSize ? ( +
+ + + + +
+ ) : ( + + ​ + + )} +
+
+ ); +}; + +export const DocTableInfinite = (props: DocTableProps) => { + const tableWrapperRef = useRef(null); + const [limit, setLimit] = useState(50); /** * depending on which version of Discover is displayed, different elements are scrolling * and have therefore to be considered for calculation of infinite scrolling */ useEffect(() => { - const scrollDiv = document.querySelector('.kbnDocTableWrapper') as HTMLElement; + // After mounting table wrapper should be initialized + const scrollDiv = tableWrapperRef.current as HTMLDivElement; const scrollMobileElem = document.documentElement; const scheduleCheck = debounce(() => { const isMobileView = document.getElementsByClassName('dscSidebar__mobile').length > 0; + const usedScrollDiv = isMobileView ? scrollMobileElem : scrollDiv; if (shouldLoadNextDocPatch(usedScrollDiv)) { setLimit((prevLimit) => prevLimit + 50); @@ -58,63 +122,26 @@ const DocTableInfiniteContent = (props: DocTableRenderProps) => { focusElem.focus(); // Only the desktop one needs to target a specific container - if (!isMobileView) { - const scrollDiv = document.querySelector('.kbnDocTableWrapper') as HTMLElement; - scrollDiv.scrollTo(0, 0); + if (!isMobileView && tableWrapperRef.current) { + tableWrapperRef.current.scrollTo(0, 0); } else if (window) { window.scrollTo(0, 0); } }, []); - return ( - - - - {props.renderHeader()} - {props.renderRows(props.rows.slice(0, limit))} - - - - - -
- {props.rows.length === props.sampleSize ? ( -
- - - - -
- ) : ( - - ​ - - )} -
-
- ); -}; + const setMaxLimit = useCallback(() => setLimit(props.rows.length), [props.rows.length]); -const DocTableWrapperMemoized = memo(DocTableWrapper); -const DocTableInfiniteContentMemoized = memo(DocTableInfiniteContent); - -const renderDocTable = (tableProps: DocTableRenderProps) => ( - -); + const renderDocTable = useCallback( + (tableProps: DocTableRenderProps) => ( + + ), + [limit, onBackToTop, setMaxLimit] + ); -export const DocTableInfinite = (props: DocTableProps) => { - return ; + return ; }; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx index 08e7dcfb66695..2fac1c828796d 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { forwardRef, useCallback, useMemo } from 'react'; import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import type { IndexPattern, IndexPatternField } from 'src/plugins/data/common'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -86,7 +86,6 @@ export interface DocTableProps { export interface DocTableRenderProps { columnLength: number; rows: DocTableRow[]; - minimumVisibleRows: number; sampleSize: number; renderRows: (row: DocTableRow[]) => JSX.Element[]; renderHeader: () => JSX.Element; @@ -100,163 +99,166 @@ export interface DocTableWrapperProps extends DocTableProps { render: (params: DocTableRenderProps) => JSX.Element; } -export const DocTableWrapper = ({ - render, - columns, - rows, - indexPattern, - onSort, - onAddColumn, - onMoveColumn, - onRemoveColumn, - sort, - onFilter, - useNewFieldsApi, - searchDescription, - sharedItemTitle, - dataTestSubj, - isLoading, -}: DocTableWrapperProps) => { - const [minimumVisibleRows, setMinimumVisibleRows] = useState(50); - const [ - defaultSortOrder, - hideTimeColumn, - isShortDots, - sampleSize, - showMultiFields, - filterManager, - addBasePath, - ] = useMemo(() => { - const services = getServices(); - return [ - services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), - services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), - services.uiSettings.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE), - services.uiSettings.get(SAMPLE_SIZE_SETTING, 500), - services.uiSettings.get(SHOW_MULTIFIELDS, false), - services.filterManager, - services.addBasePath, - ]; - }, []); +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - const onSkipBottomButtonClick = useCallback(async () => { - // delay scrolling to after the rows have been rendered - const bottomMarker = document.getElementById('discoverBottomMarker'); - const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - // show all the rows - setMinimumVisibleRows(rows.length); - - while (rows.length !== document.getElementsByClassName('kbnDocTable__row').length) { - await wait(50); - } - bottomMarker!.focus(); - await wait(50); - bottomMarker!.blur(); - }, [setMinimumVisibleRows, rows]); - - const fieldsToShow = useMemo( - () => - getFieldsToShow( - indexPattern.fields.map((field: IndexPatternField) => field.name), - indexPattern, - showMultiFields - ), - [indexPattern, showMultiFields] - ); - - const renderHeader = useCallback( - () => ( - - ), - [ +export const DocTableWrapper = forwardRef( + ( + { + render, columns, - defaultSortOrder, - hideTimeColumn, + rows, indexPattern, - isShortDots, + onSort, + onAddColumn, onMoveColumn, onRemoveColumn, - onSort, sort, - ] - ); - - const renderRows = useCallback( - (rowsToRender: DocTableRow[]) => { - return rowsToRender.map((current) => ( - - )); - }, - [ - columns, onFilter, - indexPattern, useNewFieldsApi, + searchDescription, + sharedItemTitle, + dataTestSubj, + isLoading, + }: DocTableWrapperProps, + ref + ) => { + const [ + defaultSortOrder, hideTimeColumn, - onAddColumn, - onRemoveColumn, + isShortDots, + sampleSize, + showMultiFields, filterManager, addBasePath, - fieldsToShow, - ] - ); + ] = useMemo(() => { + const services = getServices(); + return [ + services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), + services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), + services.uiSettings.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE), + services.uiSettings.get(SAMPLE_SIZE_SETTING, 500), + services.uiSettings.get(SHOW_MULTIFIELDS, false), + services.filterManager, + services.addBasePath, + ]; + }, []); + + const onSkipBottomButtonClick = useCallback(async () => { + // delay scrolling to after the rows have been rendered + const bottomMarker = document.getElementById('discoverBottomMarker'); + + while (rows.length !== document.getElementsByClassName('kbnDocTable__row').length) { + await wait(50); + } + bottomMarker!.focus(); + await wait(50); + bottomMarker!.blur(); + }, [rows]); + + const fieldsToShow = useMemo( + () => + getFieldsToShow( + indexPattern.fields.map((field: IndexPatternField) => field.name), + indexPattern, + showMultiFields + ), + [indexPattern, showMultiFields] + ); + + const renderHeader = useCallback( + () => ( + + ), + [ + columns, + defaultSortOrder, + hideTimeColumn, + indexPattern, + isShortDots, + onMoveColumn, + onRemoveColumn, + onSort, + sort, + ] + ); + + const renderRows = useCallback( + (rowsToRender: DocTableRow[]) => { + return rowsToRender.map((current) => ( + + )); + }, + [ + columns, + onFilter, + indexPattern, + useNewFieldsApi, + hideTimeColumn, + onAddColumn, + onRemoveColumn, + filterManager, + addBasePath, + fieldsToShow, + ] + ); - return ( -
- {rows.length !== 0 && - render({ - columnLength: columns.length, - rows, - minimumVisibleRows, - sampleSize, - onSkipBottomButtonClick, - renderHeader, - renderRows, - })} - {!rows.length && ( -
- - - - - -
- )} -
- ); -}; + return ( +
} + > + {rows.length !== 0 && + render({ + columnLength: columns.length, + rows, + sampleSize, + onSkipBottomButtonClick, + renderHeader, + renderRows, + })} + {!rows.length && ( +
+ + + + + +
+ )} +
+ ); + } +); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.test.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.test.tsx new file mode 100644 index 0000000000000..e94600b5d1725 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import { usePager } from './use_pager'; + +describe('usePager', () => { + const defaultProps = { + totalItems: 745, + }; + + test('should initialize the first page', () => { + const { result } = renderHook(() => { + return usePager(defaultProps); + }); + + expect(result.current.currentPage).toEqual(0); + expect(result.current.pageSize).toEqual(50); + expect(result.current.totalPages).toEqual(15); + expect(result.current.startIndex).toEqual(0); + expect(result.current.hasNextPage).toEqual(true); + }); + + test('should change the page', () => { + const { result } = renderHook(() => { + return usePager(defaultProps); + }); + + act(() => { + result.current.changePage(5); + }); + + expect(result.current.currentPage).toEqual(5); + expect(result.current.pageSize).toEqual(50); + expect(result.current.totalPages).toEqual(15); + expect(result.current.startIndex).toEqual(250); + expect(result.current.hasNextPage).toEqual(true); + }); + + test('should go to the last page', () => { + const { result } = renderHook(() => { + return usePager(defaultProps); + }); + + act(() => { + result.current.changePage(15); + }); + + expect(result.current.currentPage).toEqual(15); + expect(result.current.pageSize).toEqual(50); + expect(result.current.totalPages).toEqual(15); + expect(result.current.startIndex).toEqual(750); + expect(result.current.hasNextPage).toEqual(false); + }); + + test('should change page size and stay on the current page', () => { + const { result } = renderHook(() => usePager(defaultProps)); + + act(() => { + result.current.changePage(5); + result.current.changePageSize(100); + }); + + expect(result.current.currentPage).toEqual(5); + expect(result.current.pageSize).toEqual(100); + expect(result.current.totalPages).toEqual(8); + expect(result.current.startIndex).toEqual(500); + expect(result.current.hasNextPage).toEqual(true); + }); +}); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.ts index 5522e3c150213..d21941b8360eb 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.ts @@ -6,73 +6,38 @@ * Side Public License, v 1. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; interface MetaParams { - currentPage: number; - totalItems: number; totalPages: number; startIndex: number; hasNextPage: boolean; - pageSize: number; -} - -interface ProvidedMeta { - updatedPageSize?: number; - updatedCurrentPage?: number; } const INITIAL_PAGE_SIZE = 50; export const usePager = ({ totalItems }: { totalItems: number }) => { - const [meta, setMeta] = useState({ - currentPage: 0, - totalItems, - startIndex: 0, - totalPages: Math.ceil(totalItems / INITIAL_PAGE_SIZE), - hasNextPage: true, - pageSize: INITIAL_PAGE_SIZE, - }); - - const getNewMeta = useCallback( - (newMeta: ProvidedMeta) => { - const actualCurrentPage = newMeta.updatedCurrentPage ?? meta.currentPage; - const actualPageSize = newMeta.updatedPageSize ?? meta.pageSize; - - const newTotalPages = Math.ceil(totalItems / actualPageSize); - const newStartIndex = actualPageSize * actualCurrentPage; - - return { - currentPage: actualCurrentPage, - totalPages: newTotalPages, - startIndex: newStartIndex, - totalItems, - hasNextPage: meta.currentPage + 1 < meta.totalPages, - pageSize: actualPageSize, - }; - }, - [meta.currentPage, meta.pageSize, meta.totalPages, totalItems] - ); + const [pageSize, setPageSize] = useState(INITIAL_PAGE_SIZE); + const [currentPage, setCurrentPage] = useState(0); - const onPageChange = useCallback( - (pageIndex: number) => setMeta(getNewMeta({ updatedCurrentPage: pageIndex })), - [getNewMeta] - ); + const meta: MetaParams = useMemo(() => { + const totalPages = Math.ceil(totalItems / pageSize); + return { + totalPages, + startIndex: pageSize * currentPage, + hasNextPage: currentPage + 1 < totalPages, + }; + }, [currentPage, pageSize, totalItems]); - const onPageSizeChange = useCallback( - (newPageSize: number) => - setMeta(getNewMeta({ updatedPageSize: newPageSize, updatedCurrentPage: 0 })), - [getNewMeta] - ); + const changePage = useCallback((pageIndex: number) => setCurrentPage(pageIndex), []); - /** - * Update meta on totalItems change - */ - useEffect(() => setMeta(getNewMeta({})), [getNewMeta, totalItems]); + const changePageSize = useCallback((newPageSize: number) => setPageSize(newPageSize), []); return { ...meta, - onPageChange, - onPageSizeChange, + currentPage, + pageSize, + changePage, + changePageSize, }; }; diff --git a/src/plugins/discover/public/application/components/discover_grid/constants.ts b/src/plugins/discover/public/application/components/discover_grid/constants.ts index 34e6ca20740ad..1126c6cbbf279 100644 --- a/src/plugins/discover/public/application/components/discover_grid/constants.ts +++ b/src/plugins/discover/public/application/components/discover_grid/constants.ts @@ -17,6 +17,7 @@ export const gridStyle = { export const pageSizeArr = [25, 50, 100, 250]; export const defaultPageSize = 100; +export const defaultTimeColumnWidth = 190; export const toolbarVisibility = { showColumnSelector: { allowHide: false, diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx index 3cbac90aa39cb..46e30dd23525b 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx @@ -119,7 +119,7 @@ describe('Discover grid columns ', function () { ], "display": "Time (timestamp)", "id": "timestamp", - "initialWidth": 180, + "initialWidth": 190, "isSortable": true, "schema": "datetime", }, diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx index 3a27772662b56..2f4c0b5167df8 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx @@ -11,10 +11,11 @@ import { i18n } from '@kbn/i18n'; import { EuiDataGridColumn, EuiScreenReaderOnly } from '@elastic/eui'; import { ExpandButton } from './discover_grid_expand_button'; import { DiscoverGridSettings } from './types'; -import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns'; +import type { IndexPattern } from '../../../../../data/common'; import { buildCellActions } from './discover_grid_cell_actions'; import { getSchemaByKbnType } from './discover_grid_schema'; import { SelectButton } from './discover_grid_document_selection'; +import { defaultTimeColumnWidth } from './constants'; export function getLeadControlColumns() { return [ @@ -88,7 +89,7 @@ export function buildEuiGridColumn( if (column.id === indexPattern.timeFieldName) { column.display = `${timeString} (${indexPattern.timeFieldName})`; - column.initialWidth = 180; + column.initialWidth = defaultTimeColumnWidth; } if (columnWidth > 0) { column.initialWidth = Number(columnWidth); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx index c5b75dbe85aea..e4b67c49689ab 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx @@ -107,6 +107,7 @@ export function DiscoverGridFlyout({ size="m" data-test-subj="docTableDetailsFlyout" onKeyDown={onKeyDown} + ownFocus={false} > )} {activePage !== -1 && ( - + diff --git a/src/plugins/discover/public/application/components/source_viewer/source_viewer.tsx b/src/plugins/discover/public/application/components/source_viewer/source_viewer.tsx index 1ec595c9d17f2..9e37ae8f8bf93 100644 --- a/src/plugins/discover/public/application/components/source_viewer/source_viewer.tsx +++ b/src/plugins/discover/public/application/components/source_viewer/source_viewer.tsx @@ -127,3 +127,7 @@ export const SourceViewer = ({ /> ); }; + +// Required for usage in React.lazy +// eslint-disable-next-line import/no-default-export +export default SourceViewer; diff --git a/src/plugins/discover/public/application/components/table/table.tsx b/src/plugins/discover/public/application/components/table/table.tsx index 456103c776566..e89b27e8069f1 100644 --- a/src/plugins/discover/public/application/components/table/table.tsx +++ b/src/plugins/discover/public/application/components/table/table.tsx @@ -145,3 +145,7 @@ export const DocViewerTable = ({ /> ); }; + +// Required for usage in React.lazy +// eslint-disable-next-line import/no-default-export +export default DocViewerTable; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 9e5e32111723b..4624fa79ca14b 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -27,6 +27,7 @@ import { KibanaLegacySetup, KibanaLegacyStart } from 'src/plugins/kibana_legacy/ import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public'; +import { EuiLoadingContent } from '@elastic/eui'; import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public'; import { SavedObjectLoader, SavedObjectsStart } from '../../saved_objects/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; @@ -34,7 +35,6 @@ import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; -import { DocViewerTable } from './application/components/table/table'; import { setDocViewsRegistry, setUrlTracker, @@ -59,7 +59,7 @@ import { SearchEmbeddableFactory } from './application/embeddable'; import { UsageCollectionSetup } from '../../usage_collection/public'; import { replaceUrlHashQuery } from '../../kibana_utils/public/'; import { IndexPatternFieldEditorStart } from '../../../plugins/index_pattern_field_editor/public'; -import { SourceViewer } from './application/components/source_viewer/source_viewer'; +import { DeferredSpinner } from './shared'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -67,6 +67,12 @@ declare module '../../share/public' { } } +const DocViewerTable = React.lazy(() => import('./application/components/table/table')); + +const SourceViewer = React.lazy( + () => import('./application/components/source_viewer/source_viewer') +); + /** * @public */ @@ -232,7 +238,17 @@ export class DiscoverPlugin defaultMessage: 'Table', }), order: 10, - component: DocViewerTable, + component: (props) => ( + + + + } + > + + + ), }); this.docViewsRegistry.addDocView({ title: i18n.translate('discover.docViews.json.jsonTitle', { @@ -240,12 +256,20 @@ export class DiscoverPlugin }), order: 20, component: ({ hit, indexPattern }) => ( - + + + + } + > + + ), }); diff --git a/src/plugins/discover/public/shared/components.tsx b/src/plugins/discover/public/shared/components.tsx new file mode 100644 index 0000000000000..11f77281f9eb0 --- /dev/null +++ b/src/plugins/discover/public/shared/components.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect, useRef, useState } from 'react'; + +/** + * A component that shows it children with a 300ms delay. This is good for wrapping + * loading spinners for tasks that might potentially be very fast (e.g. loading async chunks). + * That way we don't show a quick flash of the spinner before the actual content and will only + * show the spinner once loading takes a bit longer (more than 300ms). + */ +export const DeferredSpinner: React.FC = ({ children }) => { + const timeoutRef = useRef(); + const [showContent, setShowContent] = useState(false); + useEffect(() => { + timeoutRef.current = window.setTimeout(() => { + setShowContent(true); + }, 300); + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + return showContent ? {children} : null; +}; diff --git a/src/plugins/discover/public/shared/index.ts b/src/plugins/discover/public/shared/index.ts index c82ac074db111..7471e1293baa0 100644 --- a/src/plugins/discover/public/shared/index.ts +++ b/src/plugins/discover/public/shared/index.ts @@ -12,3 +12,5 @@ export async function loadSharingDataHelpers() { return await import('../application/apps/main/utils/get_sharing_data'); } + +export { DeferredSpinner } from './components'; diff --git a/src/plugins/home/common/instruction_variant.ts b/src/plugins/home/common/instruction_variant.ts index f27b2c97bdc1e..66c841cdc8b56 100644 --- a/src/plugins/home/common/instruction_variant.ts +++ b/src/plugins/home/common/instruction_variant.ts @@ -48,7 +48,7 @@ const DISPLAY_MAP = { [INSTRUCTION_VARIANT.LINUX]: 'Linux', [INSTRUCTION_VARIANT.PHP]: 'PHP', [INSTRUCTION_VARIANT.FLEET]: i18n.translate('home.tutorial.instruction_variant.fleet', { - defaultMessage: 'Elastic APM (beta) in Fleet', + defaultMessage: 'Elastic APM in Fleet', }), }; diff --git a/src/plugins/home/public/application/components/__snapshots__/recently_accessed.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/recently_accessed.test.js.snap index c9fd411ab6070..09ca9c0c16f8a 100644 --- a/src/plugins/home/public/application/components/__snapshots__/recently_accessed.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/recently_accessed.test.js.snap @@ -45,6 +45,7 @@ exports[`render 1`] = ` anchorClassName="homRecentlyAccessed__anchor" content="label0" delay="regular" + display="inlineBlock" position="bottom" >
diff --git a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap index 7b2729d2e1b68..d85f96382e803 100644 --- a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap +++ b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap @@ -164,7 +164,6 @@ exports[` is rendered 1`] = ` > diff --git a/src/plugins/kibana_usage_collection/README.md b/src/plugins/kibana_usage_collection/README.md index 1cb14cdef647e..4ea014457fd07 100644 --- a/src/plugins/kibana_usage_collection/README.md +++ b/src/plugins/kibana_usage_collection/README.md @@ -9,7 +9,7 @@ This plugin registers the Platform Usage Collectors in Kibana. | **Config Usage** | Reports the non-default values set via `kibana.yml` config file or CLI options. It `[redacts]` any potential PII-sensitive values. | [Link](./server/collectors/config_usage/README.md) | | **User-changed UI Settings** | Reports all the UI Settings that have been overwritten by the user. It `[redacts]` any potential PII-sensitive values. | [Link](./server/collectors/management/README.md) | | **CSP configuration** | Reports the key values regarding the CSP configuration. | - | -| **Kibana** | It reports the number of Saved Objects per type. It is limited to `dashboard`, `visualization`, `search`, `index-pattern`, `graph-workspace` and `timelion-sheet`.
It exists for legacy purposes, and may still be used by Monitoring via Metricbeat. | - | +| **Kibana** | It reports the number of Saved Objects per type. It is limited to `dashboard`, `visualization`, `search`, `index-pattern`, `graph-workspace`.
It exists for legacy purposes, and may still be used by Monitoring via Metricbeat. | - | | **Saved Objects Counts** | Number of Saved Objects per type. | - | | **Localization data** | Localization settings: setup locale and installed translation files. | - | | **Ops stats** | Operation metrics from the system. | - | diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index d29d6d6a86e85..5f268a6fdfee7 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -124,7 +124,6 @@ export const applicationUsageSchema = { kibana: commonSchema, // It's a forward app so we'll likely never report it management: commonSchema, short_url_redirect: commonSchema, // It's a forward app so we'll likely never report it - timelion: commonSchema, visualize: commonSchema, error: commonSchema, status: commonSchema, diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts index 0bba64823a3e2..68583502d3c9a 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts @@ -6,120 +6,120 @@ * Side Public License, v 1. */ -import fs from 'fs'; -import type { Request, RequestOptions } from './cloud_service'; +/* eslint-disable dot-notation */ +jest.mock('node-fetch'); +jest.mock('fs/promises'); import { AWSCloudService, AWSResponse } from './aws'; -type Callback = (err: unknown, res: unknown) => void; - -const AWS = new AWSCloudService(); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const fetchMock = require('node-fetch') as jest.Mock; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { readFile } = require('fs/promises') as { readFile: jest.Mock }; describe('AWS', () => { - const expectedFilenames = ['/sys/hypervisor/uuid', '/sys/devices/virtual/dmi/id/product_uuid']; - const expectedEncoding = 'utf8'; - // mixed case to ensure we check for ec2 after lowercasing - const ec2Uuid = 'eC2abcdef-ghijk\n'; - const ec2FileSystem = { - readFile: (filename: string, encoding: string, callback: Callback) => { - expect(expectedFilenames).toContain(filename); - expect(encoding).toEqual(expectedEncoding); - - callback(null, ec2Uuid); - }, - } as typeof fs; + const mockIsWindows = jest.fn(); + const awsService = new AWSCloudService(); + awsService['_isWindows'] = mockIsWindows.mockReturnValue(false); + readFile.mockResolvedValue('eC2abcdef-ghijk\n'); + beforeEach(() => jest.clearAllMocks()); it('is named "aws"', () => { - expect(AWS.getName()).toEqual('aws'); + expect(awsService.getName()).toEqual('aws'); }); describe('_checkIfService', () => { it('handles expected response', async () => { const id = 'abcdef'; - const request = ((req: RequestOptions, callback: Callback) => { - expect(req.method).toEqual('GET'); - expect(req.uri).toEqual( - 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document' - ); - expect(req.json).toEqual(true); - - const body = `{"instanceId": "${id}","availabilityZone":"us-fake-2c", "imageId" : "ami-6df1e514"}`; - - callback(null, { statusCode: 200, body }); - }) as Request; - // ensure it does not use the fs to trump the body - const awsCheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: false, + + fetchMock.mockResolvedValue({ + json: () => + `{"instanceId": "${id}","availabilityZone":"us-fake-2c", "imageId" : "ami-6df1e514"}`, + status: 200, + ok: true, }); - const response = await awsCheckedFileSystem._checkIfService(request); + const response = await awsService['_checkIfService'](); + expect(readFile).toBeCalledTimes(0); + expect(fetchMock).toBeCalledTimes(1); + expect(fetchMock).toBeCalledWith( + 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document', + { + method: 'GET', + } + ); expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id, - region: undefined, - vm_type: undefined, - zone: 'us-fake-2c', - metadata: { - imageId: 'ami-6df1e514', - }, - }); + expect(response.toJSON()).toMatchInlineSnapshot(` + Object { + "id": "abcdef", + "metadata": Object { + "imageId": "ami-6df1e514", + }, + "name": "aws", + "region": undefined, + "vm_type": undefined, + "zone": "us-fake-2c", + } + `); }); it('handles request without a usable body by downgrading to UUID detection', async () => { - const request = ((_req: RequestOptions, callback: Callback) => - callback(null, { statusCode: 404 })) as Request; - const awsCheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: false, + fetchMock.mockResolvedValue({ + json: () => null, + status: 200, + ok: true, }); - const response = await awsCheckedFileSystem._checkIfService(request); + const response = await awsService['_checkIfService'](); expect(response.isConfirmed()).toBe(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - vm_type: undefined, - zone: undefined, - metadata: undefined, - }); + expect(response.toJSON()).toMatchInlineSnapshot(` + Object { + "id": "ec2abcdef-ghijk", + "metadata": undefined, + "name": "aws", + "region": undefined, + "vm_type": undefined, + "zone": undefined, + } + `); }); it('handles request failure by downgrading to UUID detection', async () => { - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(new Error('expected: request failed'), null)) as Request; - const awsCheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: false, + fetchMock.mockResolvedValue({ + status: 404, + ok: false, }); - const response = await awsCheckedFileSystem._checkIfService(failedRequest); + const response = await awsService['_checkIfService'](); expect(response.isConfirmed()).toBe(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - vm_type: undefined, - zone: undefined, - metadata: undefined, - }); + expect(response.toJSON()).toMatchInlineSnapshot(` + Object { + "id": "ec2abcdef-ghijk", + "metadata": undefined, + "name": "aws", + "region": undefined, + "vm_type": undefined, + "zone": undefined, + } + `); }); it('handles not running on AWS', async () => { - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(null, null)) as Request; - const awsIgnoredFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: true, + fetchMock.mockResolvedValue({ + json: () => null, + status: 404, + ok: false, }); - const response = await awsIgnoredFileSystem._checkIfService(failedRequest); + mockIsWindows.mockReturnValue(true); + + const response = await awsService['_checkIfService'](); + expect(mockIsWindows).toBeCalledTimes(1); + expect(readFile).toBeCalledTimes(0); - expect(response.getName()).toEqual(AWS.getName()); + expect(response.getName()).toEqual('aws'); expect(response.isConfirmed()).toBe(false); }); }); @@ -144,10 +144,10 @@ describe('AWS', () => { marketplaceProductCodes: null, }; - const response = AWSCloudService.parseBody(AWS.getName(), body)!; + const response = awsService.parseBody(body)!; expect(response).not.toBeNull(); - expect(response.getName()).toEqual(AWS.getName()); + expect(response.getName()).toEqual('aws'); expect(response.isConfirmed()).toEqual(true); expect(response.toJSON()).toEqual({ name: 'aws', @@ -169,141 +169,84 @@ describe('AWS', () => { it('ignores unexpected response body', () => { // @ts-expect-error - expect(AWSCloudService.parseBody(AWS.getName(), undefined)).toBe(null); + expect(awsService.parseBody(undefined)).toBe(null); // @ts-expect-error - expect(AWSCloudService.parseBody(AWS.getName(), null)).toBe(null); + expect(awsService.parseBody(null)).toBe(null); // @ts-expect-error - expect(AWSCloudService.parseBody(AWS.getName(), {})).toBe(null); + expect(awsService.parseBody({})).toBe(null); // @ts-expect-error - expect(AWSCloudService.parseBody(AWS.getName(), { privateIp: 'a.b.c.d' })).toBe(null); + expect(awsService.parseBody({ privateIp: 'a.b.c.d' })).toBe(null); }); }); - describe('_tryToDetectUuid', () => { + describe('tryToDetectUuid', () => { describe('checks the file system for UUID if not Windows', () => { - it('checks /sys/hypervisor/uuid', async () => { - const awsCheckedFileSystem = new AWSCloudService({ - _fs: { - readFile: (filename: string, encoding: string, callback: Callback) => { - expect(expectedFilenames).toContain(filename); - expect(encoding).toEqual(expectedEncoding); - - callback(null, ec2Uuid); - }, - } as typeof fs, - _isWindows: false, - }); + beforeAll(() => mockIsWindows.mockReturnValue(false)); - const response = await awsCheckedFileSystem._tryToDetectUuid(); + it('checks /sys/hypervisor/uuid and /sys/devices/virtual/dmi/id/product_uuid', async () => { + const response = await awsService['tryToDetectUuid'](); - expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - zone: undefined, - vm_type: undefined, - metadata: undefined, - }); - }); + readFile.mockImplementation(async (filename: string, encoding: string) => { + expect(['/sys/hypervisor/uuid', '/sys/devices/virtual/dmi/id/product_uuid']).toContain( + filename + ); + expect(encoding).toEqual('utf8'); - it('checks /sys/devices/virtual/dmi/id/product_uuid', async () => { - const awsCheckedFileSystem = new AWSCloudService({ - _fs: { - readFile: (filename: string, encoding: string, callback: Callback) => { - expect(expectedFilenames).toContain(filename); - expect(encoding).toEqual(expectedEncoding); - - callback(null, ec2Uuid); - }, - } as typeof fs, - _isWindows: false, + return 'eC2abcdef-ghijk\n'; }); - const response = await awsCheckedFileSystem._tryToDetectUuid(); - + expect(readFile).toBeCalledTimes(2); expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - zone: undefined, - vm_type: undefined, - metadata: undefined, - }); + expect(response.toJSON()).toMatchInlineSnapshot(` + Object { + "id": "ec2abcdef-ghijk", + "metadata": undefined, + "name": "aws", + "region": undefined, + "vm_type": undefined, + "zone": undefined, + } + `); }); it('returns confirmed if only one file exists', async () => { - let callCount = 0; - const awsCheckedFileSystem = new AWSCloudService({ - _fs: { - readFile: (filename: string, encoding: string, callback: Callback) => { - if (callCount === 0) { - callCount++; - throw new Error('oops'); - } - callback(null, ec2Uuid); - }, - } as typeof fs, - _isWindows: false, - }); + readFile.mockRejectedValueOnce(new Error('oops')); + readFile.mockResolvedValueOnce('ec2Uuid'); - const response = await awsCheckedFileSystem._tryToDetectUuid(); + const response = await awsService['tryToDetectUuid'](); + expect(readFile).toBeCalledTimes(2); expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - zone: undefined, - vm_type: undefined, - metadata: undefined, - }); + expect(response.toJSON()).toMatchInlineSnapshot(` + Object { + "id": "ec2uuid", + "metadata": undefined, + "name": "aws", + "region": undefined, + "vm_type": undefined, + "zone": undefined, + } + `); }); it('returns unconfirmed if all files return errors', async () => { - const awsFailedFileSystem = new AWSCloudService({ - _fs: ({ - readFile: () => { - throw new Error('oops'); - }, - } as unknown) as typeof fs, - _isWindows: false, - }); - - const response = await awsFailedFileSystem._tryToDetectUuid(); + readFile.mockRejectedValue(new Error('oops')); + const response = await awsService['tryToDetectUuid'](); expect(response.isConfirmed()).toEqual(false); }); - }); - it('ignores UUID if it does not start with ec2', async () => { - const notEC2FileSystem = { - readFile: (filename: string, encoding: string, callback: Callback) => { - expect(expectedFilenames).toContain(filename); - expect(encoding).toEqual(expectedEncoding); + it('ignores UUID if it does not start with ec2', async () => { + readFile.mockResolvedValue('notEC2'); - callback(null, 'notEC2'); - }, - } as typeof fs; - - const awsCheckedFileSystem = new AWSCloudService({ - _fs: notEC2FileSystem, - _isWindows: false, + const response = await awsService['tryToDetectUuid'](); + expect(response.isConfirmed()).toEqual(false); }); - - const response = await awsCheckedFileSystem._tryToDetectUuid(); - - expect(response.isConfirmed()).toEqual(false); }); it('does NOT check the file system for UUID on Windows', async () => { - const awsUncheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: true, - }); - - const response = await awsUncheckedFileSystem._tryToDetectUuid(); + mockIsWindows.mockReturnValue(true); + const response = await awsService['tryToDetectUuid'](); expect(response.isConfirmed()).toEqual(false); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts index 69e5698489b30..785313e752c5e 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import fs from 'fs'; -import { get, isString, omit } from 'lodash'; -import { promisify } from 'util'; -import { CloudService, CloudServiceOptions, Request, RequestOptions } from './cloud_service'; +import { readFile } from 'fs/promises'; +import { get, omit } from 'lodash'; +import fetch from 'node-fetch'; +import { CloudService } from './cloud_service'; import { CloudServiceResponse } from './cloud_response'; // We explicitly call out the version, 2016-09-02, rather than 'latest' to avoid unexpected changes @@ -40,9 +40,9 @@ export interface AWSResponse { * @internal */ export class AWSCloudService extends CloudService { - private readonly _isWindows: boolean; - private readonly _fs: typeof fs; - + constructor() { + super('aws'); + } /** * Parse the AWS response, if possible. * @@ -64,7 +64,8 @@ export class AWSCloudService extends CloudService { * "version" : "2010-08-31", * } */ - static parseBody(name: string, body: AWSResponse): CloudServiceResponse | null { + parseBody = (body: AWSResponse): CloudServiceResponse | null => { + const name = this.getName(); const id: string | undefined = get(body, 'instanceId'); const vmType: string | undefined = get(body, 'instanceType'); const region: string | undefined = get(body, 'region'); @@ -88,64 +89,60 @@ export class AWSCloudService extends CloudService { } return null; - } + }; - constructor(options: CloudServiceOptions = {}) { - super('aws', options); + private _isWindows = (): boolean => { + return process.platform.startsWith('win'); + }; - // Allow the file system handler to be swapped out for tests - const { _fs = fs, _isWindows = process.platform.startsWith('win') } = options; - - this._fs = _fs; - this._isWindows = _isWindows; - } + protected _checkIfService = async () => { + try { + const response = await fetch(SERVICE_ENDPOINT, { + method: 'GET', + }); - async _checkIfService(request: Request) { - const req: RequestOptions = { - method: 'GET', - uri: SERVICE_ENDPOINT, - json: true, - }; + if (!response.ok || response.status === 404) { + throw new Error('AWS request failed'); + } - return promisify(request)(req) - .then((response) => - this._parseResponse(response.body, (body) => - AWSCloudService.parseBody(this.getName(), body) - ) - ) - .catch(() => this._tryToDetectUuid()); - } + const jsonBody: AWSResponse = await response.json(); + return this._parseResponse(jsonBody, this.parseBody); + } catch (_) { + return this.tryToDetectUuid(); + } + }; /** * Attempt to load the UUID by checking `/sys/hypervisor/uuid`. * * This is a fallback option if the metadata service is unavailable for some reason. */ - _tryToDetectUuid() { + private tryToDetectUuid = async () => { + const isWindows = this._isWindows(); // Windows does not have an easy way to check - if (!this._isWindows) { + if (!isWindows) { const pathsToCheck = ['/sys/hypervisor/uuid', '/sys/devices/virtual/dmi/id/product_uuid']; - const promises = pathsToCheck.map((path) => promisify(this._fs.readFile)(path, 'utf8')); - - return Promise.allSettled(promises).then((responses) => { - for (const response of responses) { - let uuid; - if (response.status === 'fulfilled' && isString(response.value)) { - // Some AWS APIs return it lowercase (like the file did in testing), while others return it uppercase - uuid = response.value.trim().toLowerCase(); - - // There is a small chance of a false positive here in the unlikely event that a uuid which doesn't - // belong to ec2 happens to be generated with `ec2` as the first three characters. - if (uuid.startsWith('ec2')) { - return new CloudServiceResponse(this._name, true, { id: uuid }); - } + const responses = await Promise.allSettled( + pathsToCheck.map((path) => readFile(path, 'utf8')) + ); + + for (const response of responses) { + let uuid; + if (response.status === 'fulfilled' && typeof response.value === 'string') { + // Some AWS APIs return it lowercase (like the file did in testing), while others return it uppercase + uuid = response.value.trim().toLowerCase(); + + // There is a small chance of a false positive here in the unlikely event that a uuid which doesn't + // belong to ec2 happens to be generated with `ec2` as the first three characters. + if (uuid.startsWith('ec2')) { + return new CloudServiceResponse(this._name, true, { id: uuid }); } } + } - return this._createUnconfirmedResponse(); - }); + return this._createUnconfirmedResponse(); } - return Promise.resolve(this._createUnconfirmedResponse()); - } + return this._createUnconfirmedResponse(); + }; } diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts index 17205562fa335..5bdbbbda55de6 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts @@ -6,36 +6,47 @@ * Side Public License, v 1. */ -import type { Request, RequestOptions } from './cloud_service'; +/* eslint-disable dot-notation */ +jest.mock('node-fetch'); import { AzureCloudService } from './azure'; -type Callback = (err: unknown, res: unknown) => void; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const fetchMock = require('node-fetch') as jest.Mock; -const AZURE = new AzureCloudService(); - -describe('Azure', () => { +describe('AzureCloudService', () => { + const azureCloudService = new AzureCloudService(); it('is named "azure"', () => { - expect(AZURE.getName()).toEqual('azure'); + expect(azureCloudService.getName()).toEqual('azure'); }); describe('_checkIfService', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it('handles expected response', async () => { const id = 'abcdef'; - const request = ((req: RequestOptions, callback: Callback) => { - expect(req.method).toEqual('GET'); - expect(req.uri).toEqual('http://169.254.169.254/metadata/instance?api-version=2017-04-02'); - expect(req.headers?.Metadata).toEqual('true'); - expect(req.json).toEqual(true); + fetchMock.mockResolvedValue({ + json: () => + `{"compute":{"vmId": "${id}","location":"fakeus","availabilityZone":"fakeus-2"}}`, + status: 200, + ok: true, + }); - const body = `{"compute":{"vmId": "${id}","location":"fakeus","availabilityZone":"fakeus-2"}}`; + const response = await azureCloudService['_checkIfService'](); - callback(null, { statusCode: 200, body }); - }) as Request; - const response = await AZURE._checkIfService(request); + expect(fetchMock).toBeCalledTimes(1); + expect(fetchMock).toBeCalledWith( + 'http://169.254.169.254/metadata/instance?api-version=2017-04-02', + { + method: 'GET', + headers: { Metadata: 'true' }, + } + ); expect(response.isConfirmed()).toEqual(true); expect(response.toJSON()).toEqual({ - name: AZURE.getName(), + name: azureCloudService.getName(), id, region: 'fakeus', vm_type: undefined, @@ -49,34 +60,30 @@ describe('Azure', () => { // NOTE: the CloudService method, checkIfService, catches the errors that follow it('handles not running on Azure with error by rethrowing it', async () => { const someError = new Error('expected: request failed'); - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(someError, null)) as Request; + fetchMock.mockRejectedValue(someError); - expect(async () => { - await AZURE._checkIfService(failedRequest); - }).rejects.toThrowError(someError.message); + await expect(() => azureCloudService['_checkIfService']()).rejects.toThrowError( + someError.message + ); }); it('handles not running on Azure with 404 response by throwing error', async () => { - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(null, { statusCode: 404 })) as Request; + fetchMock.mockResolvedValue({ status: 404 }); - expect(async () => { - await AZURE._checkIfService(failedRequest); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"Azure request failed"`); + await expect(() => + azureCloudService['_checkIfService']() + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Azure request failed"`); }); it('handles not running on Azure with unexpected response by throwing error', async () => { - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(null, null)) as Request; - - expect(async () => { - await AZURE._checkIfService(failedRequest); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"Azure request failed"`); + fetchMock.mockResolvedValue({ ok: false }); + await expect(() => + azureCloudService['_checkIfService']() + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Azure request failed"`); }); }); - describe('_parseBody', () => { + describe('parseBody', () => { // it's expected that most users use the resource manager UI (which has been out for years) it('parses object in expected format', () => { const body = { @@ -119,10 +126,10 @@ describe('Azure', () => { }, }; - const response = AzureCloudService.parseBody(AZURE.getName(), body)!; + const response = azureCloudService['parseBody'](body)!; expect(response).not.toBeNull(); - expect(response.getName()).toEqual(AZURE.getName()); + expect(response.getName()).toEqual(azureCloudService.getName()); expect(response.isConfirmed()).toEqual(true); expect(response.toJSON()).toEqual({ name: 'azure', @@ -172,10 +179,10 @@ describe('Azure', () => { }, }; - const response = AzureCloudService.parseBody(AZURE.getName(), body)!; + const response = azureCloudService['parseBody'](body)!; expect(response).not.toBeNull(); - expect(response.getName()).toEqual(AZURE.getName()); + expect(response.getName()).toEqual(azureCloudService.getName()); expect(response.isConfirmed()).toEqual(true); expect(response.toJSON()).toEqual({ name: 'azure', @@ -191,13 +198,13 @@ describe('Azure', () => { it('ignores unexpected response body', () => { // @ts-expect-error - expect(AzureCloudService.parseBody(AZURE.getName(), undefined)).toBe(null); + expect(azureCloudService['parseBody'](undefined)).toBe(null); // @ts-expect-error - expect(AzureCloudService.parseBody(AZURE.getName(), null)).toBe(null); + expect(azureCloudService['parseBody'](null)).toBe(null); // @ts-expect-error - expect(AzureCloudService.parseBody(AZURE.getName(), {})).toBe(null); + expect(azureCloudService['parseBody']({})).toBe(null); // @ts-expect-error - expect(AzureCloudService.parseBody(AZURE.getName(), { privateIp: 'a.b.c.d' })).toBe(null); + expect(azureCloudService['parseBody']({ privateIp: 'a.b.c.d' })).toBe(null); }); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts index b846636f0ce6c..06a135960bd60 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts @@ -7,8 +7,8 @@ */ import { get, omit } from 'lodash'; -import { promisify } from 'util'; -import { CloudService, Request } from './cloud_service'; +import fetch from 'node-fetch'; +import { CloudService } from './cloud_service'; import { CloudServiceResponse } from './cloud_response'; // 2017-04-02 is the first GA release of this API @@ -25,6 +25,9 @@ interface AzureResponse { * @internal */ export class AzureCloudService extends CloudService { + constructor() { + super('azure'); + } /** * Parse the Azure response, if possible. * @@ -51,7 +54,8 @@ export class AzureCloudService extends CloudService { * } * } */ - static parseBody(name: string, body: AzureResponse): CloudServiceResponse | null { + private parseBody = (body: AzureResponse): CloudServiceResponse | null => { + const name = this.getName(); const compute: Record | undefined = get(body, 'compute'); const id = get, string>(compute, 'vmId'); const vmType = get, string>(compute, 'vmSize'); @@ -72,32 +76,22 @@ export class AzureCloudService extends CloudService { } return null; - } - - constructor(options = {}) { - super('azure', options); - } + }; - async _checkIfService(request: Request) { - const req = { + protected _checkIfService = async () => { + const response = await fetch(SERVICE_ENDPOINT, { method: 'GET', - uri: SERVICE_ENDPOINT, headers: { // Azure requires this header Metadata: 'true', }, - json: true, - }; - - const response = await promisify(request)(req); + }); - // Note: there is no fallback option for Azure - if (!response || response.statusCode === 404) { + if (!response.ok || response.status === 404) { throw new Error('Azure request failed'); } - return this._parseResponse(response.body, (body) => - AzureCloudService.parseBody(this.getName(), body) - ); - } + const jsonBody: AzureResponse = await response.json(); + return this._parseResponse(jsonBody, this.parseBody); + }; } diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts index 3d093c81f8896..9930110979b87 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts @@ -37,9 +37,9 @@ export class CloudDetector { /** * Get any cloud details that we have detected. */ - getCloudDetails() { + public getCloudDetails = () => { return this.cloudDetails; - } + }; /** * Asynchronously detect the cloud service. @@ -48,9 +48,9 @@ export class CloudDetector { * caller to trigger the lookup and then simply use it whenever we * determine it. */ - async detectCloudService() { + public detectCloudService = async () => { this.cloudDetails = await this.getCloudService(); - } + }; /** * Check every cloud service until the first one reports success from detection. diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts index 0a7d5899486ab..22bef6753e9cf 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { CloudService, Response } from './cloud_service'; +import { CloudService } from './cloud_service'; import { CloudServiceResponse } from './cloud_response'; describe('CloudService', () => { @@ -30,9 +30,9 @@ describe('CloudService', () => { describe('_checkIfService', () => { it('throws an exception unless overridden', async () => { - expect(async () => { - await service._checkIfService(undefined); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"not implemented"`); + await expect(() => + service._checkIfService(undefined) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"not implemented"`); }); }); @@ -88,52 +88,59 @@ describe('CloudService', () => { describe('_parseResponse', () => { const body = { some: { body: {} } }; - it('throws error upon failure to parse body as object', async () => { - expect(async () => { - await service._parseResponse(); - }).rejects.toMatchInlineSnapshot(`undefined`); - expect(async () => { - await service._parseResponse(null); - }).rejects.toMatchInlineSnapshot(`undefined`); - expect(async () => { - await service._parseResponse({}); - }).rejects.toMatchInlineSnapshot(`undefined`); - expect(async () => { - await service._parseResponse(123); - }).rejects.toMatchInlineSnapshot(`undefined`); - expect(async () => { - await service._parseResponse('raw string'); - }).rejects.toMatchInlineSnapshot(`[Error: 'raw string' is not a JSON object]`); - expect(async () => { - await service._parseResponse('{{}'); - }).rejects.toMatchInlineSnapshot(`[Error: '{{}' is not a JSON object]`); + it('throws error upon failure to parse body as object', () => { + expect(() => service._parseResponse()).toThrowErrorMatchingInlineSnapshot( + `"Unable to handle body"` + ); + expect(() => service._parseResponse(null)).toThrowErrorMatchingInlineSnapshot( + `"Unable to handle body"` + ); + expect(() => service._parseResponse({})).toThrowErrorMatchingInlineSnapshot( + `"Unable to handle body"` + ); + expect(() => service._parseResponse(123)).toThrowErrorMatchingInlineSnapshot( + `"Unable to handle body"` + ); + expect(() => service._parseResponse('raw string')).toThrowErrorMatchingInlineSnapshot( + `"'raw string' is not a JSON object"` + ); + expect(() => service._parseResponse('{{}')).toThrowErrorMatchingInlineSnapshot( + `"'{{}' is not a JSON object"` + ); }); - it('expects unusable bodies', async () => { - const parseBody = (parsedBody: Response['body']) => { - expect(parsedBody).toEqual(body); - - return null; - }; - - expect(async () => { - await service._parseResponse(JSON.stringify(body), parseBody); - }).rejects.toMatchInlineSnapshot(`undefined`); - expect(async () => { - await service._parseResponse(body, parseBody); - }).rejects.toMatchInlineSnapshot(`undefined`); + it('expects unusable bodies', () => { + const parseBody = jest.fn().mockReturnValue(null); + + expect(() => + service._parseResponse(JSON.stringify(body), parseBody) + ).toThrowErrorMatchingInlineSnapshot(`"Unable to handle body"`); + expect(parseBody).toBeCalledTimes(1); + expect(parseBody).toBeCalledWith(body); + parseBody.mockClear(); + + expect(() => service._parseResponse(body, parseBody)).toThrowErrorMatchingInlineSnapshot( + `"Unable to handle body"` + ); + expect(parseBody).toBeCalledTimes(1); + expect(parseBody).toBeCalledWith(body); }); it('uses parsed object to create response', async () => { const serviceResponse = new CloudServiceResponse('a123', true, { id: 'xyz' }); - const parseBody = (parsedBody: Response['body']) => { - expect(parsedBody).toEqual(body); - - return serviceResponse; - }; + const parseBody = jest.fn().mockReturnValue(serviceResponse); const response = await service._parseResponse(body, parseBody); + expect(parseBody).toBeCalledWith(body); + expect(response).toBe(serviceResponse); + }); + + it('parses object before passing it to parseBody to create response', async () => { + const serviceResponse = new CloudServiceResponse('a123', true, { id: 'xyz' }); + const parseBody = jest.fn().mockReturnValue(serviceResponse); + const response = await service._parseResponse(JSON.stringify(body), parseBody); + expect(parseBody).toBeCalledWith(body); expect(response).toBe(serviceResponse); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts index 768a46a457d7d..bea51437d25c4 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts @@ -6,81 +6,56 @@ * Side Public License, v 1. */ -import fs from 'fs'; -import { isObject, isString, isPlainObject } from 'lodash'; -import defaultRequest from 'request'; -import type { OptionsWithUri, Response as DefaultResponse } from 'request'; +import { isObject, isPlainObject } from 'lodash'; import { CloudServiceResponse } from './cloud_response'; -/** @internal */ -export type Request = typeof defaultRequest; - -/** @internal */ -export type RequestOptions = OptionsWithUri; - -/** @internal */ -export type Response = DefaultResponse; - -/** @internal */ -export interface CloudServiceOptions { - _request?: Request; - _fs?: typeof fs; - _isWindows?: boolean; -} - /** * CloudService provides a mechanism for cloud services to be checked for * metadata that may help to determine the best defaults and priorities. */ export abstract class CloudService { - private readonly _request: Request; protected readonly _name: string; - constructor(name: string, options: CloudServiceOptions = {}) { + constructor(name: string) { this._name = name.toLowerCase(); - - // Allow the HTTP handler to be swapped out for tests - const { _request = defaultRequest } = options; - - this._request = _request; } /** * Get the search-friendly name of the Cloud Service. */ - getName() { + public getName = () => { return this._name; - } + }; /** * Using whatever mechanism is required by the current Cloud Service, * determine if Kibana is running in it and return relevant metadata. */ - async checkIfService() { + public checkIfService = async () => { try { - return await this._checkIfService(this._request); + return await this._checkIfService(); } catch (e) { return this._createUnconfirmedResponse(); } - } + }; - _checkIfService(request: Request): Promise { + protected _checkIfService = async (): Promise => { // should always be overridden by a subclass return Promise.reject(new Error('not implemented')); - } + }; /** * Create a new CloudServiceResponse that denotes that this cloud service * is not being used by the current machine / VM. */ - _createUnconfirmedResponse() { + protected _createUnconfirmedResponse = () => { return CloudServiceResponse.unconfirmed(this._name); - } + }; /** * Strictly parse JSON. */ - _stringToJson(value: string) { + protected _stringToJson = (value: string) => { // note: this will throw an error if this is not a string value = value.trim(); @@ -94,7 +69,7 @@ export abstract class CloudService { } catch (e) { throw new Error(`'${value}' is not a JSON object`); } - } + }; /** * Convert the response to a JSON object and attempt to parse it using the @@ -103,28 +78,21 @@ export abstract class CloudService { * If the response cannot be parsed as a JSON object, or if it fails to be * useful, then parseBody should return null. */ - _parseResponse( - body: Response['body'], - parseBody?: (body: Response['body']) => CloudServiceResponse | null - ): Promise { + protected _parseResponse = ( + body: string | Body, + parseBodyFn: (body: Body) => CloudServiceResponse | null + ): CloudServiceResponse => { // parse it if necessary - if (isString(body)) { - try { - body = this._stringToJson(body); - } catch (err) { - return Promise.reject(err); - } - } - - if (isObject(body) && parseBody) { - const response = parseBody(body); + const jsonBody: Body = typeof body === 'string' ? this._stringToJson(body) : body; + if (isObject(jsonBody) && typeof parseBodyFn !== 'undefined') { + const response = parseBodyFn(jsonBody); if (response) { - return Promise.resolve(response); + return response; } } // use default handling - return Promise.reject(); - } + throw new Error('Unable to handle body'); + }; } diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts index fd0b3331b4ad1..40bd0ef1fa1b1 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts @@ -5,136 +5,185 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -import type { Request, RequestOptions } from './cloud_service'; +/* eslint-disable dot-notation */ +jest.mock('node-fetch'); import { GCPCloudService } from './gcp'; - -type Callback = (err: unknown, res: unknown) => void; - -const GCP = new GCPCloudService(); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const fetchMock = require('node-fetch') as jest.Mock; describe('GCP', () => { + const gcpService = new GCPCloudService(); + beforeEach(() => jest.clearAllMocks()); + it('is named "gcp"', () => { - expect(GCP.getName()).toEqual('gcp'); + expect(gcpService.getName()).toEqual('gcp'); }); describe('_checkIfService', () => { // GCP responds with the header that they expect (and request lowercases the header's name) - const headers = { 'metadata-flavor': 'Google' }; + const headers = new Map(); + headers.set('metadata-flavor', 'Google'); it('handles expected responses', async () => { + const basePath = 'http://169.254.169.254/computeMetadata/v1/instance/'; const metadata: Record = { id: 'abcdef', 'machine-type': 'projects/441331612345/machineTypes/f1-micro', zone: 'projects/441331612345/zones/us-fake4-c', }; - const request = ((req: RequestOptions, callback: Callback) => { - const basePath = 'http://169.254.169.254/computeMetadata/v1/instance/'; - - expect(req.method).toEqual('GET'); - expect((req.uri as string).startsWith(basePath)).toBe(true); - expect(req.headers!['Metadata-Flavor']).toEqual('Google'); - expect(req.json).toEqual(false); - const requestKey = (req.uri as string).substring(basePath.length); - let body = null; + fetchMock.mockImplementation((url: string) => { + const requestKey = url.substring(basePath.length); + let body: string | null = null; if (metadata[requestKey]) { body = metadata[requestKey]; } + return { + status: 200, + ok: true, + text: () => body, + headers, + }; + }); - callback(null, { statusCode: 200, body, headers }); - }) as Request; - const response = await GCP._checkIfService(request); + const response = await gcpService['_checkIfService'](); + const fetchParams = { + headers: { 'Metadata-Flavor': 'Google' }, + method: 'GET', + }; + expect(fetchMock).toBeCalledTimes(3); + expect(fetchMock).toHaveBeenNthCalledWith(1, `${basePath}id`, fetchParams); + expect(fetchMock).toHaveBeenNthCalledWith(2, `${basePath}machine-type`, fetchParams); + expect(fetchMock).toHaveBeenNthCalledWith(3, `${basePath}zone`, fetchParams); expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: GCP.getName(), - id: metadata.id, - region: 'us-fake4', - vm_type: 'f1-micro', - zone: 'us-fake4-c', - metadata: undefined, - }); + expect(response.toJSON()).toMatchInlineSnapshot(` + Object { + "id": "abcdef", + "metadata": undefined, + "name": "gcp", + "region": "us-fake4", + "vm_type": "f1-micro", + "zone": "us-fake4-c", + } + `); }); // NOTE: the CloudService method, checkIfService, catches the errors that follow it('handles unexpected responses', async () => { - const request = ((_req: RequestOptions, callback: Callback) => - callback(null, { statusCode: 200, headers })) as Request; + fetchMock.mockResolvedValue({ + status: 200, + ok: true, + headers, + text: () => undefined, + }); - expect(async () => { - await GCP._checkIfService(request); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"unrecognized responses"`); + await expect(() => + gcpService['_checkIfService']() + ).rejects.toThrowErrorMatchingInlineSnapshot(`"unrecognized responses"`); }); it('handles unexpected responses without response header', async () => { - const body = 'xyz'; - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(null, { statusCode: 200, body })) as Request; + fetchMock.mockResolvedValue({ + status: 200, + ok: true, + headers: new Map(), + text: () => 'xyz', + }); - expect(async () => { - await GCP._checkIfService(failedRequest); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"unrecognized responses"`); + await expect(() => + gcpService['_checkIfService']() + ).rejects.toThrowErrorMatchingInlineSnapshot(`"GCP request failed"`); }); - it('handles not running on GCP with error by rethrowing it', async () => { + it('handles not running on GCP', async () => { const someError = new Error('expected: request failed'); - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(someError, null)) as Request; + fetchMock.mockRejectedValue(someError); - expect(async () => { - await GCP._checkIfService(failedRequest); - }).rejects.toThrowError(someError); + await expect(() => + gcpService['_checkIfService']() + ).rejects.toThrowErrorMatchingInlineSnapshot(`"GCP request failed"`); }); it('handles not running on GCP with 404 response by throwing error', async () => { - const body = 'This is some random error text'; - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(null, { statusCode: 404, headers, body })) as Request; + fetchMock.mockResolvedValue({ + status: 404, + ok: false, + headers, + text: () => 'This is some random error text', + }); - expect(async () => { - await GCP._checkIfService(failedRequest); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GCP request failed"`); + await expect(() => + gcpService['_checkIfService']() + ).rejects.toThrowErrorMatchingInlineSnapshot(`"GCP request failed"`); }); - it('handles not running on GCP with unexpected response by throwing error', async () => { - const failedRequest = ((_req: RequestOptions, callback: Callback) => - callback(null, null)) as Request; + it('handles GCP response even if some requests fail', async () => { + fetchMock + .mockResolvedValueOnce({ + status: 200, + ok: true, + headers, + text: () => 'some_id', + }) + .mockRejectedValueOnce({ + status: 500, + ok: false, + headers, + text: () => 'This is some random error text', + }) + .mockResolvedValueOnce({ + status: 404, + ok: false, + headers, + text: () => 'URI Not found', + }); + const response = await gcpService['_checkIfService'](); + + expect(fetchMock).toBeCalledTimes(3); - expect(async () => { - await GCP._checkIfService(failedRequest); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GCP request failed"`); + expect(response.isConfirmed()).toEqual(true); + expect(response.toJSON()).toMatchInlineSnapshot(` + Object { + "id": "some_id", + "metadata": undefined, + "name": "gcp", + "region": undefined, + "vm_type": undefined, + "zone": undefined, + } + `); }); }); - describe('_extractValue', () => { + describe('extractValue', () => { it('only handles strings', () => { // @ts-expect-error - expect(GCP._extractValue()).toBe(undefined); + expect(gcpService['extractValue']()).toBe(undefined); // @ts-expect-error - expect(GCP._extractValue(null, null)).toBe(undefined); + expect(gcpService['extractValue'](null, null)).toBe(undefined); // @ts-expect-error - expect(GCP._extractValue('abc', { field: 'abcxyz' })).toBe(undefined); + expect(gcpService['extractValue']('abc', { field: 'abcxyz' })).toBe(undefined); // @ts-expect-error - expect(GCP._extractValue('abc', 1234)).toBe(undefined); - expect(GCP._extractValue('abc/', 'abc/xyz')).toEqual('xyz'); + expect(gcpService['extractValue']('abc', 1234)).toBe(undefined); + expect(gcpService['extractValue']('abc/', 'abc/xyz')).toEqual('xyz'); }); it('uses the last index of the prefix to truncate', () => { - expect(GCP._extractValue('abc/', ' \n 123/abc/xyz\t \n')).toEqual('xyz'); + expect(gcpService['extractValue']('abc/', ' \n 123/abc/xyz\t \n')).toEqual('xyz'); }); }); - describe('_combineResponses', () => { + describe('combineResponses', () => { it('parses in expected format', () => { const id = '5702733457649812345'; const machineType = 'projects/441331612345/machineTypes/f1-micro'; const zone = 'projects/441331612345/zones/us-fake4-c'; - const response = GCP._combineResponses(id, machineType, zone); + const response = gcpService['combineResponses'](id, machineType, zone); - expect(response.getName()).toEqual(GCP.getName()); + expect(response.getName()).toEqual('gcp'); expect(response.isConfirmed()).toEqual(true); expect(response.toJSON()).toEqual({ name: 'gcp', @@ -152,9 +201,9 @@ describe('GCP', () => { const machineType = 'f1-micro'; const zone = 'us-fake4-c'; - const response = GCP._combineResponses(id, machineType, zone); + const response = gcpService['combineResponses'](id, machineType, zone); - expect(response.getName()).toEqual(GCP.getName()); + expect(response.getName()).toEqual('gcp'); expect(response.isConfirmed()).toEqual(true); expect(response.toJSON()).toEqual({ name: 'gcp', @@ -167,18 +216,16 @@ describe('GCP', () => { }); it('ignores unexpected response body', () => { + expect(() => gcpService['combineResponses']()).toThrow(); + expect(() => gcpService['combineResponses'](undefined, undefined, undefined)).toThrow(); // @ts-expect-error - expect(() => GCP._combineResponses()).toThrow(); - // @ts-expect-error - expect(() => GCP._combineResponses(undefined, undefined, undefined)).toThrow(); - // @ts-expect-error - expect(() => GCP._combineResponses(null, null, null)).toThrow(); + expect(() => gcpService['combineResponses'](null, null, null)).toThrow(); expect(() => // @ts-expect-error - GCP._combineResponses({ id: 'x' }, { machineType: 'a' }, { zone: 'b' }) + gcpService['combineResponses']({ id: 'x' }, { machineType: 'a' }, { zone: 'b' }) ).toThrow(); // @ts-expect-error - expect(() => GCP._combineResponses({ privateIp: 'a.b.c.d' })).toThrow(); + expect(() => gcpService['combineResponses']({ privateIp: 'a.b.c.d' })).toThrow(); }); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts index 565c07abd1d2c..2cdf3f87cfe8f 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts @@ -6,14 +6,15 @@ * Side Public License, v 1. */ -import { isString } from 'lodash'; -import { promisify } from 'util'; -import { CloudService, CloudServiceOptions, Request, Response } from './cloud_service'; +import fetch, { Response } from 'node-fetch'; +import { CloudService } from './cloud_service'; import { CloudServiceResponse } from './cloud_response'; // GCP documentation shows both 'metadata.google.internal' (mostly) and '169.254.169.254' (sometimes) // To bypass potential DNS changes, the IP was used because it's shared with other cloud services const SERVICE_ENDPOINT = 'http://169.254.169.254/computeMetadata/v1/instance'; +// GCP required headers +const SERVICE_HEADERS = { 'Metadata-Flavor': 'Google' }; /** * Checks and loads the service metadata for an Google Cloud Platform VM if it is available. @@ -21,61 +22,54 @@ const SERVICE_ENDPOINT = 'http://169.254.169.254/computeMetadata/v1/instance'; * @internal */ export class GCPCloudService extends CloudService { - constructor(options: CloudServiceOptions = {}) { - super('gcp', options); + constructor() { + super('gcp'); } - _checkIfService(request: Request) { + protected _checkIfService = async () => { // we need to call GCP individually for each field we want metadata for const fields = ['id', 'machine-type', 'zone']; - const create = this._createRequestForField; - const allRequests = fields.map((field) => promisify(request)(create(field))); - return ( - Promise.all(allRequests) - // Note: there is no fallback option for GCP; - // responses are arrays containing [fullResponse, body]; - // because GCP returns plaintext, we have no way of validating - // without using the response code. - .then((responses) => { - return responses.map((response) => { - if (!response || response.statusCode === 404) { - throw new Error('GCP request failed'); - } - return this._extractBody(response, response.body); - }); - }) - .then(([id, machineType, zone]) => this._combineResponses(id, machineType, zone)) + const settledResponses = await Promise.allSettled( + fields.map(async (field) => { + return await fetch(`${SERVICE_ENDPOINT}/${field}`, { + method: 'GET', + headers: { ...SERVICE_HEADERS }, + }); + }) ); - } - _createRequestForField(field: string) { - return { - method: 'GET', - uri: `${SERVICE_ENDPOINT}/${field}`, - headers: { - // GCP requires this header - 'Metadata-Flavor': 'Google', - }, - // GCP does _not_ return JSON - json: false, - }; - } + const hasValidResponses = settledResponses.some(this.isValidResponse); - /** - * Extract the body if the response is valid and it came from GCP. - */ - _extractBody(response: Response, body?: Response['body']) { - if ( - response?.statusCode === 200 && - response.headers && - response.headers['metadata-flavor'] === 'Google' - ) { - return body; + if (!hasValidResponses) { + throw new Error('GCP request failed'); } - return null; - } + // Note: there is no fallback option for GCP; + // responses are arrays containing [fullResponse, body]; + // because GCP returns plaintext, we have no way of validating + // without using the response code. + const [id, machineType, zone] = await Promise.all( + settledResponses.map(async (settledResponse) => { + if (this.isValidResponse(settledResponse)) { + // GCP does _not_ return JSON + return await settledResponse.value.text(); + } + }) + ); + + return this.combineResponses(id, machineType, zone); + }; + + private isValidResponse = ( + settledResponse: PromiseSettledResult + ): settledResponse is PromiseFulfilledResult => { + if (settledResponse.status === 'rejected') { + return false; + } + const { value } = settledResponse; + return value.ok && value.status !== 404 && value.headers.get('metadata-flavor') === 'Google'; + }; /** * Parse the GCP responses, if possible. @@ -86,17 +80,11 @@ export class GCPCloudService extends CloudService { * machineType: 'projects/441331612345/machineTypes/f1-micro' * zone: 'projects/441331612345/zones/us-east4-c' */ - _combineResponses(id: string, machineType: string, zone: string) { - const vmId = isString(id) ? id.trim() : undefined; - const vmType = this._extractValue('machineTypes/', machineType); - const vmZone = this._extractValue('zones/', zone); - - let region; - - if (vmZone) { - // converts 'us-east4-c' into 'us-east4' - region = vmZone.substring(0, vmZone.lastIndexOf('-')); - } + private combineResponses = (id?: string, machineType?: string, zone?: string) => { + const vmId = typeof id === 'string' ? id.trim() : undefined; + const vmType = this.extractValue('machineTypes/', machineType); + const vmZone = this.extractValue('zones/', zone); + const region = vmZone ? vmZone.substring(0, vmZone.lastIndexOf('-')) : undefined; // ensure we actually have some data if (vmId || vmType || region || vmZone) { @@ -104,7 +92,7 @@ export class GCPCloudService extends CloudService { } throw new Error('unrecognized responses'); - } + }; /** * Extract the useful information returned from GCP while discarding @@ -113,15 +101,15 @@ export class GCPCloudService extends CloudService { * For example, this turns something like * 'projects/441331612345/machineTypes/f1-micro' into 'f1-micro'. */ - _extractValue(fieldPrefix: string, value: string) { - if (isString(value)) { - const index = value.lastIndexOf(fieldPrefix); - - if (index !== -1) { - return value.substring(index + fieldPrefix.length).trim(); - } + private extractValue = (fieldPrefix: string, value?: string) => { + if (typeof value !== 'string') { + return; } - return undefined; - } + const index = value.lastIndexOf(fieldPrefix); + + if (index !== -1) { + return value.substring(index + fieldPrefix.length).trim(); + } + }; } diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 68d56944d9974..237ec54e4692b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -104,22 +104,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, }, - 'timelion:default_rows': { - type: 'long', - _meta: { description: 'Non-default value of setting.' }, - }, - 'timelion:default_columns': { - type: 'long', - _meta: { description: 'Non-default value of setting.' }, - }, 'timelion:es.default_index': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, }, - 'timelion:showTutorial': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, 'securitySolution:timeDefaults': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index ed46e6b38b283..0c4b848ff3544 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -49,10 +49,7 @@ export interface UsageStats { 'timelion:max_buckets': number; 'timelion:es.timefield': string; 'timelion:min_interval': string; - 'timelion:default_rows': number; - 'timelion:default_columns': number; 'timelion:es.default_index': string; - 'timelion:showTutorial': boolean; 'securitySolution:timeDefaults': string; 'securitySolution:defaultAnomalyScore': number; 'securitySolution:refreshIntervalDefaults': string; diff --git a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts index 6097910afe22b..fc9f9a6e8c2d3 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts @@ -56,7 +56,6 @@ describe('kibana_usage', () => { search: { total: 0 }, index_pattern: { total: 0 }, graph_workspace: { total: 0 }, - timelion_sheet: { total: 0 }, }); }); }); @@ -81,7 +80,6 @@ describe('getKibanaSavedObjectCounts', () => { search: { total: 0 }, index_pattern: { total: 0 }, graph_workspace: { total: 0 }, - timelion_sheet: { total: 0 }, }); }); @@ -91,7 +89,6 @@ describe('getKibanaSavedObjectCounts', () => { types: { buckets: [ { key: 'dashboard', doc_count: 1 }, - { key: 'timelion-sheet', doc_count: 2 }, { key: 'index-pattern', value: 2 }, // Malformed on purpose { key: 'graph_workspace', doc_count: 3 }, // already snake_cased ], @@ -106,7 +103,6 @@ describe('getKibanaSavedObjectCounts', () => { search: { total: 0 }, index_pattern: { total: 0 }, graph_workspace: { total: 3 }, - timelion_sheet: { total: 2 }, }); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts index 1ebb61c446083..13b9d0ca2104c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts @@ -19,21 +19,13 @@ interface KibanaSavedObjectCounts { search: { total: number }; index_pattern: { total: number }; graph_workspace: { total: number }; - timelion_sheet: { total: number }; } interface KibanaUsage extends KibanaSavedObjectCounts { index: string; } -const TYPES = [ - 'dashboard', - 'visualization', - 'search', - 'index-pattern', - 'graph-workspace', - 'timelion-sheet', -]; +const TYPES = ['dashboard', 'visualization', 'search', 'index-pattern', 'graph-workspace']; export async function getKibanaSavedObjectCounts( esClient: ElasticsearchClient, @@ -89,12 +81,6 @@ export function registerKibanaUsageCollector( _meta: { description: 'Total number of graph_workspace saved objects' }, }, }, - timelion_sheet: { - total: { - type: 'long', - _meta: { description: 'Total number of timelion_sheet saved objects' }, - }, - }, }, async fetch({ esClient }) { const { diff --git a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts index fec314fc6b87e..5252ab24395aa 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts @@ -28,7 +28,6 @@ const uiMetricFromDataPluginSchema: MakeSchemaFrom = { kibana: commonSchema, // It's a forward app so we'll likely never report it management: commonSchema, short_url_redirect: commonSchema, // It's a forward app so we'll likely never report it - timelion: commonSchema, visualize: commonSchema, // X-Pack diff --git a/src/plugins/maps_ems/common/ems_defaults.ts b/src/plugins/maps_ems/common/ems_defaults.ts deleted file mode 100644 index 7eb82ac04858e..0000000000000 --- a/src/plugins/maps_ems/common/ems_defaults.ts +++ /dev/null @@ -1,18 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// Default config for the elastic hosted EMS endpoints -export const DEFAULT_EMS_FILE_API_URL = 'https://vector.maps.elastic.co'; -export const DEFAULT_EMS_TILE_API_URL = 'https://tiles.maps.elastic.co'; -export const DEFAULT_EMS_LANDING_PAGE_URL = 'https://maps.elastic.co/v7.15'; -export const DEFAULT_EMS_FONT_LIBRARY_URL = - 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'; - -export const DEFAULT_EMS_ROADMAP_ID = 'road_map'; -export const DEFAULT_EMS_ROADMAP_DESATURATED_ID = 'road_map_desaturated'; -export const DEFAULT_EMS_DARKMAP_ID = 'dark_map'; diff --git a/src/plugins/maps_ems/common/index.ts b/src/plugins/maps_ems/common/index.ts index d83a3319d3d15..f7d7ff1102e59 100644 --- a/src/plugins/maps_ems/common/index.ts +++ b/src/plugins/maps_ems/common/index.ts @@ -6,10 +6,16 @@ * Side Public License, v 1. */ -// TODO: https://github.com/elastic/kibana/issues/109853 -/* eslint-disable @kbn/eslint/no_export_all */ - export const TMS_IN_YML_ID = 'TMS in config/kibana.yml'; -export * from './ems_defaults'; +export const DEFAULT_EMS_FILE_API_URL = 'https://vector.maps.elastic.co'; +export const DEFAULT_EMS_TILE_API_URL = 'https://tiles.maps.elastic.co'; +export const DEFAULT_EMS_LANDING_PAGE_URL = 'https://maps.elastic.co/v7.15'; +export const DEFAULT_EMS_FONT_LIBRARY_URL = + 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'; + +export const DEFAULT_EMS_ROADMAP_ID = 'road_map'; +export const DEFAULT_EMS_ROADMAP_DESATURATED_ID = 'road_map_desaturated'; +export const DEFAULT_EMS_DARKMAP_ID = 'dark_map'; + export { ORIGIN } from './origin'; diff --git a/src/plugins/maps_ems/config.ts b/src/plugins/maps_ems/config.ts index ed1648ebbe8bb..710cb52f32a09 100644 --- a/src/plugins/maps_ems/config.ts +++ b/src/plugins/maps_ems/config.ts @@ -40,7 +40,6 @@ export const emsConfigSchema = schema.object({ tilemap: tilemapConfigSchema, includeElasticMapsService: schema.boolean({ defaultValue: true }), proxyElasticMapsServiceInMaps: schema.boolean({ defaultValue: false }), - manifestServiceUrl: schema.string({ defaultValue: '' }), emsUrl: schema.conditional( schema.siblingRef('proxyElasticMapsServiceInMaps'), true, diff --git a/src/plugins/maps_ems/public/index.ts b/src/plugins/maps_ems/public/index.ts index 50f55dd3157bc..a4a0fc45d9164 100644 --- a/src/plugins/maps_ems/public/index.ts +++ b/src/plugins/maps_ems/public/index.ts @@ -6,9 +6,6 @@ * Side Public License, v 1. */ -// TODO: https://github.com/elastic/kibana/issues/109853 -/* eslint-disable @kbn/eslint/no_export_all */ - import { PluginInitializerContext } from 'kibana/public'; import { MapsEmsPlugin } from './plugin'; import { IServiceSettings } from './service_settings'; @@ -27,9 +24,9 @@ export function plugin(initializerContext: PluginInitializerContext) { return new MapsEmsPlugin(initializerContext); } -export type { MapsEmsConfig } from '../config'; +export { TMS_IN_YML_ID } from '../common'; -export * from '../common'; +export type { MapsEmsConfig } from '../config'; export interface MapsEmsPluginSetup { config: MapsEmsConfig; diff --git a/src/plugins/maps_ems/server/index.ts b/src/plugins/maps_ems/server/index.ts index b8b84d222b958..7422dbcfcdec9 100644 --- a/src/plugins/maps_ems/server/index.ts +++ b/src/plugins/maps_ems/server/index.ts @@ -19,7 +19,6 @@ export const config: PluginConfigDescriptor = { tilemap: true, includeElasticMapsService: true, proxyElasticMapsServiceInMaps: true, - manifestServiceUrl: true, emsUrl: true, emsFileApiUrl: true, emsTileApiUrl: true, 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 c39263f304249..6c07cb0c46ec6 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 @@ -14,6 +14,7 @@ exports[`Relationships should render dashboards normally 1`] = ` ({ +const createObject = ({ + namespaces, + hiddenType = false, +}: CreateObjectOptions = {}): SavedObjectWithMetadata => ({ id: 'foo', type: 'bar', attributes: {}, references: [], namespaces, - meta: {}, + meta: { + hiddenType, + }, }); describe('DeleteConfirmModal', () => { @@ -81,7 +87,7 @@ describe('DeleteConfirmModal', () => { isDeleting={false} onConfirm={onConfirm} onCancel={onCancel} - selectedObjects={[]} + selectedObjects={[createObject()]} /> ); wrapper.find('EuiButton').simulate('click'); @@ -90,6 +96,81 @@ describe('DeleteConfirmModal', () => { expect(onCancel).not.toHaveBeenCalled(); }); + describe('when trying to delete hidden objects', () => { + it('excludes the hidden objects from the table', () => { + const objs = [ + createObject({ hiddenType: true }), + createObject({ hiddenType: false }), + createObject({ hiddenType: true }), + ]; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('.euiTableRow')).toHaveLength(1); + }); + + it('displays a callout when at least one object cannot be deleted', () => { + const objs = [ + createObject({ hiddenType: false }), + createObject({ hiddenType: false }), + createObject({ hiddenType: true }), + ]; + const wrapper = mountWithIntl( + + ); + + const callout = findTestSubject(wrapper, 'cannotDeleteObjectsConfirmWarning'); + expect(callout).toHaveLength(1); + }); + + it('does not display a callout when all objects can be deleted', () => { + const objs = [ + createObject({ hiddenType: false }), + createObject({ hiddenType: false }), + createObject({ hiddenType: false }), + ]; + const wrapper = mountWithIntl( + + ); + + const callout = findTestSubject(wrapper, 'cannotDeleteObjectsConfirmWarning'); + expect(callout).toHaveLength(0); + }); + + it('disable the submit button when all objects cannot be deleted', () => { + const objs = [ + createObject({ hiddenType: true }), + createObject({ hiddenType: true }), + createObject({ hiddenType: true }), + ]; + const wrapper = mountWithIntl( + + ); + + expect(wrapper.find('EuiButton').getDOMNode()).toBeDisabled(); + }); + }); + describe('shared objects warning', () => { it('does not display a callout when no objects are shared', () => { const objs = [ diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx index 7f1f3adc96d8b..e3ffc6d52a3ab 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx @@ -86,7 +86,7 @@ export const DeleteConfirmModal: FC = ({ title={ } iconType="alert" @@ -95,7 +95,8 @@ export const DeleteConfirmModal: FC = ({

@@ -186,6 +187,7 @@ export const DeleteConfirmModal: FC = ({ fill color="danger" onClick={onConfirm} + disabled={deletableObjects.length === 0} data-test-subj="confirmModalConfirmButton" > createHashHistory()); - app.factory('kbnUrlStateStorage', (history) => - createKbnUrlStateStorage({ - history, - useHash: deps.core.uiSettings.get('state:storeInSessionStorage'), - ...withNotifyOnErrors(deps.core.notifications.toasts), - }) - ); - app.config(watchMultiDecorator); - - app - .controller('TimelionVisController', function ($scope) { - $scope.$on('timelionChartRendered', (event) => { - event.stopPropagation(); - $scope.renderComplete(); - }); - }) - .constant('timelionPanels', deps.timelionPanels) - .directive('chart', Chart) - .directive('timelionInterval', TimelionInterval) - .directive('timelionExpressionSuggestions', TimelionExpressionSuggestions) - .directive('timelionExpressionInput', timelionExpInput(deps)); - - initTimelionHelpDirective(app); - initInputFocusDirective(app); - initTimelionTabsDirective(app, deps); - initTimelionTDeprecationDirective(app, deps); - initTimelionTopNavDirective(app, deps); - initSavedObjectFinderDirective(app, savedSheetLoader, deps.core.uiSettings); - initSavedObjectSaveAsCheckBoxDirective(app); - initCellsDirective(app); - initFixedElementDirective(app); - initFullscreenDirective(app); - initTimelionSaveSheetDirective(app); - initTimelionLoadSheetDirective(app); - initTimelionOptionsSheetDirective(app); - - const location = 'Timelion'; - - app.directive('timelionApp', function () { - return { - restrict: 'E', - controllerAs: 'timelionApp', - controller: timelionController, - }; - }); - - function timelionController( - $http, - $route, - $routeParams, - $scope, - $timeout, - history, - kbnUrlStateStorage - ) { - // Keeping this at app scope allows us to keep the current page when the user - // switches to say, the timepicker. - $scope.page = deps.core.uiSettings.get('timelion:showTutorial', true) ? 1 : 0; - $scope.setPage = (page) => ($scope.page = page); - const timefilter = deps.plugins.data.query.timefilter.timefilter; - - timefilter.enableAutoRefreshSelector(); - timefilter.enableTimeRangeSelector(); - - deps.core.chrome.docTitle.change('Timelion - Kibana'); - - // starts syncing `_g` portion of url with query services - const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( - deps.plugins.data.query, - kbnUrlStateStorage - ); - - const savedSheet = $route.current.locals.savedSheet; - - function getStateDefaults() { - return { - sheet: savedSheet.timelion_sheet, - selected: 0, - columns: savedSheet.timelion_columns, - rows: savedSheet.timelion_rows, - interval: savedSheet.timelion_interval, - }; - } - - const { stateContainer, stopStateSync } = initTimelionAppState({ - stateDefaults: getStateDefaults(), - kbnUrlStateStorage, - }); - - $scope.state = _.cloneDeep(stateContainer.getState()); - $scope.expression = _.clone($scope.state.sheet[$scope.state.selected]); - $scope.updatedSheets = []; - - const savedVisualizations = deps.plugins.visualizations.savedVisualizationsLoader; - const timezone = visTypeTimelion.getTimezone(deps.core.uiSettings); - - const defaultExpression = '.es(*)'; - - $scope.topNavMenu = getTopNavMenu(); - - $timeout(function () { - if (deps.core.uiSettings.get('timelion:showTutorial', true)) { - $scope.toggleMenu('showHelp'); - } - }, 0); - - $scope.transient = {}; - - function getTopNavMenu() { - const newSheetAction = { - id: 'new', - label: i18n.translate('timelion.topNavMenu.newSheetButtonLabel', { - defaultMessage: 'New', - }), - description: i18n.translate('timelion.topNavMenu.newSheetButtonAriaLabel', { - defaultMessage: 'New Sheet', - }), - run: function () { - history.push('/'); - $route.reload(); - }, - testId: 'timelionNewButton', - }; - - const addSheetAction = { - id: 'add', - label: i18n.translate('timelion.topNavMenu.addChartButtonLabel', { - defaultMessage: 'Add', - }), - description: i18n.translate('timelion.topNavMenu.addChartButtonAriaLabel', { - defaultMessage: 'Add a chart', - }), - run: function () { - $scope.$evalAsync(() => $scope.newCell()); - }, - testId: 'timelionAddChartButton', - }; - - const saveSheetAction = { - id: 'save', - label: i18n.translate('timelion.topNavMenu.saveSheetButtonLabel', { - defaultMessage: 'Save', - }), - description: i18n.translate('timelion.topNavMenu.saveSheetButtonAriaLabel', { - defaultMessage: 'Save Sheet', - }), - run: () => { - $scope.$evalAsync(() => $scope.toggleMenu('showSave')); - }, - testId: 'timelionSaveButton', - }; - - const deleteSheetAction = { - id: 'delete', - label: i18n.translate('timelion.topNavMenu.deleteSheetButtonLabel', { - defaultMessage: 'Delete', - }), - description: i18n.translate('timelion.topNavMenu.deleteSheetButtonAriaLabel', { - defaultMessage: 'Delete current sheet', - }), - disableButton: function () { - return !savedSheet.id; - }, - run: function () { - const title = savedSheet.title; - function doDelete() { - savedSheet - .delete() - .then(() => { - deps.core.notifications.toasts.addSuccess( - i18n.translate('timelion.topNavMenu.delete.modal.successNotificationText', { - defaultMessage: `Deleted '{title}'`, - values: { title }, - }) - ); - history.push('/'); - }) - .catch((error) => addFatalError(deps.core.fatalErrors, error, location)); - } - - const confirmModalOptions = { - confirmButtonText: i18n.translate( - 'timelion.topNavMenu.delete.modal.confirmButtonLabel', - { - defaultMessage: 'Delete', - } - ), - title: i18n.translate('timelion.topNavMenu.delete.modalTitle', { - defaultMessage: `Delete Timelion sheet '{title}'?`, - values: { title }, - }), - }; - - $scope.$evalAsync(() => { - deps.core.overlays - .openConfirm( - i18n.translate('timelion.topNavMenu.delete.modal.warningText', { - defaultMessage: `You can't recover deleted sheets.`, - }), - confirmModalOptions - ) - .then((isConfirmed) => { - if (isConfirmed) { - doDelete(); - } - }); - }); - }, - testId: 'timelionDeleteButton', - }; - - const openSheetAction = { - id: 'open', - label: i18n.translate('timelion.topNavMenu.openSheetButtonLabel', { - defaultMessage: 'Open', - }), - description: i18n.translate('timelion.topNavMenu.openSheetButtonAriaLabel', { - defaultMessage: 'Open Sheet', - }), - run: () => { - $scope.$evalAsync(() => $scope.toggleMenu('showLoad')); - }, - testId: 'timelionOpenButton', - }; - - const optionsAction = { - id: 'options', - label: i18n.translate('timelion.topNavMenu.optionsButtonLabel', { - defaultMessage: 'Options', - }), - description: i18n.translate('timelion.topNavMenu.optionsButtonAriaLabel', { - defaultMessage: 'Options', - }), - run: () => { - $scope.$evalAsync(() => $scope.toggleMenu('showOptions')); - }, - testId: 'timelionOptionsButton', - }; - - const helpAction = { - id: 'help', - label: i18n.translate('timelion.topNavMenu.helpButtonLabel', { - defaultMessage: 'Help', - }), - description: i18n.translate('timelion.topNavMenu.helpButtonAriaLabel', { - defaultMessage: 'Help', - }), - run: () => { - $scope.$evalAsync(() => $scope.toggleMenu('showHelp')); - }, - testId: 'timelionDocsButton', - }; - - if (deps.core.application.capabilities.timelion.save) { - return [ - newSheetAction, - addSheetAction, - saveSheetAction, - deleteSheetAction, - openSheetAction, - optionsAction, - helpAction, - ]; - } - return [newSheetAction, addSheetAction, openSheetAction, optionsAction, helpAction]; - } - - let refresher; - const setRefreshData = function () { - if (refresher) $timeout.cancel(refresher); - const interval = timefilter.getRefreshInterval(); - if (interval.value > 0 && !interval.pause) { - function startRefresh() { - refresher = $timeout(function () { - if (!$scope.running) $scope.search(); - startRefresh(); - }, interval.value); - } - startRefresh(); - } - }; - - const init = function () { - $scope.running = false; - $scope.search(); - setRefreshData(); - - $scope.model = { - timeRange: timefilter.getTime(), - refreshInterval: timefilter.getRefreshInterval(), - }; - - const unsubscribeStateUpdates = stateContainer.subscribe((state) => { - const clonedState = _.cloneDeep(state); - $scope.updatedSheets.forEach((updatedSheet) => { - clonedState.sheet[updatedSheet.id] = updatedSheet.expression; - }); - $scope.state = clonedState; - $scope.opts.state = clonedState; - $scope.expression = _.clone($scope.state.sheet[$scope.state.selected]); - $scope.search(); - }); - - timefilter.getFetch$().subscribe($scope.search); - - $scope.opts = { - saveExpression: saveExpression, - saveSheet: saveSheet, - savedSheet: savedSheet, - state: _.cloneDeep(stateContainer.getState()), - search: $scope.search, - dontShowHelp: function () { - deps.core.uiSettings.set('timelion:showTutorial', false); - $scope.setPage(0); - $scope.closeMenus(); - }, - }; - - $scope.$watch('opts.state.rows', function (newRow) { - const state = stateContainer.getState(); - if (state.rows !== newRow) { - stateContainer.transitions.set('rows', newRow); - } - }); - - $scope.$watch('opts.state.columns', function (newColumn) { - const state = stateContainer.getState(); - if (state.columns !== newColumn) { - stateContainer.transitions.set('columns', newColumn); - } - }); - - $scope.menus = { - showHelp: false, - showSave: false, - showLoad: false, - showOptions: false, - }; - - $scope.toggleMenu = (menuName) => { - const curState = $scope.menus[menuName]; - $scope.closeMenus(); - $scope.menus[menuName] = !curState; - }; - - $scope.closeMenus = () => { - _.forOwn($scope.menus, function (value, key) { - $scope.menus[key] = false; - }); - }; - - $scope.$on('$destroy', () => { - stopSyncingQueryServiceStateWithUrl(); - unsubscribeStateUpdates(); - stopStateSync(); - }); - }; - - $scope.onTimeUpdate = function ({ dateRange }) { - $scope.model.timeRange = { - ...dateRange, - }; - timefilter.setTime(dateRange); - if (!$scope.running) $scope.search(); - }; - - $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { - $scope.model.refreshInterval = { - pause: isPaused, - value: refreshInterval, - }; - timefilter.setRefreshInterval({ - pause: isPaused, - value: refreshInterval ? refreshInterval : $scope.refreshInterval.value, - }); - - setRefreshData(); - }; - - $scope.$watch( - function () { - return savedSheet.lastSavedTitle; - }, - function (newTitle) { - if (savedSheet.id && newTitle) { - deps.core.chrome.docTitle.change(newTitle); - } - } - ); - - $scope.$watch('expression', function (newExpression) { - const state = stateContainer.getState(); - if (state.sheet[state.selected] !== newExpression) { - const updatedSheet = $scope.updatedSheets.find( - (updatedSheet) => updatedSheet.id === state.selected - ); - if (updatedSheet) { - updatedSheet.expression = newExpression; - } else { - $scope.updatedSheets.push({ - id: state.selected, - expression: newExpression, - }); - } - } - }); - - $scope.toggle = function (property) { - $scope[property] = !$scope[property]; - }; - - $scope.changeInterval = function (interval) { - $scope.currentInterval = interval; - }; - - $scope.updateChart = function () { - const state = stateContainer.getState(); - const newSheet = _.clone(state.sheet); - if ($scope.updatedSheets.length) { - $scope.updatedSheets.forEach((updatedSheet) => { - newSheet[updatedSheet.id] = updatedSheet.expression; - }); - $scope.updatedSheets = []; - } - stateContainer.transitions.updateState({ - interval: $scope.currentInterval ? $scope.currentInterval : state.interval, - sheet: newSheet, - }); - }; - - $scope.newSheet = function () { - history.push('/'); - }; - - $scope.removeSheet = function (removedIndex) { - const state = stateContainer.getState(); - const newSheet = state.sheet.filter((el, index) => index !== removedIndex); - $scope.updatedSheets = $scope.updatedSheets.filter((el) => el.id !== removedIndex); - stateContainer.transitions.updateState({ - sheet: newSheet, - selected: removedIndex ? removedIndex - 1 : removedIndex, - }); - }; - - $scope.newCell = function () { - const state = stateContainer.getState(); - const newSheet = [...state.sheet, defaultExpression]; - stateContainer.transitions.updateState({ sheet: newSheet, selected: newSheet.length - 1 }); - }; - - $scope.setActiveCell = function (cell) { - const state = stateContainer.getState(); - if (state.selected !== cell) { - stateContainer.transitions.updateState({ sheet: $scope.state.sheet, selected: cell }); - } - }; - - $scope.search = function () { - $scope.running = true; - const state = stateContainer.getState(); - - // parse the time range client side to make sure it behaves like other charts - const timeRangeBounds = timefilter.getBounds(); - - const httpResult = $http - .post('../api/timelion/run', { - sheet: state.sheet, - time: _.assignIn( - { - from: timeRangeBounds.min, - to: timeRangeBounds.max, - }, - { - interval: state.interval, - timezone: timezone, - } - ), - }) - .then((resp) => resp.data) - .catch((resp) => { - throw resp.data; - }); - - httpResult - .then(function (resp) { - $scope.stats = resp.stats; - $scope.sheet = resp.sheet; - _.forEach(resp.sheet, function (cell) { - if (cell.exception && cell.plot !== state.selected) { - stateContainer.transitions.set('selected', cell.plot); - } - }); - $scope.running = false; - }) - .catch(function (resp) { - $scope.sheet = []; - $scope.running = false; - - const err = new Error(resp.message); - err.stack = resp.stack; - deps.core.notifications.toasts.addError(err, { - title: i18n.translate('timelion.searchErrorTitle', { - defaultMessage: 'Timelion request error', - }), - }); - }); - }; - - $scope.safeSearch = _.debounce($scope.search, 500); - - function saveSheet() { - const state = stateContainer.getState(); - savedSheet.timelion_sheet = state.sheet; - savedSheet.timelion_interval = state.interval; - savedSheet.timelion_columns = state.columns; - savedSheet.timelion_rows = state.rows; - savedSheet.save().then(function (id) { - if (id) { - deps.core.notifications.toasts.addSuccess({ - title: i18n.translate('timelion.saveSheet.successNotificationText', { - defaultMessage: `Saved sheet '{title}'`, - values: { title: savedSheet.title }, - }), - 'data-test-subj': 'timelionSaveSuccessToast', - }); - - if (savedSheet.id !== $routeParams.id) { - history.push(`/${savedSheet.id}`); - } - } - }); - } - - async function saveExpression(title) { - const vis = await deps.plugins.visualizations.createVis('timelion', { - title, - params: { - expression: $scope.state.sheet[$scope.state.selected], - interval: $scope.state.interval, - }, - }); - const state = deps.plugins.visualizations.convertFromSerializedVis(vis.serialize()); - const visSavedObject = await savedVisualizations.get(); - Object.assign(visSavedObject, state); - const id = await visSavedObject.save(); - if (id) { - deps.core.notifications.toasts.addSuccess( - i18n.translate('timelion.saveExpression.successNotificationText', { - defaultMessage: `Saved expression '{title}'`, - values: { title: state.title }, - }) - ); - } - } - - init(); - } - - app.config(function ($routeProvider) { - $routeProvider - .when('/:id?', { - template: rootTemplate, - reloadOnSearch: false, - k7Breadcrumbs: ($injector, $route) => - $injector.invoke( - $route.current.params.id ? getSavedSheetBreadcrumbs : getCreateBreadcrumbs - ), - badge: () => { - if (deps.core.application.capabilities.timelion.save) { - return undefined; - } - - return { - text: i18n.translate('timelion.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('timelion.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save Timelion sheets', - }), - iconType: 'glasses', - }; - }, - resolve: { - savedSheet: function (savedSheets, $route) { - return savedSheets - .get($route.current.params.id) - .then((savedSheet) => { - if ($route.current.params.id) { - deps.core.chrome.recentlyAccessed.add( - savedSheet.getFullPath(), - savedSheet.title, - savedSheet.id - ); - } - return savedSheet; - }) - .catch(); - }, - }, - }) - .otherwise('/'); - }); -} diff --git a/src/plugins/timelion/public/application.ts b/src/plugins/timelion/public/application.ts deleted file mode 100644 index 1e3cf43f62655..0000000000000 --- a/src/plugins/timelion/public/application.ts +++ /dev/null @@ -1,129 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import './index.scss'; - -import { EuiIcon } from '@elastic/eui'; -import angular, { IModule } from 'angular'; -// required for `ngSanitize` angular module -import 'angular-sanitize'; -// required for ngRoute -import 'angular-route'; -import 'angular-sortable-view'; -import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; -import { - IUiSettingsClient, - CoreStart, - PluginInitializerContext, - AppMountParameters, -} from 'kibana/public'; -import { getTimeChart } from './panels/timechart/timechart'; -import { Panel } from './panels/panel'; - -import { configureAppAngularModule } from '../../kibana_legacy/public'; -import { TimelionPluginStartDependencies } from './plugin'; -import { DataPublicPluginStart } from '../../data/public'; -// @ts-ignore -import { initTimelionApp } from './app'; - -export interface RenderDeps { - pluginInitializerContext: PluginInitializerContext; - mountParams: AppMountParameters; - core: CoreStart; - plugins: TimelionPluginStartDependencies; - timelionPanels: Map; -} - -export interface TimelionVisualizationDependencies { - uiSettings: IUiSettingsClient; - timelionPanels: Map; - data: DataPublicPluginStart; - $rootScope: any; - $compile: any; -} - -let angularModuleInstance: IModule | null = null; - -export const renderApp = (deps: RenderDeps) => { - if (!angularModuleInstance) { - angularModuleInstance = createLocalAngularModule(deps); - // global routing stuff - configureAppAngularModule( - angularModuleInstance, - { core: deps.core, env: deps.pluginInitializerContext.env }, - true - ); - initTimelionApp(angularModuleInstance, deps); - } - - const $injector = mountTimelionApp(deps.mountParams.appBasePath, deps.mountParams.element, deps); - - return () => { - $injector.get('$rootScope').$destroy(); - }; -}; - -function registerPanels(dependencies: TimelionVisualizationDependencies) { - const timeChartPanel: Panel = getTimeChart(dependencies); - - dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); -} - -const mainTemplate = (basePath: string) => `
- -
`; - -const moduleName = 'app/timelion'; - -const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react', 'angular-sortable-view']; - -function mountTimelionApp(appBasePath: string, element: HTMLElement, deps: RenderDeps) { - const mountpoint = document.createElement('div'); - mountpoint.setAttribute('class', 'timelionAppContainer'); - // eslint-disable-next-line no-unsanitized/property - mountpoint.innerHTML = mainTemplate(appBasePath); - // bootstrap angular into detached element and attach it later to - // make angular-within-angular possible - const $injector = angular.bootstrap(mountpoint, [moduleName]); - - registerPanels({ - uiSettings: deps.core.uiSettings, - timelionPanels: deps.timelionPanels, - data: deps.plugins.data, - $rootScope: $injector.get('$rootScope'), - $compile: $injector.get('$compile'), - }); - element.appendChild(mountpoint); - return $injector; -} - -function createLocalAngularModule(deps: RenderDeps) { - createLocalI18nModule(); - createLocalIconModule(); - - const dashboardAngularModule = angular.module(moduleName, [ - ...thirdPartyAngularDependencies, - 'app/timelion/I18n', - 'app/timelion/icon', - ]); - return dashboardAngularModule; -} - -function createLocalIconModule() { - angular - .module('app/timelion/icon', ['react']) - .directive('icon', (reactDirective) => reactDirective(EuiIcon)); -} - -function createLocalI18nModule() { - angular - .module('app/timelion/I18n', []) - .provider('i18n', I18nProvider) - .filter('i18n', i18nFilter) - .directive('i18nId', i18nDirective); -} diff --git a/src/plugins/timelion/public/breadcrumbs.js b/src/plugins/timelion/public/breadcrumbs.js deleted file mode 100644 index aff173823946b..0000000000000 --- a/src/plugins/timelion/public/breadcrumbs.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -const ROOT_BREADCRUMB = { - text: i18n.translate('timelion.breadcrumbs.root', { - defaultMessage: 'Timelion', - }), - href: '#', -}; - -export function getCreateBreadcrumbs() { - return [ - ROOT_BREADCRUMB, - { - text: i18n.translate('timelion.breadcrumbs.create', { - defaultMessage: 'Create', - }), - }, - ]; -} - -export function getSavedSheetBreadcrumbs($route) { - const { savedSheet } = $route.current.locals; - return [ - ROOT_BREADCRUMB, - { - text: savedSheet.title, - }, - ]; -} diff --git a/src/plugins/timelion/public/components/timelion_deprecation.tsx b/src/plugins/timelion/public/components/timelion_deprecation.tsx deleted file mode 100644 index 117aabed6773c..0000000000000 --- a/src/plugins/timelion/public/components/timelion_deprecation.tsx +++ /dev/null @@ -1,42 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiSpacer, EuiCallOut, EuiLink } from '@elastic/eui'; -import React from 'react'; -import { DocLinksStart } from '../../../../core/public'; - -export const TimelionDeprecation = ({ links }: DocLinksStart) => { - const timelionDeprecationLink = links.visualize.timelionDeprecation; - return ( - <> - - - - ), - }} - /> - } - color="warning" - iconType="alert" - size="s" - /> - - - ); -}; diff --git a/src/plugins/timelion/public/components/timelion_deprecation_directive.js b/src/plugins/timelion/public/components/timelion_deprecation_directive.js deleted file mode 100644 index 2aeea00991864..0000000000000 --- a/src/plugins/timelion/public/components/timelion_deprecation_directive.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { TimelionDeprecation } from './timelion_deprecation'; - -export function initTimelionTDeprecationDirective(app, deps) { - app.directive('timelionDeprecation', function (reactDirective) { - return reactDirective( - () => { - return ( - - - - ); - }, - [], - { - restrict: 'E', - scope: { - docLinks: '=', - }, - } - ); - }); -} diff --git a/src/plugins/timelion/public/components/timelion_top_nav_directive.js b/src/plugins/timelion/public/components/timelion_top_nav_directive.js deleted file mode 100644 index 4ec3b40e47c52..0000000000000 --- a/src/plugins/timelion/public/components/timelion_top_nav_directive.js +++ /dev/null @@ -1,48 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -export function initTimelionTopNavDirective(app, deps) { - app.directive('timelionTopNav', function (reactDirective) { - return reactDirective( - (props) => { - const { TopNavMenu } = deps.plugins.navigation.ui; - return ( - - - - ); - }, - [ - ['topNavMenu', { watchDepth: 'reference' }], - ['onTimeUpdate', { watchDepth: 'reference' }], - ], - { - restrict: 'E', - scope: { - topNavMenu: '=', - onTimeUpdate: '=', - }, - } - ); - }); -} diff --git a/src/plugins/timelion/public/components/timelionhelp_tabs.js b/src/plugins/timelion/public/components/timelionhelp_tabs.js deleted file mode 100644 index 537e1bfa393b7..0000000000000 --- a/src/plugins/timelion/public/components/timelionhelp_tabs.js +++ /dev/null @@ -1,48 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import PropTypes from 'prop-types'; -import React from 'react'; - -import { EuiTabs, EuiTab } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -function handleClick(activateTab, tabName) { - activateTab(tabName); -} - -export function TimelionHelpTabs(props) { - return ( - - handleClick(props.activateTab, 'funcref')} - > - - - handleClick(props.activateTab, 'keyboardtips')} - > - - - - ); -} - -TimelionHelpTabs.propTypes = { - activeTab: PropTypes.string, - activateTab: PropTypes.func, -}; diff --git a/src/plugins/timelion/public/components/timelionhelp_tabs_directive.js b/src/plugins/timelion/public/components/timelionhelp_tabs_directive.js deleted file mode 100644 index a88e156cb5c51..0000000000000 --- a/src/plugins/timelion/public/components/timelionhelp_tabs_directive.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { TimelionHelpTabs } from './timelionhelp_tabs'; - -export function initTimelionTabsDirective(app, deps) { - app.directive('timelionHelpTabs', function (reactDirective) { - return reactDirective( - (props) => { - return ( - - - - ); - }, - [['activeTab'], ['activateTab', { watchDepth: 'reference' }]], - { - restrict: 'E', - scope: { - activeTab: '=', - activateTab: '=', - }, - } - ); - }); -} diff --git a/src/plugins/timelion/public/directives/_form.scss b/src/plugins/timelion/public/directives/_form.scss deleted file mode 100644 index 37a0cc4c0f3e5..0000000000000 --- a/src/plugins/timelion/public/directives/_form.scss +++ /dev/null @@ -1,83 +0,0 @@ -.form-control { - @include euiFontSizeS; - display: block; - width: 100%; - height: $euiFormControlCompressedHeight; - padding: $euiSizeXS $euiSizeM; - border: $euiBorderThin; - background-color: $euiFormBackgroundColor; - color: $euiTextColor; - border-radius: $euiBorderRadius; - cursor: pointer; - - &:not([type='range']) { - appearance: none; - } - - &:focus { - border-color: $euiColorPrimary; - outline: none; - box-shadow: none; - } -} - -select.form-control { // stylelint-disable-line selector-no-qualifying-type - // Makes the select arrow similar to EUI's arrowDown icon - background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"%3E%3Cpath fill="#{hexToRGB($euiTextColor)}" d="M13.0688508,5.15725038 L8.38423975,9.76827428 C8.17054415,9.97861308 7.82999214,9.97914095 7.61576025,9.76827428 L2.93114915,5.15725038 C2.7181359,4.94758321 2.37277319,4.94758321 2.15975994,5.15725038 C1.94674669,5.36691756 1.94674669,5.70685522 2.15975994,5.9165224 L6.84437104,10.5275463 C7.48517424,11.1582836 8.51644979,11.1566851 9.15562896,10.5275463 L13.8402401,5.9165224 C14.0532533,5.70685522 14.0532533,5.36691756 13.8402401,5.15725038 C13.6272268,4.94758321 13.2818641,4.94758321 13.0688508,5.15725038 Z"/%3E%3C/svg%3E'); - background-size: $euiSize; - background-repeat: no-repeat; - background-position: calc(100% - #{$euiSizeS}); - padding-right: $euiSizeXL; -} - -.fullWidth { - width: 100%; -} - -.timDropdownWarning { - margin-bottom: $euiSize; - padding: $euiSizeXS $euiSizeS; - color: $euiColorDarkestShade; - border-left: solid 2px $euiColorDanger; - font-size: $euiSizeM; -} - -.timFormCheckbox { - display: flex; - align-items: center; - line-height: 1.5; - position: relative; -} - -.timFormCheckbox__input { - appearance: none; - background-color: $euiColorLightestShade; - border: 1px solid $euiColorLightShade; - border-radius: $euiSizeXS; - width: $euiSize; - height: $euiSize; - font-size: $euiSizeM; - transition: background-color .1s linear; -} - -.timFormCheckbox__input:checked { - border-color: $euiColorPrimary; - background-color: $euiColorPrimary; -} - -.timFormCheckbox__icon { - position: absolute; - top: 0; - left: 2px; -} - -.timFormTextarea { - padding: $euiSizeXS $euiSizeM; - font-size: $euiSize; - line-height: 1.5; - color: $euiColorDarkestShade; - background-color: $euiFormBackgroundColor; - border: 1px solid $euiColorLightShade; - border-radius: $euiSizeXS; - transition: border-color .1s linear; -} diff --git a/src/plugins/timelion/public/directives/_index.scss b/src/plugins/timelion/public/directives/_index.scss deleted file mode 100644 index 2a015711062a6..0000000000000 --- a/src/plugins/timelion/public/directives/_index.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import './timelion_expression_input'; -@import './cells/index'; -@import './timelion_expression_suggestions/index'; -@import './timelion_help/index'; -@import './timelion_interval/index'; -@import './saved_object_finder'; -@import './form'; diff --git a/src/plugins/timelion/public/directives/_saved_object_finder.scss b/src/plugins/timelion/public/directives/_saved_object_finder.scss deleted file mode 100644 index 55882fe78e99e..0000000000000 --- a/src/plugins/timelion/public/directives/_saved_object_finder.scss +++ /dev/null @@ -1,132 +0,0 @@ -.list-group-menu { - &.select-mode a { - outline: none; - color: tintOrShade($euiColorPrimary, 10%, 10%); - } - - .list-group-menu-item { - list-style: none; - color: tintOrShade($euiColorPrimary, 10%, 10%); - - &.active { - font-weight: bold; - background-color: $euiColorLightShade; - } - - &:hover { - background-color: tintOrShade($euiColorPrimary, 90%, 90%); - } - - li { - list-style: none; - color: tintOrShade($euiColorPrimary, 10%, 10%); - } - } -} - -saved-object-finder { - - .timSearchBar { - display: flex; - align-items: center; - } - - .timSearchBar__section { - position: relative; - margin-right: $euiSize; - flex: 1; - } - - .timSearchBar__icon { - position: absolute; - top: $euiSizeS; - left: $euiSizeS; - font-size: $euiSize; - color: $euiColorDarkShade; - } - - .timSearchBar__input { - padding: $euiSizeS $euiSizeM; - color: $euiColorDarkestShade; - background-color: $euiColorEmptyShade; - border: 1px solid $euiColorLightShade; - border-radius: $euiSizeXS; - transition: border-color .1s linear; - padding-left: $euiSizeXL; - width: 100%; - font-size: $euiSize; - } - - .timSearchBar__pagecount { - font-size: $euiSize; - color: $euiColorDarkShade; - } - - .list-sort-button { - border-top-left-radius: 0; - border-top-right-radius: 0; - border: none; - padding: $euiSizeS $euiSize; - font-weight: $euiFontWeightRegular; - background-color: $euiColorLightestShade; - margin-top: $euiSize; - } - - .li-striped { - li { - border: none; - } - - li:nth-child(even) { - background-color: $euiColorLightestShade; - } - - li:nth-child(odd) { - background-color: $euiColorEmptyShade; - } - - .paginate-heading { - font-weight: $euiFontWeightRegular; - color: $euiColorDarkestShade; - } - - .list-group-item { - padding: $euiSizeS $euiSize; - - ul { - padding: 0; - display: flex; - flex-direction: row; - - .finder-type { - margin-right: $euiSizeS; - } - } - - a { - display: block; - color: $euiColorPrimary; - - i { - color: shade($euiColorPrimary, 10%); - margin-right: $euiSizeS; - } - } - - &:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - &.list-group-no-results p { - margin-bottom: 0; - } - } - } - - paginate { - paginate-controls { - margin: $euiSize; - } - } -} diff --git a/src/plugins/timelion/public/directives/_timelion_expression_input.scss b/src/plugins/timelion/public/directives/_timelion_expression_input.scss deleted file mode 100644 index e4294d8454c7c..0000000000000 --- a/src/plugins/timelion/public/directives/_timelion_expression_input.scss +++ /dev/null @@ -1,15 +0,0 @@ -/** - * 1. Anchor suggestions beneath input. - * 2. Allow for option of positioning suggestions absolutely. - */ - -.timExpressionInput__container { - flex: 1 1 auto; - display: flex; - flex-direction: column; /* 1 */ - position: relative; /* 2 */ -} - -.timExpressionInput { - min-height: 70px; // Matches buttons on the right with new vertical rhythm sizing -} diff --git a/src/plugins/timelion/public/directives/cells/_cells.scss b/src/plugins/timelion/public/directives/cells/_cells.scss deleted file mode 100644 index d1e5e976fc8d2..0000000000000 --- a/src/plugins/timelion/public/directives/cells/_cells.scss +++ /dev/null @@ -1,61 +0,0 @@ -.timCell { - display: inline-block; - cursor: pointer; - position: relative; - box-sizing: border-box; - border: 2px dashed transparent; - // sass-lint:disable-block no-important - padding-left: 0 !important; - padding-right: 0 !important; - margin-bottom: $euiSizeM; - - &.active { - border-color: $euiColorLightShade; - } -} - -.timCell.running { - opacity: .5; -} - -.timCell__actions { - position: absolute; - bottom: $euiSizeXS; - left: $euiSizeXS; - - > .timCell__action, - > .timCell__id { - @include euiFontSizeXS; - font-weight: $euiFontWeightBold; - color: $euiColorMediumShade; - display: inline-block; - text-align: center; - width: $euiSizeL; - height: $euiSizeL; - border-radius: $euiSizeL / 2; - border: $euiBorderThin; - background-color: $euiColorLightestShade; - z-index: $euiZLevel1; - } - - > .timCell__action { - opacity: 0; - - &:focus { - opacity: 1; - } - - &:hover, - &:focus { - color: $euiTextColor; - border-color: $euiColorMediumShade; - background-color: $euiColorLightShade; - } - } -} - -.timCell:hover { - .timCell__action { - opacity: 1; - } -} diff --git a/src/plugins/timelion/public/directives/cells/_index.scss b/src/plugins/timelion/public/directives/cells/_index.scss deleted file mode 100644 index 8611b4d8ba1d0..0000000000000 --- a/src/plugins/timelion/public/directives/cells/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './cells'; diff --git a/src/plugins/timelion/public/directives/cells/cells.html b/src/plugins/timelion/public/directives/cells/cells.html deleted file mode 100644 index f90b85abaf920..0000000000000 --- a/src/plugins/timelion/public/directives/cells/cells.html +++ /dev/null @@ -1,52 +0,0 @@ -
- -
- -
-
-
{{$index + 1}}
- - - - -
-
- -
diff --git a/src/plugins/timelion/public/directives/cells/cells.js b/src/plugins/timelion/public/directives/cells/cells.js deleted file mode 100644 index af9e315a7d944..0000000000000 --- a/src/plugins/timelion/public/directives/cells/cells.js +++ /dev/null @@ -1,41 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { move } from './collection'; -import { initTimelionGridDirective } from '../timelion_grid'; - -import html from './cells.html'; - -export function initCellsDirective(app) { - initTimelionGridDirective(app); - - app.directive('timelionCells', function () { - return { - restrict: 'E', - scope: { - sheet: '=', - state: '=', - transient: '=', - onSearch: '=', - onSelect: '=', - onRemoveSheet: '=', - }, - template: html, - link: function ($scope) { - $scope.removeCell = function (index) { - $scope.onRemoveSheet(index); - }; - - $scope.dropCell = function (item, partFrom, partTo, indexFrom, indexTo) { - move($scope.sheet, indexFrom, indexTo); - $scope.onSelect(indexTo); - }; - }, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/cells/collection.ts b/src/plugins/timelion/public/directives/cells/collection.ts deleted file mode 100644 index 188f00bef16ae..0000000000000 --- a/src/plugins/timelion/public/directives/cells/collection.ts +++ /dev/null @@ -1,65 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; - -/** - * move an obj either up or down in the collection by - * injecting it either before/after the prev/next obj that - * satisfied the qualifier - * - * or, just from one index to another... - * - * @param {array} objs - the list to move the object within - * @param {number|any} obj - the object that should be moved, or the index that the object is currently at - * @param {number|boolean} below - the index to move the object to, or whether it should be moved up or down - * @param {function} qualifier - a lodash-y callback, object = _.where, string = _.pluck - * @return {array} - the objs argument - */ -export function move( - objs: any[], - obj: object | number, - below: number | boolean, - qualifier?: ((object: object, index: number) => any) | Record | string -): object[] { - const origI = _.isNumber(obj) ? obj : objs.indexOf(obj); - if (origI === -1) { - return objs; - } - - if (_.isNumber(below)) { - // move to a specific index - objs.splice(below, 0, objs.splice(origI, 1)[0]); - return objs; - } - - below = !!below; - qualifier = qualifier && _.iteratee(qualifier); - - const above = !below; - const finder = below ? _.findIndex : _.findLastIndex; - - // find the index of the next/previous obj that meets the qualifications - const targetI = finder(objs, (otherAgg, otherI) => { - if (below && otherI <= origI) { - return; - } - if (above && otherI >= origI) { - return; - } - return Boolean(_.isFunction(qualifier) && qualifier(otherAgg, otherI)); - }); - - if (targetI === -1) { - return objs; - } - - // place the obj at it's new index - objs.splice(targetI, 0, objs.splice(origI, 1)[0]); - return objs; -} diff --git a/src/plugins/timelion/public/directives/chart/chart.js b/src/plugins/timelion/public/directives/chart/chart.js deleted file mode 100644 index 8f02fb70436e7..0000000000000 --- a/src/plugins/timelion/public/directives/chart/chart.js +++ /dev/null @@ -1,55 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -export function Chart(timelionPanels) { - return { - restrict: 'A', - scope: { - seriesList: '=chart', // The flot object, data, config and all - search: '=', // The function to execute to kick off a search - interval: '=', // Required for formatting x-axis ticks - rerenderTrigger: '=', - }, - link: function ($scope, $elem) { - let panelScope = $scope.$new(true); - - function render() { - panelScope.$destroy(); - - if (!$scope.seriesList) return; - - $scope.seriesList.render = $scope.seriesList.render || { - type: 'timechart', - }; - - const panelSchema = timelionPanels.get($scope.seriesList.render.type); - - if (!panelSchema) { - $elem.text( - i18n.translate('timelion.chart.seriesList.noSchemaWarning', { - defaultMessage: 'No such panel type: {renderType}', - values: { renderType: $scope.seriesList.render.type }, - }) - ); - return; - } - - panelScope = $scope.$new(true); - panelScope.seriesList = $scope.seriesList; - panelScope.interval = $scope.interval; - panelScope.search = $scope.search; - - panelSchema.render(panelScope, $elem); - } - - $scope.$watchGroup(['seriesList', 'rerenderTrigger'], render); - }, - }; -} diff --git a/src/plugins/timelion/public/directives/fixed_element.js b/src/plugins/timelion/public/directives/fixed_element.js deleted file mode 100644 index 4349161892367..0000000000000 --- a/src/plugins/timelion/public/directives/fixed_element.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import $ from 'jquery'; - -export function initFixedElementDirective(app) { - app.directive('fixedElementRoot', function () { - return { - restrict: 'A', - link: function ($elem) { - let fixedAt; - $(window).bind('scroll', function () { - const fixed = $('[fixed-element]', $elem); - const body = $('[fixed-element-body]', $elem); - const top = fixed.offset().top; - - if ($(window).scrollTop() > top) { - // This is a gross hack, but its better than it was. I guess - fixedAt = $(window).scrollTop(); - fixed.addClass(fixed.attr('fixed-element')); - body.addClass(fixed.attr('fixed-element-body')); - body.css({ top: fixed.height() }); - } - - if ($(window).scrollTop() < fixedAt) { - fixed.removeClass(fixed.attr('fixed-element')); - body.removeClass(fixed.attr('fixed-element-body')); - body.removeAttr('style'); - } - }); - }, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/fullscreen/fullscreen.html b/src/plugins/timelion/public/directives/fullscreen/fullscreen.html deleted file mode 100644 index 1ed6aa82ea3b9..0000000000000 --- a/src/plugins/timelion/public/directives/fullscreen/fullscreen.html +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
- -
-
diff --git a/src/plugins/timelion/public/directives/fullscreen/fullscreen.js b/src/plugins/timelion/public/directives/fullscreen/fullscreen.js deleted file mode 100644 index 8403d861a4479..0000000000000 --- a/src/plugins/timelion/public/directives/fullscreen/fullscreen.js +++ /dev/null @@ -1,25 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import html from './fullscreen.html'; - -export function initFullscreenDirective(app) { - app.directive('timelionFullscreen', function () { - return { - restrict: 'E', - scope: { - expression: '=', - series: '=', - state: '=', - transient: '=', - onSearch: '=', - }, - template: html, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/input_focus.js b/src/plugins/timelion/public/directives/input_focus.js deleted file mode 100644 index 23b8c54d623c3..0000000000000 --- a/src/plugins/timelion/public/directives/input_focus.js +++ /dev/null @@ -1,24 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function initInputFocusDirective(app) { - app.directive('inputFocus', function ($parse, $timeout) { - return { - restrict: 'A', - link: function ($scope, $elem, attrs) { - const isDisabled = attrs.disableInputFocus && $parse(attrs.disableInputFocus)($scope); - if (!isDisabled) { - $timeout(function () { - $elem.focus(); - if (attrs.inputFocus === 'select') $elem.select(); - }); - } - }, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/key_map.ts b/src/plugins/timelion/public/directives/key_map.ts deleted file mode 100644 index 3e28bf3d7a3d5..0000000000000 --- a/src/plugins/timelion/public/directives/key_map.ts +++ /dev/null @@ -1,110 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const keyMap: { [key: number]: string } = { - 8: 'backspace', - 9: 'tab', - 13: 'enter', - 16: 'shift', - 17: 'ctrl', - 18: 'alt', - 19: 'pause', - 20: 'capsLock', - 27: 'escape', - 32: 'space', - 33: 'pageUp', - 34: 'pageDown', - 35: 'end', - 36: 'home', - 37: 'left', - 38: 'up', - 39: 'right', - 40: 'down', - 45: 'insert', - 46: 'delete', - 48: '0', - 49: '1', - 50: '2', - 51: '3', - 52: '4', - 53: '5', - 54: '6', - 55: '7', - 56: '8', - 57: '9', - 65: 'a', - 66: 'b', - 67: 'c', - 68: 'd', - 69: 'e', - 70: 'f', - 71: 'g', - 72: 'h', - 73: 'i', - 74: 'j', - 75: 'k', - 76: 'l', - 77: 'm', - 78: 'n', - 79: 'o', - 80: 'p', - 81: 'q', - 82: 'r', - 83: 's', - 84: 't', - 85: 'u', - 86: 'v', - 87: 'w', - 88: 'x', - 89: 'y', - 90: 'z', - 91: 'leftWindowKey', - 92: 'rightWindowKey', - 93: 'selectKey', - 96: '0', - 97: '1', - 98: '2', - 99: '3', - 100: '4', - 101: '5', - 102: '6', - 103: '7', - 104: '8', - 105: '9', - 106: 'multiply', - 107: 'add', - 109: 'subtract', - 110: 'period', - 111: 'divide', - 112: 'f1', - 113: 'f2', - 114: 'f3', - 115: 'f4', - 116: 'f5', - 117: 'f6', - 118: 'f7', - 119: 'f8', - 120: 'f9', - 121: 'f10', - 122: 'f11', - 123: 'f12', - 144: 'numLock', - 145: 'scrollLock', - 186: 'semiColon', - 187: 'equalSign', - 188: 'comma', - 189: 'dash', - 190: 'period', - 191: 'forwardSlash', - 192: 'graveAccent', - 219: 'openBracket', - 220: 'backSlash', - 221: 'closeBracket', - 222: 'singleQuote', - 224: 'meta', -}; diff --git a/src/plugins/timelion/public/directives/saved_object_finder.html b/src/plugins/timelion/public/directives/saved_object_finder.html deleted file mode 100644 index 1ce10efe4e0a8..0000000000000 --- a/src/plugins/timelion/public/directives/saved_object_finder.html +++ /dev/null @@ -1,112 +0,0 @@ -
-
-
- - -
- -
-

-
- - - -
-
-
-
- - - - - diff --git a/src/plugins/timelion/public/directives/saved_object_finder.js b/src/plugins/timelion/public/directives/saved_object_finder.js deleted file mode 100644 index 3bd6a2d9581f4..0000000000000 --- a/src/plugins/timelion/public/directives/saved_object_finder.js +++ /dev/null @@ -1,302 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import rison from 'rison-node'; -import savedObjectFinderTemplate from './saved_object_finder.html'; -import { keyMap } from './key_map'; -import { - PaginateControlsDirectiveProvider, - PaginateDirectiveProvider, -} from '../../../kibana_legacy/public'; -import { PER_PAGE_SETTING } from '../../../saved_objects/public'; -import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../visualizations/public'; - -export function initSavedObjectFinderDirective(app, savedSheetLoader, uiSettings) { - app - .directive('paginate', PaginateDirectiveProvider) - .directive('paginateControls', PaginateControlsDirectiveProvider) - .directive('savedObjectFinder', function () { - return { - restrict: 'E', - scope: { - type: '@', - // optional make-url attr, sets the userMakeUrl in our scope - userMakeUrl: '=?makeUrl', - // optional on-choose attr, sets the userOnChoose in our scope - userOnChoose: '=?onChoose', - // optional useLocalManagement attr, removes link to management section - useLocalManagement: '=?useLocalManagement', - /** - * @type {function} - an optional function. If supplied an `Add new X` button is shown - * and this function is called when clicked. - */ - onAddNew: '=', - /** - * @{type} boolean - set this to true, if you don't want the search box above the - * table to automatically gain focus once loaded - */ - disableAutoFocus: '=', - }, - template: savedObjectFinderTemplate, - controllerAs: 'finder', - controller: function ($scope, $element, $location, history) { - const self = this; - - // the text input element - const $input = $element.find('input[ng-model=filter]'); - - // The number of items to show in the list - $scope.perPage = uiSettings.get(PER_PAGE_SETTING); - - // the list that will hold the suggestions - const $list = $element.find('ul'); - - // the current filter string, used to check that returned results are still useful - let currentFilter = $scope.filter; - - // the most recently entered search/filter - let prevSearch; - - // the list of hits, used to render display - self.hits = []; - - self.service = savedSheetLoader; - self.properties = self.service.loaderProperties; - - filterResults(); - - /** - * Boolean that keeps track of whether hits are sorted ascending (true) - * or descending (false) by title - * @type {Boolean} - */ - self.isAscending = true; - - /** - * Sorts saved object finder hits either ascending or descending - * @param {Array} hits Array of saved finder object hits - * @return {Array} Array sorted either ascending or descending - */ - self.sortHits = function (hits) { - self.isAscending = !self.isAscending; - self.hits = self.isAscending - ? _.sortBy(hits, ['title']) - : _.sortBy(hits, ['title']).reverse(); - }; - - /** - * Passed the hit objects and will determine if the - * hit should have a url in the UI, returns it if so - * @return {string|null} - the url or nothing - */ - self.makeUrl = function (hit) { - if ($scope.userMakeUrl) { - return $scope.userMakeUrl(hit); - } - - if (!$scope.userOnChoose) { - return hit.url; - } - - return '#'; - }; - - self.preventClick = function ($event) { - $event.preventDefault(); - }; - - /** - * Called when a hit object is clicked, can override the - * url behavior if necessary. - */ - self.onChoose = function (hit, $event) { - if ($scope.userOnChoose) { - $scope.userOnChoose(hit, $event); - } - - const url = self.makeUrl(hit); - if (!url || url === '#' || url.charAt(0) !== '#') return; - - $event.preventDefault(); - - history.push(url.substr(1)); - }; - - $scope.$watch('filter', function (newFilter) { - // ensure that the currentFilter changes from undefined to '' - // which triggers - currentFilter = newFilter || ''; - filterResults(); - }); - - $scope.pageFirstItem = 0; - $scope.pageLastItem = 0; - $scope.onPageChanged = (page) => { - $scope.pageFirstItem = page.firstItem; - $scope.pageLastItem = page.lastItem; - }; - - //manages the state of the keyboard selector - self.selector = { - enabled: false, - index: -1, - }; - - self.getLabel = function () { - return _.words(self.properties.nouns).map(_.capitalize).join(' '); - }; - - //key handler for the filter text box - self.filterKeyDown = function ($event) { - switch (keyMap[$event.keyCode]) { - case 'enter': - if (self.hitCount !== 1) return; - const hit = self.hits[0]; - if (!hit) return; - - self.onChoose(hit, $event); - $event.preventDefault(); - break; - } - }; - - //key handler for the list items - self.hitKeyDown = function ($event, page, paginate) { - switch (keyMap[$event.keyCode]) { - case 'tab': - if (!self.selector.enabled) break; - - self.selector.index = -1; - self.selector.enabled = false; - - //if the user types shift-tab return to the textbox - //if the user types tab, set the focus to the currently selected hit. - if ($event.shiftKey) { - $input.focus(); - } else { - $list.find('li.active a').focus(); - } - - $event.preventDefault(); - break; - case 'down': - if (!self.selector.enabled) break; - - if (self.selector.index + 1 < page.length) { - self.selector.index += 1; - } - $event.preventDefault(); - break; - case 'up': - if (!self.selector.enabled) break; - - if (self.selector.index > 0) { - self.selector.index -= 1; - } - $event.preventDefault(); - break; - case 'right': - if (!self.selector.enabled) break; - - if (page.number < page.count) { - paginate.goToPage(page.number + 1); - self.selector.index = 0; - selectTopHit(); - } - $event.preventDefault(); - break; - case 'left': - if (!self.selector.enabled) break; - - if (page.number > 1) { - paginate.goToPage(page.number - 1); - self.selector.index = 0; - selectTopHit(); - } - $event.preventDefault(); - break; - case 'escape': - if (!self.selector.enabled) break; - - $input.focus(); - $event.preventDefault(); - break; - case 'enter': - if (!self.selector.enabled) break; - - const hitIndex = (page.number - 1) * paginate.perPage + self.selector.index; - const hit = self.hits[hitIndex]; - if (!hit) break; - - self.onChoose(hit, $event); - $event.preventDefault(); - break; - case 'shift': - break; - default: - $input.focus(); - break; - } - }; - - self.hitBlur = function () { - self.selector.index = -1; - self.selector.enabled = false; - }; - - self.manageObjects = function (type) { - $location.url('/management/kibana/objects?_a=' + rison.encode({ tab: type })); - }; - - self.hitCountNoun = function () { - return (self.hitCount === 1 - ? self.properties.noun - : self.properties.nouns - ).toLowerCase(); - }; - - function selectTopHit() { - setTimeout(function () { - //triggering a focus event kicks off a new angular digest cycle. - $list.find('a:first').focus(); - }, 0); - } - - function filterResults() { - if (!self.service) return; - if (!self.properties) return; - - // track the filter that we use for this search, - // but ensure that we don't search for the same - // thing twice. This is called from multiple places - // and needs to be smart about when it actually searches - const filter = currentFilter; - if (prevSearch === filter) return; - - prevSearch = filter; - - const isLabsEnabled = uiSettings.get(VISUALIZE_ENABLE_LABS_SETTING); - self.service.find(filter).then(function (hits) { - hits.hits = hits.hits.filter( - (hit) => isLabsEnabled || _.get(hit, 'type.stage') !== 'experimental' - ); - hits.total = hits.hits.length; - - // ensure that we don't display old results - // as we can't really cancel requests - if (currentFilter === filter) { - self.hitCount = hits.total; - self.hits = _.sortBy(hits.hits, ['title']); - } - }); - } - }, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/saved_object_save_as_checkbox.html b/src/plugins/timelion/public/directives/saved_object_save_as_checkbox.html deleted file mode 100644 index a001ddc751748..0000000000000 --- a/src/plugins/timelion/public/directives/saved_object_save_as_checkbox.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- - -
- diff --git a/src/plugins/timelion/public/directives/saved_object_save_as_checkbox.js b/src/plugins/timelion/public/directives/saved_object_save_as_checkbox.js deleted file mode 100644 index 865e5ea473b85..0000000000000 --- a/src/plugins/timelion/public/directives/saved_object_save_as_checkbox.js +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import saveObjectSaveAsCheckboxTemplate from './saved_object_save_as_checkbox.html'; - -export function initSavedObjectSaveAsCheckBoxDirective(app) { - app.directive('savedObjectSaveAsCheckBox', function () { - return { - restrict: 'E', - template: saveObjectSaveAsCheckboxTemplate, - replace: true, - scope: { - savedObject: '=', - }, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/timelion_expression_input.html b/src/plugins/timelion/public/directives/timelion_expression_input.html deleted file mode 100644 index 6c115118860ba..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_expression_input.html +++ /dev/null @@ -1,41 +0,0 @@ -
- - - - -
diff --git a/src/plugins/timelion/public/directives/timelion_expression_input.js b/src/plugins/timelion/public/directives/timelion_expression_input.js deleted file mode 100644 index c29c802914987..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_expression_input.js +++ /dev/null @@ -1,266 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Timelion Expression Autocompleter - * - * This directive allows users to enter multiline timelion expressions. If the user has entered - * a valid expression and then types a ".", this directive will display a list of suggestions. - * - * Users can navigate suggestions using the arrow keys. When a user selects a suggestion, it's - * inserted into the expression and the caret position is updated to be inside of the newly- - * added function's parentheses. - * - * Beneath the hood, we use a PEG grammar to validate the Timelion expression and detect if - * the caret is in a position within the expression that allows functions to be suggested. - * - * NOTE: This directive doesn't work well with contenteditable divs. Challenges include: - * - You have to replace markup with newline characters and spaces when passing the expression - * to the grammar. - * - You have to do the opposite when loading a saved expression, so that it appears correctly - * within the contenteditable (i.e. replace newlines with
markup). - * - The Range and Selection APIs ignore newlines when providing caret position, so there is - * literally no way to insert suggestions into the correct place in a multiline expression - * that has more than a single consecutive newline. - */ - -import _ from 'lodash'; -import $ from 'jquery'; -import timelionExpressionInputTemplate from './timelion_expression_input.html'; -import { - SUGGESTION_TYPE, - Suggestions, - suggest, - insertAtLocation, -} from './timelion_expression_input_helpers'; -import { comboBoxKeyCodes } from '@elastic/eui'; - -export function timelionExpInput(deps) { - return ($http, $timeout) => { - return { - restrict: 'E', - scope: { - rows: '=', - sheet: '=', - updateChart: '&', - shouldPopoverSuggestions: '@', - }, - replace: true, - template: timelionExpressionInputTemplate, - link: function (scope, elem) { - const argValueSuggestions = deps.plugins.visTypeTimelion.getArgValueSuggestions(); - const expressionInput = elem.find('[data-expression-input]'); - const functionReference = {}; - let suggestibleFunctionLocation = {}; - - scope.suggestions = new Suggestions(); - - function init() { - $http.get('../api/timelion/functions').then(function (resp) { - Object.assign(functionReference, { - byName: _.keyBy(resp.data, 'name'), - list: resp.data, - }); - }); - } - - function setCaretOffset(caretOffset) { - // Wait for Angular to update the input with the new expression and *then* we can set - // the caret position. - $timeout(() => { - expressionInput.focus(); - expressionInput[0].selectionStart = expressionInput[0].selectionEnd = caretOffset; - scope.$apply(); - }, 0); - } - - function insertSuggestionIntoExpression(suggestionIndex) { - if (scope.suggestions.isEmpty()) { - return; - } - - const { min, max } = suggestibleFunctionLocation; - let insertedValue; - let insertPositionMinOffset = 0; - - switch (scope.suggestions.type) { - case SUGGESTION_TYPE.FUNCTIONS: { - // Position the caret inside of the function parentheses. - insertedValue = `${scope.suggestions.list[suggestionIndex].name}()`; - - // min advanced one to not replace function '.' - insertPositionMinOffset = 1; - break; - } - case SUGGESTION_TYPE.ARGUMENTS: { - // Position the caret after the '=' - insertedValue = `${scope.suggestions.list[suggestionIndex].name}=`; - break; - } - case SUGGESTION_TYPE.ARGUMENT_VALUE: { - // Position the caret after the argument value - insertedValue = `${scope.suggestions.list[suggestionIndex].name}`; - break; - } - } - - const updatedExpression = insertAtLocation( - insertedValue, - scope.sheet, - min + insertPositionMinOffset, - max - ); - scope.sheet = updatedExpression; - - const newCaretOffset = min + insertedValue.length; - setCaretOffset(newCaretOffset); - } - - function scrollToSuggestionAt(index) { - // We don't cache these because the list changes based on user input. - const suggestionsList = $('[data-suggestions-list]'); - const suggestionListItem = $('[data-suggestion-list-item]')[index]; - // Scroll to the position of the item relative to the list, not to the window. - suggestionsList.scrollTop(suggestionListItem.offsetTop - suggestionsList[0].offsetTop); - } - - function getCursorPosition() { - if (expressionInput.length) { - return expressionInput[0].selectionStart; - } - return null; - } - - async function getSuggestions() { - const suggestions = await suggest( - scope.sheet, - functionReference.list, - getCursorPosition(), - argValueSuggestions - ); - - // We're using ES6 Promises, not $q, so we have to wrap this in $apply. - scope.$apply(() => { - if (suggestions) { - scope.suggestions.setList(suggestions.list, suggestions.type); - scope.suggestions.show(); - suggestibleFunctionLocation = suggestions.location; - $timeout(() => { - const suggestionsList = $('[data-suggestions-list]'); - suggestionsList.scrollTop(0); - }, 0); - return; - } - - suggestibleFunctionLocation = undefined; - scope.suggestions.reset(); - }); - } - - function isNavigationalKey(keyCode) { - const keyCodes = _.values(comboBoxKeyCodes); - return keyCodes.includes(keyCode); - } - - scope.onFocusInput = () => { - // Wait for the caret position of the input to update and then we can get suggestions - // (which depends on the caret position). - $timeout(getSuggestions, 0); - }; - - scope.onBlurInput = () => { - scope.suggestions.hide(); - }; - - scope.onKeyDownInput = (e) => { - // If we've pressed any non-navigational keys, then the user has typed something and we - // can exit early without doing any navigation. The keyup handler will pull up suggestions. - if (!isNavigationalKey(e.keyCode)) { - return; - } - - switch (e.keyCode) { - case comboBoxKeyCodes.UP: - if (scope.suggestions.isVisible) { - // Up and down keys navigate through suggestions. - e.preventDefault(); - scope.suggestions.stepForward(); - scrollToSuggestionAt(scope.suggestions.index); - } - break; - - case comboBoxKeyCodes.DOWN: - if (scope.suggestions.isVisible) { - // Up and down keys navigate through suggestions. - e.preventDefault(); - scope.suggestions.stepBackward(); - scrollToSuggestionAt(scope.suggestions.index); - } - break; - - case comboBoxKeyCodes.TAB: - // If there are no suggestions or none is selected, the user tabs to the next input. - if (scope.suggestions.isEmpty() || scope.suggestions.index < 0) { - // Before letting the tab be handled to focus the next element - // we need to hide the suggestions, otherwise it will focus these - // instead of the time interval select. - scope.suggestions.hide(); - return; - } - - // If we have suggestions, complete the selected one. - e.preventDefault(); - insertSuggestionIntoExpression(scope.suggestions.index); - break; - - case comboBoxKeyCodes.ENTER: - if (e.metaKey || e.ctrlKey) { - // Re-render the chart when the user hits CMD+ENTER. - e.preventDefault(); - scope.updateChart(); - } else if (!scope.suggestions.isEmpty()) { - // If the suggestions are open, complete the expression with the suggestion. - e.preventDefault(); - insertSuggestionIntoExpression(scope.suggestions.index); - } - break; - - case comboBoxKeyCodes.ESCAPE: - e.preventDefault(); - scope.suggestions.hide(); - break; - } - }; - - scope.onKeyUpInput = (e) => { - // If the user isn't navigating, then we should update the suggestions based on their input. - if (!isNavigationalKey(e.keyCode)) { - getSuggestions(); - } - }; - - scope.onClickExpression = () => { - getSuggestions(); - }; - - scope.onClickSuggestion = (index) => { - insertSuggestionIntoExpression(index); - }; - - scope.getActiveSuggestionId = () => { - if (scope.suggestions.isVisible && scope.suggestions.index > -1) { - return `timelionSuggestion${scope.suggestions.index}`; - } - return ''; - }; - - init(); - }, - }; - }; -} diff --git a/src/plugins/timelion/public/directives/timelion_expression_input_helpers.js b/src/plugins/timelion/public/directives/timelion_expression_input_helpers.js deleted file mode 100644 index 0bc5897c49d6f..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_expression_input_helpers.js +++ /dev/null @@ -1,264 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import { _LEGACY_ as visTypeTimelion } from '../../../vis_type_timelion/public'; - -export const SUGGESTION_TYPE = { - ARGUMENTS: 'arguments', - ARGUMENT_VALUE: 'argument_value', - FUNCTIONS: 'functions', -}; - -export class Suggestions { - constructor() { - this.reset(); - } - - reset() { - this.index = -1; - this.list = []; - this.type = null; - this.isVisible = false; - } - - setList(list, type) { - this.list = list.sort((a, b) => { - if (a.name < b.name) { - return -1; - } - if (a.name > b.name) { - return 1; - } - // names must be equal - return 0; - }); - this.type = type; - - // Only try to position index inside of list range, when it was already focused - // beforehand (i.e. not -1) - if (this.index > -1) { - // We may get a shorter list than the one we have now, so we need to make sure our index doesn't - // fall outside of the new list's range. - this.index = Math.max(0, Math.min(this.index, this.list.length - 1)); - } - } - - getCount() { - return this.list.length; - } - - isEmpty() { - return this.list.length === 0; - } - - show() { - this.isVisible = true; - } - - hide() { - this.isVisible = false; - } - - stepForward() { - if (this.index > 0) { - this.index -= 1; - } - } - - stepBackward() { - if (this.index < this.list.length - 1) { - this.index += 1; - } - } -} - -function inLocation(cursorPosition, location) { - return cursorPosition >= location.min && cursorPosition <= location.max; -} - -function getArgumentsHelp(functionHelp, functionArgs = []) { - if (!functionHelp) { - return []; - } - - // Do not provide 'inputSeries' as argument suggestion for chainable functions - const argsHelp = functionHelp.chainable ? functionHelp.args.slice(1) : functionHelp.args.slice(0); - - // ignore arguments that are already provided in function declaration - const functionArgNames = functionArgs.map((arg) => { - return arg.name; - }); - return argsHelp.filter((arg) => { - return !functionArgNames.includes(arg.name); - }); -} - -async function extractSuggestionsFromParsedResult( - result, - cursorPosition, - functionList, - argValueSuggestions -) { - const activeFunc = result.functions.find((func) => { - return cursorPosition >= func.location.min && cursorPosition < func.location.max; - }); - - if (!activeFunc) { - return; - } - - const functionHelp = functionList.find((func) => { - return func.name === activeFunc.function; - }); - - // return function suggestion when cursor is outside of parentheses - // location range includes '.', function name, and '('. - const openParen = activeFunc.location.min + activeFunc.function.length + 2; - if (cursorPosition < openParen) { - return { list: [functionHelp], location: activeFunc.location, type: SUGGESTION_TYPE.FUNCTIONS }; - } - - // return argument value suggestions when cursor is inside argument value - const activeArg = activeFunc.arguments.find((argument) => { - return inLocation(cursorPosition, argument.location); - }); - if ( - activeArg && - activeArg.type === 'namedArg' && - inLocation(cursorPosition, activeArg.value.location) - ) { - const { function: functionName, arguments: functionArgs } = activeFunc; - - const { - name: argName, - value: { text: partialInput }, - } = activeArg; - - let valueSuggestions; - if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { - valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( - functionName, - argName, - functionArgs, - partialInput - ); - } else { - const { suggestions: staticSuggestions } = functionHelp.args.find((arg) => { - return arg.name === activeArg.name; - }); - valueSuggestions = argValueSuggestions.getStaticSuggestionsForInput( - partialInput, - staticSuggestions - ); - } - return { - list: valueSuggestions, - location: activeArg.value.location, - type: SUGGESTION_TYPE.ARGUMENT_VALUE, - }; - } - - // return argument suggestions - const argsHelp = getArgumentsHelp(functionHelp, activeFunc.arguments); - const argumentSuggestions = argsHelp.filter((arg) => { - if (_.get(activeArg, 'type') === 'namedArg') { - return _.startsWith(arg.name, activeArg.name); - } else if (activeArg) { - return _.startsWith(arg.name, activeArg.text); - } - return true; - }); - const location = activeArg ? activeArg.location : { min: cursorPosition, max: cursorPosition }; - return { list: argumentSuggestions, location: location, type: SUGGESTION_TYPE.ARGUMENTS }; -} - -export async function suggest(expression, functionList, cursorPosition, argValueSuggestions) { - try { - const result = await visTypeTimelion.parseTimelionExpressionAsync(expression); - return await extractSuggestionsFromParsedResult( - result, - cursorPosition, - functionList, - argValueSuggestions - ); - } catch (e) { - let message; - try { - // The grammar will throw an error containing a message if the expression is formatted - // correctly and is prepared to accept suggestions. If the expression is not formatted - // correctly the grammar will just throw a regular PEG SyntaxError, and this JSON.parse - // attempt will throw an error. - message = JSON.parse(e.message); - } catch (e) { - // The expression isn't correctly formatted, so JSON.parse threw an error. - return; - } - - switch (message.type) { - case 'incompleteFunction': { - let list; - if (message.function) { - // The user has start typing a function name, so we'll filter the list down to only - // possible matches. - list = functionList.filter((func) => _.startsWith(func.name, message.function)); - } else { - // The user hasn't typed anything yet, so we'll just return the entire list. - list = functionList; - } - return { list, location: message.location, type: SUGGESTION_TYPE.FUNCTIONS }; - } - case 'incompleteArgument': { - const { currentFunction: functionName, currentArgs: functionArgs } = message; - const functionHelp = functionList.find((func) => func.name === functionName); - return { - list: getArgumentsHelp(functionHelp, functionArgs), - location: message.location, - type: SUGGESTION_TYPE.ARGUMENTS, - }; - } - case 'incompleteArgumentValue': { - const { name: argName, currentFunction: functionName, currentArgs: functionArgs } = message; - let valueSuggestions = []; - if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { - valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( - functionName, - argName, - functionArgs - ); - } else { - const functionHelp = functionList.find((func) => func.name === functionName); - if (functionHelp) { - const argHelp = functionHelp.args.find((arg) => arg.name === argName); - if (argHelp && argHelp.suggestions) { - valueSuggestions = argHelp.suggestions; - } - } - } - return { - list: valueSuggestions, - location: { min: cursorPosition, max: cursorPosition }, - type: SUGGESTION_TYPE.ARGUMENT_VALUE, - }; - } - } - } -} - -export function insertAtLocation( - valueToInsert, - destination, - replacementRangeStart, - replacementRangeEnd -) { - // Insert the value at a location caret within the destination. - const prefix = destination.slice(0, replacementRangeStart); - const suffix = destination.slice(replacementRangeEnd, destination.length); - const result = `${prefix}${valueToInsert}${suffix}`; - return result; -} diff --git a/src/plugins/timelion/public/directives/timelion_expression_suggestions/_index.scss b/src/plugins/timelion/public/directives/timelion_expression_suggestions/_index.scss deleted file mode 100644 index 6fd0098aea68e..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_expression_suggestions/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './timelion_expression_suggestions'; diff --git a/src/plugins/timelion/public/directives/timelion_expression_suggestions/_timelion_expression_suggestions.scss b/src/plugins/timelion/public/directives/timelion_expression_suggestions/_timelion_expression_suggestions.scss deleted file mode 100644 index 4bf6ba24108d2..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_expression_suggestions/_timelion_expression_suggestions.scss +++ /dev/null @@ -1,36 +0,0 @@ -.timSuggestions { - @include euiBottomShadowMedium; - background-color: $euiColorLightestShade; - color: $euiTextColor; - border: $euiBorderThin; - // sass-lint:disable-block no-important - border-radius: 0 0 $euiBorderRadius $euiBorderRadius !important; - z-index: $euiZLevel9; - max-height: $euiSizeXL * 10; - overflow-y: auto; - - &.timSuggestions-isPopover { - position: absolute; - top: 100%; - } -} - -.timSuggestions__item { - border-bottom: $euiBorderThin; - padding: $euiSizeXS $euiSizeL; - - &:hover, - &.active { - background-color: $euiColorLightShade; - } -} - -.timSuggestions__details { - background-color: $euiColorLightestShade; - padding: $euiSizeM; - border-radius: $euiBorderRadius; - - > table { - margin-bottom: 0; - } -} diff --git a/src/plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.html b/src/plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.html deleted file mode 100644 index ddb9f21615aee..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.html +++ /dev/null @@ -1,109 +0,0 @@ -
-
- -
- -
-

- .{{suggestion.name}}() - - - - -

- -
-
- - - {{arg.name}}=({{arg.types.join(' | ')}}) - , - -
- -
- - - - - - - - - - - -
{{arg.name}}{{arg.types.join(', ')}}{{arg.help}}
-
-
-
- -
-

- {{suggestion.name}}= - - {{suggestion.help}} - -

-
- Accepts: - {{suggestion.types.join(', ')}} -
-
- -
-

- {{suggestion.name}} - - {{suggestion.help}} - -

-
- -
-
-
diff --git a/src/plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js b/src/plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js deleted file mode 100644 index cce0f17f4ef1a..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js +++ /dev/null @@ -1,28 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import template from './timelion_expression_suggestions.html'; - -export function TimelionExpressionSuggestions() { - return { - restrict: 'E', - scope: { - suggestions: '=', - suggestionsType: '=', - selectedIndex: '=', - onClickSuggestion: '&', - shouldPopover: '=', - }, - replace: true, - template, - link: function (scope) { - // This will prevent the expression input from losing focus. - scope.onMouseDown = (e) => e.preventDefault(); - }, - }; -} diff --git a/src/plugins/timelion/public/directives/timelion_grid.js b/src/plugins/timelion/public/directives/timelion_grid.js deleted file mode 100644 index cb55dd6b8e110..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_grid.js +++ /dev/null @@ -1,57 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import $ from 'jquery'; - -export function initTimelionGridDirective(app) { - app.directive('timelionGrid', function () { - return { - restrict: 'A', - scope: { - timelionGridRows: '=', - timelionGridColumns: '=', - }, - link: function ($scope, $elem) { - function init() { - setDimensions(); - } - - $scope.$on('$destroy', function () { - $(window).off('resize'); //remove the handler added earlier - }); - - $(window).resize(function () { - setDimensions(); - }); - - $scope.$watchMulti(['timelionGridColumns', 'timelionGridRows'], function () { - setDimensions(); - }); - - function setDimensions() { - const borderSize = 2; - const headerSize = 45 + 35 + 28 + 20 * 2; // chrome + subnav + buttons + (container padding) - const verticalPadding = 10; - - if ($scope.timelionGridColumns != null) { - $elem.width($elem.parent().width() / $scope.timelionGridColumns - borderSize * 2); - } - - if ($scope.timelionGridRows != null) { - $elem.height( - ($(window).height() - headerSize) / $scope.timelionGridRows - - (verticalPadding + borderSize * 2) - ); - } - } - - init(); - }, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/timelion_help/_index.scss b/src/plugins/timelion/public/directives/timelion_help/_index.scss deleted file mode 100644 index 4228e56180066..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_help/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './timelion_help'; diff --git a/src/plugins/timelion/public/directives/timelion_help/_timelion_help.scss b/src/plugins/timelion/public/directives/timelion_help/_timelion_help.scss deleted file mode 100644 index 1f8551116aab0..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_help/_timelion_help.scss +++ /dev/null @@ -1,33 +0,0 @@ -.timHelp { - // EUITODO: Make .euiText > code background transparent - code { - background-color: transparentize($euiTextColor, .9); - } -} - -.timHelp__buttons { - display: flex; - justify-content: space-between; -} - -.timHelp__functions { - height: $euiSizeXL * 10; - overflow-y: auto; -} - -.timHelp__links { - color: $euiColorPrimary; - - &:hover { - text-decoration: underline; - } -} - -/** - * 1. Override bootstrap .table styles. - */ -.timHelp__functionsTableRow:hover, -.timHelp__functionDetailsTable { - // sass-lint:disable-block no-important - background-color: $euiColorLightestShade !important; /* 1 */ -} diff --git a/src/plugins/timelion/public/directives/timelion_help/timelion_help.html b/src/plugins/timelion/public/directives/timelion_help/timelion_help.html deleted file mode 100644 index 4c4fdfe4faf51..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_help/timelion_help.html +++ /dev/null @@ -1,741 +0,0 @@ -
-
-
-

-

-

- - . -

-
-
- - - - - - -
-
-
-
-
-

-

-

-
-
- - - - - - - -
-
-
-
-

-

- - - -

-

-
    -
  • - -

    - - - - - - -

    -
  • -
  • - -

    -
  • -
-

-
-
- - - - - - -
-
-
-
-
-

-

-

- - - -

-

-

-

-

- - - -

-
-
- - - - - - -
-
- -
-
-

-

-

-

- - - - - - - - - - - - - - - - - -
.es(*), .es(US)
.es(*).color(#f66), .es(US).bars(1)
- .es(*).color(#f66).lines(fill=3), - .es(US).bars(1).points(radius=3, weight=1) -
(.es(*), .es(GB)).points()
-

- - . -

-
-
- - - - - - -
-
-
-
-

-

-

-

-

-

-

- - - -

-
-
- - - - - - - -
-
-
-
-

- - - - -
-
- - . -
- -
- - - - - - - - -
.{{function.name}}(){{function.help}}
-
- - - - - - - - - - - -
{{arg.name}}{{arg.types.join(', ')}}{{arg.help}}
-
- -
-
-
-
-
- -
- -
-
-
-
-
Ctrl/Cmd + Enter
-
-
- - -
-
-
- -
-
-
-
Enter/Tab
-
-
Esc
-
-
-
-
diff --git a/src/plugins/timelion/public/directives/timelion_help/timelion_help.js b/src/plugins/timelion/public/directives/timelion_help/timelion_help.js deleted file mode 100644 index ee518a8bce75c..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_help/timelion_help.js +++ /dev/null @@ -1,155 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import template from './timelion_help.html'; -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import moment from 'moment'; - -export function initTimelionHelpDirective(app) { - app.directive('timelionHelp', function ($http) { - return { - restrict: 'E', - template, - controller: function ($scope) { - $scope.functions = { - list: [], - details: null, - }; - - $scope.activeTab = 'funcref'; - $scope.activateTab = function (tabName) { - $scope.activeTab = tabName; - }; - - function init() { - $scope.es = { - invalidCount: 0, - }; - - $scope.translations = { - nextButtonLabel: i18n.translate('timelion.help.nextPageButtonLabel', { - defaultMessage: 'Next', - }), - previousButtonLabel: i18n.translate('timelion.help.previousPageButtonLabel', { - defaultMessage: 'Previous', - }), - dontShowHelpButtonLabel: i18n.translate('timelion.help.dontShowHelpButtonLabel', { - defaultMessage: `Don't show this again`, - }), - strongNextText: i18n.translate('timelion.help.welcome.content.strongNextText', { - defaultMessage: 'Next', - }), - emphasizedEverythingText: i18n.translate( - 'timelion.help.welcome.content.emphasizedEverythingText', - { - defaultMessage: 'everything', - } - ), - notValidAdvancedSettingsPath: i18n.translate( - 'timelion.help.configuration.notValid.advancedSettingsPathText', - { - defaultMessage: 'Management / Kibana / Advanced Settings', - } - ), - validAdvancedSettingsPath: i18n.translate( - 'timelion.help.configuration.valid.advancedSettingsPathText', - { - defaultMessage: 'Management/Kibana/Advanced Settings', - } - ), - esAsteriskQueryDescription: i18n.translate( - 'timelion.help.querying.esAsteriskQueryDescriptionText', - { - defaultMessage: 'hey Elasticsearch, find everything in my default index', - } - ), - esIndexQueryDescription: i18n.translate( - 'timelion.help.querying.esIndexQueryDescriptionText', - { - defaultMessage: 'use * as the q (query) for the logstash-* index', - } - ), - strongAddText: i18n.translate('timelion.help.expressions.strongAddText', { - defaultMessage: 'Add', - }), - twoExpressionsDescriptionTitle: i18n.translate( - 'timelion.help.expressions.examples.twoExpressionsDescriptionTitle', - { - defaultMessage: 'Double the fun.', - } - ), - customStylingDescriptionTitle: i18n.translate( - 'timelion.help.expressions.examples.customStylingDescriptionTitle', - { - defaultMessage: 'Custom styling.', - } - ), - namedArgumentsDescriptionTitle: i18n.translate( - 'timelion.help.expressions.examples.namedArgumentsDescriptionTitle', - { - defaultMessage: 'Named arguments.', - } - ), - groupedExpressionsDescriptionTitle: i18n.translate( - 'timelion.help.expressions.examples.groupedExpressionsDescriptionTitle', - { - defaultMessage: 'Grouped expressions.', - } - ), - }; - - getFunctions(); - checkElasticsearch(); - } - - function getFunctions() { - return $http.get('../api/timelion/functions').then(function (resp) { - $scope.functions.list = resp.data; - }); - } - $scope.recheckElasticsearch = function () { - $scope.es.valid = null; - checkElasticsearch().then(function (valid) { - if (!valid) $scope.es.invalidCount++; - }); - }; - - function checkElasticsearch() { - return $http.get('../api/timelion/validate/es').then(function (resp) { - if (resp.data.ok) { - $scope.es.valid = true; - $scope.es.stats = { - min: moment(resp.data.min).format('LLL'), - max: moment(resp.data.max).format('LLL'), - field: resp.data.field, - }; - } else { - $scope.es.valid = false; - $scope.es.invalidReason = (function () { - try { - const esResp = JSON.parse(resp.data.resp.response); - return _.get(esResp, 'error.root_cause[0].reason'); - } catch (e) { - if (_.get(resp, 'data.resp.message')) return _.get(resp, 'data.resp.message'); - if (_.get(resp, 'data.resp.output.payload.message')) - return _.get(resp, 'data.resp.output.payload.message'); - return i18n.translate('timelion.help.unknownErrorMessage', { - defaultMessage: 'Unknown error', - }); - } - })(); - } - return $scope.es.valid; - }); - } - init(); - }, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/timelion_interval/_index.scss b/src/plugins/timelion/public/directives/timelion_interval/_index.scss deleted file mode 100644 index 2a201f9b35a4d..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_interval/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './timelion_interval'; diff --git a/src/plugins/timelion/public/directives/timelion_interval/_timelion_interval.scss b/src/plugins/timelion/public/directives/timelion_interval/_timelion_interval.scss deleted file mode 100644 index 7ce09155cafd8..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_interval/_timelion_interval.scss +++ /dev/null @@ -1,30 +0,0 @@ -timelion-interval { - display: flex; -} - -.timInterval__input { - width: $euiSizeXL * 2; - padding: $euiSizeXS $euiSizeM; - color: $euiColorDarkestShade; - border: 1px solid $euiColorLightShade; - border-radius: $euiSizeXS; - transition: border-color .1s linear; - font-size: 14px; -} - -.timInterval__input--compact { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.timInterval__presets { - width: $euiSizeXL * 3; -} - -.timInterval__presets--compact { - width: $euiSizeXL * 1; - padding-left: 0; - border-left: none; - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} diff --git a/src/plugins/timelion/public/directives/timelion_interval/timelion_interval.html b/src/plugins/timelion/public/directives/timelion_interval/timelion_interval.html deleted file mode 100644 index 49009355e49f4..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_interval/timelion_interval.html +++ /dev/null @@ -1,22 +0,0 @@ - diff --git a/src/plugins/timelion/public/directives/timelion_interval/timelion_interval.js b/src/plugins/timelion/public/directives/timelion_interval/timelion_interval.js deleted file mode 100644 index 55f50fff132eb..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_interval/timelion_interval.js +++ /dev/null @@ -1,72 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import $ from 'jquery'; -import template from './timelion_interval.html'; - -export function TimelionInterval($timeout) { - return { - restrict: 'E', - scope: { - // The interval model - model: '=', - changeInterval: '=', - }, - template, - link: function ($scope, $elem) { - $scope.intervalOptions = ['auto', '1s', '1m', '1h', '1d', '1w', '1M', '1y', 'other']; - $scope.intervalLabels = { - auto: 'auto', - '1s': '1 second', - '1m': '1 minute', - '1h': '1 hour', - '1d': '1 day', - '1w': '1 week', - '1M': '1 month', - '1y': '1 year', - other: 'other', - }; - - $scope.$watch('model', function (newVal, oldVal) { - // Only run this on initialization - if (newVal !== oldVal || oldVal == null) return; - - if (_.includes($scope.intervalOptions, newVal)) { - $scope.interval = newVal; - } else { - $scope.interval = 'other'; - } - - if (newVal !== 'other') { - $scope.otherInterval = newVal; - } - }); - - $scope.$watch('interval', function (newVal, oldVal) { - if (newVal === oldVal || $scope.model === newVal) return; - - if (newVal === 'other') { - $scope.otherInterval = oldVal; - $scope.changeInterval($scope.otherInterval); - $timeout(function () { - $('input', $elem).select(); - }, 0); - } else { - $scope.otherInterval = $scope.interval; - $scope.changeInterval($scope.interval); - } - }); - - $scope.$watch('otherInterval', function (newVal, oldVal) { - if (newVal === oldVal || $scope.model === newVal) return; - $scope.changeInterval(newVal); - }); - }, - }; -} diff --git a/src/plugins/timelion/public/directives/timelion_load_sheet.js b/src/plugins/timelion/public/directives/timelion_load_sheet.js deleted file mode 100644 index 83b8b4c2e262b..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_load_sheet.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import template from '../partials/load_sheet.html'; - -export function initTimelionLoadSheetDirective(app) { - app.directive('timelionLoad', function () { - return { - replace: true, - restrict: 'E', - template, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/timelion_options_sheet.js b/src/plugins/timelion/public/directives/timelion_options_sheet.js deleted file mode 100644 index b3560111d4152..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_options_sheet.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import template from '../partials/sheet_options.html'; - -export function initTimelionOptionsSheetDirective(app) { - app.directive('timelionOptions', function () { - return { - replace: true, - restrict: 'E', - template, - }; - }); -} diff --git a/src/plugins/timelion/public/directives/timelion_save_sheet.js b/src/plugins/timelion/public/directives/timelion_save_sheet.js deleted file mode 100644 index 40289b1bb8353..0000000000000 --- a/src/plugins/timelion/public/directives/timelion_save_sheet.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import saveTemplate from '../partials/save_sheet.html'; - -export function initTimelionSaveSheetDirective(app) { - app.directive('timelionSave', function () { - return { - replace: true, - restrict: 'E', - template: saveTemplate, - }; - }); -} diff --git a/src/plugins/timelion/public/index.html b/src/plugins/timelion/public/index.html deleted file mode 100644 index 3fb518e81e882..0000000000000 --- a/src/plugins/timelion/public/index.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - -
- - - - - -
- -
-
- -
-
- -
- -
- - - -
-
- -
- - - -
-
-
-
- diff --git a/src/plugins/timelion/public/index.scss b/src/plugins/timelion/public/index.scss deleted file mode 100644 index 7a4259b2a17c8..0000000000000 --- a/src/plugins/timelion/public/index.scss +++ /dev/null @@ -1,18 +0,0 @@ -/* Timelion plugin styles */ - -// Prefix all styles with "tim" to avoid conflicts. -// Examples -// timChart -// timChart__legend -// timChart__legend--small -// timChart__legend-isLoading - -@import './app'; -@import './base'; -@import './directives/index'; - -// these styles is needed to be loaded here explicitly if the timelion visualization was not opened in browser -// styles for timelion visualization are lazy loaded only while a vis is opened -// this will duplicate styles only if both Timelion app and timelion visualization are loaded -// could be left here as it is since the Timelion app is deprecated -@import '../../vis_type_timelion/public/legacy/timelion_vis.scss'; diff --git a/src/plugins/timelion/public/index.ts b/src/plugins/timelion/public/index.ts deleted file mode 100644 index 866d8a4ad20a2..0000000000000 --- a/src/plugins/timelion/public/index.ts +++ /dev/null @@ -1,14 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PluginInitializerContext } from 'kibana/public'; -import { TimelionPlugin as Plugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} diff --git a/src/plugins/timelion/public/lib/observe_resize.js b/src/plugins/timelion/public/lib/observe_resize.js deleted file mode 100644 index d2902a59821c3..0000000000000 --- a/src/plugins/timelion/public/lib/observe_resize.js +++ /dev/null @@ -1,33 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export default function ($elem, fn, frequency) { - frequency = frequency || 500; - let currentHeight = $elem.height(); - let currentWidth = $elem.width(); - - let timeout; - - function checkLoop() { - timeout = setTimeout(function () { - if (currentHeight !== $elem.height() || currentWidth !== $elem.width()) { - currentHeight = $elem.height(); - currentWidth = $elem.width(); - - if (currentWidth > 0 && currentWidth > 0) fn(); - } - checkLoop(); - }, frequency); - } - - checkLoop(); - - return function () { - clearTimeout(timeout); - }; -} diff --git a/src/plugins/timelion/public/panels/panel.ts b/src/plugins/timelion/public/panels/panel.ts deleted file mode 100644 index 333634976eba1..0000000000000 --- a/src/plugins/timelion/public/panels/panel.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -interface PanelConfig { - help?: string; - render?: Function; -} - -export class Panel { - name: string; - help: string; - render: Function | undefined; - - constructor(name: string, config: PanelConfig) { - this.name = name; - this.help = config.help || ''; - this.render = config.render; - - if (!config.render) { - throw new Error( - i18n.translate('timelion.panels.noRenderFunctionErrorMessage', { - defaultMessage: 'Panel must have a rendering function', - }) - ); - } - } -} diff --git a/src/plugins/timelion/public/panels/timechart/schema.ts b/src/plugins/timelion/public/panels/timechart/schema.ts deleted file mode 100644 index dc26adc6ea5f5..0000000000000 --- a/src/plugins/timelion/public/panels/timechart/schema.ts +++ /dev/null @@ -1,386 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import $ from 'jquery'; -import moment from 'moment-timezone'; -// @ts-ignore -import observeResize from '../../lib/observe_resize'; -import { _LEGACY_ as visTypeTimelion } from '../../../../vis_type_timelion/public'; -import { TimelionVisualizationDependencies } from '../../application'; - -const DEBOUNCE_DELAY = 50; - -export function timechartFn(dependencies: TimelionVisualizationDependencies) { - const { - $rootScope, - $compile, - uiSettings, - data: { - query: { timefilter }, - }, - } = dependencies; - - return function () { - return { - help: 'Draw a timeseries chart', - render($scope: any, $elem: any) { - const template = '
'; - const formatters = visTypeTimelion.tickFormatters() as any; - const getxAxisFormatter = visTypeTimelion.xaxisFormatterProvider(uiSettings); - const generateTicks = visTypeTimelion.generateTicksProvider(); - - // TODO: I wonder if we should supply our own moment that sets this every time? - // could just use angular's injection to provide a moment service? - moment.tz.setDefault(uiSettings.get('dateFormat:tz')); - - const render = $scope.seriesList.render || {}; - - $scope.chart = $scope.seriesList.list; - $scope.interval = $scope.interval; - $scope.search = $scope.search || _.noop; - - let legendValueNumbers: any; - let legendCaption: any; - const debouncedSetLegendNumbers = _.debounce(setLegendNumbers, DEBOUNCE_DELAY, { - maxWait: DEBOUNCE_DELAY, - leading: true, - trailing: false, - }); - // ensure legend is the same height with or without a caption so legend items do not move around - const emptyCaption = '
'; - - const defaultOptions = { - xaxis: { - mode: 'time', - tickLength: 5, - timezone: 'browser', - }, - selection: { - mode: 'x', - color: '#ccc', - }, - crosshair: { - mode: 'x', - color: '#C66', - lineWidth: 2, - }, - grid: { - show: render.grid, - borderWidth: 0, - borderColor: null, - margin: 10, - hoverable: true, - autoHighlight: false, - }, - legend: { - backgroundColor: 'rgb(255,255,255,0)', - position: 'nw', - labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter(label: any, series: any) { - const wrapperSpan = document.createElement('span'); - const labelSpan = document.createElement('span'); - const numberSpan = document.createElement('span'); - - wrapperSpan.setAttribute('class', 'ngLegendValue'); - wrapperSpan.setAttribute('kbn-accessible-click', ''); - wrapperSpan.setAttribute('ng-click', `toggleSeries(${series._id})`); - wrapperSpan.setAttribute('ng-focus', `focusSeries(${series._id})`); - wrapperSpan.setAttribute('ng-mouseover', `highlightSeries(${series._id})`); - - labelSpan.setAttribute('ng-non-bindable', ''); - labelSpan.appendChild(document.createTextNode(label)); - numberSpan.setAttribute('class', 'ngLegendValueNumber'); - - wrapperSpan.appendChild(labelSpan); - wrapperSpan.appendChild(numberSpan); - - return wrapperSpan.outerHTML; - }, - }, - colors: [ - '#01A4A4', - '#C66', - '#D0D102', - '#616161', - '#00A1CB', - '#32742C', - '#F18D05', - '#113F8C', - '#61AE24', - '#D70060', - ], - }; - - const originalColorMap = new Map(); - $scope.chart.forEach((series: any, seriesIndex: any) => { - if (!series.color) { - const colorIndex = seriesIndex % defaultOptions.colors.length; - series.color = defaultOptions.colors[colorIndex]; - } - originalColorMap.set(series, series.color); - }); - - let highlightedSeries: any; - let focusedSeries: any; - function unhighlightSeries() { - if (highlightedSeries === null) { - return; - } - - highlightedSeries = null; - focusedSeries = null; - $scope.chart.forEach((series: any) => { - series.color = originalColorMap.get(series); // reset the colors - }); - drawPlot($scope.chart); - } - $scope.highlightSeries = _.debounce(function (id: any) { - if (highlightedSeries === id) { - return; - } - - highlightedSeries = id; - $scope.chart.forEach((series: any, seriesIndex: any) => { - if (seriesIndex !== id) { - series.color = 'rgba(128,128,128,0.1)'; // mark as grey - } else { - series.color = originalColorMap.get(series); // color it like it was - } - }); - drawPlot($scope.chart); - }, DEBOUNCE_DELAY); - $scope.focusSeries = function (id: any) { - focusedSeries = id; - $scope.highlightSeries(id); - }; - - $scope.toggleSeries = function (id: any) { - const series = $scope.chart[id]; - series._hide = !series._hide; - drawPlot($scope.chart); - }; - - const cancelResize = observeResize($elem, function () { - drawPlot($scope.chart); - }); - - $scope.$on('$destroy', function () { - cancelResize(); - $elem.off('plothover'); - $elem.off('plotselected'); - $elem.off('mouseleave'); - }); - - $elem.on('plothover', function (event: any, pos: any, item: any) { - $rootScope.$broadcast('timelionPlotHover', event, pos, item); - }); - - $elem.on('plotselected', function (event: any, ranges: any) { - timefilter.timefilter.setTime({ - from: moment(ranges.xaxis.from), - to: moment(ranges.xaxis.to), - }); - }); - - $elem.on('mouseleave', function () { - $rootScope.$broadcast('timelionPlotLeave'); - }); - - $scope.$on('timelionPlotHover', function (angularEvent: any, flotEvent: any, pos: any) { - if (!$scope.plot) return; - $scope.plot.setCrosshair(pos); - debouncedSetLegendNumbers(pos); - }); - - $scope.$on('timelionPlotLeave', function () { - if (!$scope.plot) return; - $scope.plot.clearCrosshair(); - clearLegendNumbers(); - }); - - // Shamelessly borrowed from the flotCrosshairs example - function setLegendNumbers(pos: any) { - unhighlightSeries(); - - const plot = $scope.plot; - - const axes = plot.getAxes(); - if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { - return; - } - - let i; - const dataset = plot.getData(); - if (legendCaption) { - legendCaption.text( - moment(pos.x).format( - _.get(dataset, '[0]._global.legend.timeFormat', visTypeTimelion.DEFAULT_TIME_FORMAT) - ) - ); - } - for (i = 0; i < dataset.length; ++i) { - const series = dataset[i]; - const useNearestPoint = series.lines.show && !series.lines.steps; - const precision = _.get(series, '_meta.precision', 2); - - if (series._hide) continue; - - const currentPoint = series.data.find((point: any, index: number) => { - if (index + 1 === series.data.length) { - return true; - } - if (useNearestPoint) { - return pos.x - point[0] < series.data[index + 1][0] - pos.x; - } else { - return pos.x < series.data[index + 1][0]; - } - }); - - const y = currentPoint[1]; - - if (y != null) { - let label = y.toFixed(precision); - if (series.yaxis.tickFormatter) { - label = series.yaxis.tickFormatter(label, series.yaxis); - } - legendValueNumbers.eq(i).text(`(${label})`); - } else { - legendValueNumbers.eq(i).empty(); - } - } - } - - function clearLegendNumbers() { - if (legendCaption) { - legendCaption.html(emptyCaption); - } - _.each(legendValueNumbers, function (num) { - $(num).empty(); - }); - } - - let legendScope = $scope.$new(); - function drawPlot(plotConfig: any) { - if (!$('.chart-canvas', $elem).length) $elem.html(template); - const canvasElem = $('.chart-canvas', $elem); - - // we can't use `$.plot` to draw the chart when the height or width is 0 - // so, we'll need another event to trigger drawPlot to actually draw it - if (canvasElem.height() === 0 || canvasElem.width() === 0) { - return; - } - - const title = _(plotConfig).map('_title').compact().last() as any; - $('.chart-top-title', $elem).text(title == null ? '' : title); - - const options = _.cloneDeep(defaultOptions) as any; - - // Get the X-axis tick format - const time = timefilter.timefilter.getBounds() as any; - const interval = visTypeTimelion.calculateInterval( - time.min.valueOf(), - time.max.valueOf(), - uiSettings.get('timelion:target_buckets') || 200, - $scope.interval, - uiSettings.get('timelion:min_interval') || '1ms' - ); - const format = getxAxisFormatter(interval); - - // Use moment to format ticks so we get timezone correction - options.xaxis.tickFormatter = function (val: any) { - return moment(val).format(format); - }; - - // Calculate how many ticks can fit on the axis - const tickLetterWidth = 7; - const tickPadding = 45; - options.xaxis.ticks = Math.floor( - $elem.width() / (format.length * tickLetterWidth + tickPadding) - ); - - const series = _.map(plotConfig, function (serie: any, index) { - serie = _.cloneDeep( - _.defaults(serie, { - shadowSize: 0, - lines: { - lineWidth: 3, - }, - }) - ); - serie._id = index; - - if (serie.color) { - const span = document.createElement('span'); - span.style.color = serie.color; - serie.color = span.style.color; - } - - if (serie._hide) { - serie.data = []; - serie.stack = false; - // serie.color = "#ddd"; - serie.label = '(hidden) ' + serie.label; - } - - if (serie._global) { - _.mergeWith(options, serie._global, function (objVal, srcVal) { - // This is kind of gross, it means that you can't replace a global value with a null - // best you can do is an empty string. Deal with it. - if (objVal == null) return srcVal; - if (srcVal == null) return objVal; - }); - } - - return serie; - }); - - if (options.yaxes) { - options.yaxes.forEach((yaxis: any) => { - if (yaxis && yaxis.units) { - yaxis.tickFormatter = formatters[yaxis.units.type]; - const byteModes = ['bytes', 'bytes/s']; - if (byteModes.includes(yaxis.units.type)) { - yaxis.tickGenerator = generateTicks; - } - } - }); - } - - // @ts-ignore - $scope.plot = $.plot(canvasElem, _.compact(series), options); - - if ($scope.plot) { - $scope.$emit('timelionChartRendered'); - } - - legendScope.$destroy(); - legendScope = $scope.$new(); - // Used to toggle the series, and for displaying values on hover - legendValueNumbers = canvasElem.find('.ngLegendValueNumber'); - _.each(canvasElem.find('.ngLegendValue'), function (elem) { - $compile(elem)(legendScope); - }); - - if (_.get($scope.plot.getData(), '[0]._global.legend.showTime', true)) { - legendCaption = $(''); - legendCaption.html(emptyCaption); - canvasElem.find('div.legend table').append(legendCaption); - - // legend has been re-created. Apply focus on legend element when previously set - if (focusedSeries || focusedSeries === 0) { - const $legendLabels = canvasElem.find('div.legend table .legendLabel>span'); - $legendLabels.get(focusedSeries).focus(); - } - } - } - $scope.$watch('chart', drawPlot); - }, - }; - }; -} diff --git a/src/plugins/timelion/public/panels/timechart/timechart.ts b/src/plugins/timelion/public/panels/timechart/timechart.ts deleted file mode 100644 index 6af7096bcb282..0000000000000 --- a/src/plugins/timelion/public/panels/timechart/timechart.ts +++ /dev/null @@ -1,17 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { timechartFn } from './schema'; -import { Panel } from '../panel'; -import { TimelionVisualizationDependencies } from '../../application'; - -export function getTimeChart(dependencies: TimelionVisualizationDependencies) { - // Schema is broken out so that it may be extended for use in other plugins - // Its also easier to test. - return new Panel('timechart', timechartFn(dependencies)()); -} diff --git a/src/plugins/timelion/public/partials/load_sheet.html b/src/plugins/timelion/public/partials/load_sheet.html deleted file mode 100644 index 8d8cf7193416c..0000000000000 --- a/src/plugins/timelion/public/partials/load_sheet.html +++ /dev/null @@ -1,12 +0,0 @@ -
-

- - -
diff --git a/src/plugins/timelion/public/partials/save_sheet.html b/src/plugins/timelion/public/partials/save_sheet.html deleted file mode 100644 index 7773a9d25df71..0000000000000 --- a/src/plugins/timelion/public/partials/save_sheet.html +++ /dev/null @@ -1,107 +0,0 @@ -
- - -
-
- - - - - - - -
-
- - - -
-
-
- - {{opts.state.sheet[opts.state.selected]}} -
-
- - -
-
- -
-
-
-
diff --git a/src/plugins/timelion/public/partials/sheet_options.html b/src/plugins/timelion/public/partials/sheet_options.html deleted file mode 100644 index eae5709331659..0000000000000 --- a/src/plugins/timelion/public/partials/sheet_options.html +++ /dev/null @@ -1,36 +0,0 @@ -
-

- -
-
- - -
-
- - -
-
-
diff --git a/src/plugins/timelion/public/plugin.ts b/src/plugins/timelion/public/plugin.ts deleted file mode 100644 index 63ea9a38e2795..0000000000000 --- a/src/plugins/timelion/public/plugin.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { BehaviorSubject } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; -import { - CoreSetup, - Plugin, - PluginInitializerContext, - DEFAULT_APP_CATEGORIES, - AppMountParameters, - AppUpdater, - ScopedHistory, - AppNavLinkStatus, -} from '../../../core/public'; -import { Panel } from './panels/panel'; -import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { createKbnUrlTracker } from '../../kibana_utils/public'; -import { DataPublicPluginStart, esFilters, DataPublicPluginSetup } from '../../data/public'; -import { NavigationPublicPluginStart } from '../../navigation/public'; -import { VisualizationsStart } from '../../visualizations/public'; -import { SavedObjectsStart } from '../../saved_objects/public'; -import { - VisTypeTimelionPluginStart, - VisTypeTimelionPluginSetup, -} from '../../vis_type_timelion/public'; - -export interface TimelionPluginSetupDependencies { - data: DataPublicPluginSetup; - visTypeTimelion: VisTypeTimelionPluginSetup; -} - -export interface TimelionPluginStartDependencies { - data: DataPublicPluginStart; - navigation: NavigationPublicPluginStart; - visualizations: VisualizationsStart; - visTypeTimelion: VisTypeTimelionPluginStart; - savedObjects: SavedObjectsStart; - kibanaLegacy: KibanaLegacyStart; -} - -/** @internal */ -export class TimelionPlugin - implements Plugin { - initializerContext: PluginInitializerContext; - private appStateUpdater = new BehaviorSubject(() => ({})); - private stopUrlTracking: (() => void) | undefined = undefined; - private currentHistory: ScopedHistory | undefined = undefined; - - constructor(initializerContext: PluginInitializerContext) { - this.initializerContext = initializerContext; - } - - public setup( - core: CoreSetup, - { - data, - visTypeTimelion, - }: { data: DataPublicPluginSetup; visTypeTimelion: VisTypeTimelionPluginSetup } - ) { - const timelionPanels: Map = new Map(); - - const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ - baseUrl: core.http.basePath.prepend('/app/timelion'), - defaultSubUrl: '#/', - storageKey: `lastUrl:${core.http.basePath.get()}:timelion`, - navLinkUpdater$: this.appStateUpdater, - toastNotifications: core.notifications.toasts, - stateParams: [ - { - kbnUrlKey: '_g', - stateUpdate$: data.query.state$.pipe( - filter( - ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) - ), - map(({ state }) => ({ - ...state, - filters: state.filters?.filter(esFilters.isFilterPinned), - })) - ), - }, - ], - getHistory: () => this.currentHistory!, - }); - - this.stopUrlTracking = () => { - stopUrlTracker(); - }; - - core.application.register({ - id: 'timelion', - title: 'Timelion', - order: 8000, - defaultPath: '#/', - euiIconType: 'logoKibana', - category: DEFAULT_APP_CATEGORIES.kibana, - navLinkStatus: - visTypeTimelion.isUiEnabled === false ? AppNavLinkStatus.hidden : AppNavLinkStatus.default, - mount: async (params: AppMountParameters) => { - const [coreStart, pluginsStart] = await core.getStartServices(); - await pluginsStart.kibanaLegacy.loadAngularBootstrap(); - this.currentHistory = params.history; - - appMounted(); - - const unlistenParentHistory = params.history.listen(() => { - window.dispatchEvent(new HashChangeEvent('hashchange')); - }); - - const { renderApp } = await import('./application'); - params.element.classList.add('timelionAppContainer'); - const unmount = renderApp({ - mountParams: params, - pluginInitializerContext: this.initializerContext, - timelionPanels, - core: coreStart, - plugins: pluginsStart, - }); - return () => { - unlistenParentHistory(); - unmount(); - appUnMounted(); - }; - }, - }); - } - - public start() {} - - public stop(): void { - if (this.stopUrlTracking) { - this.stopUrlTracking(); - } - } -} diff --git a/src/plugins/timelion/public/services/_saved_sheet.ts b/src/plugins/timelion/public/services/_saved_sheet.ts deleted file mode 100644 index a903c70aad69b..0000000000000 --- a/src/plugins/timelion/public/services/_saved_sheet.ts +++ /dev/null @@ -1,62 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { IUiSettingsClient } from 'kibana/public'; -import { SavedObjectsStart } from '../../../saved_objects/public'; - -// Used only by the savedSheets service, usually no reason to change this -export function createSavedSheetClass(savedObjects: SavedObjectsStart, config: IUiSettingsClient) { - class SavedSheet extends savedObjects.SavedObjectClass { - static type = 'timelion-sheet'; - - // if type:sheet has no mapping, we push this mapping into ES - static mapping = { - title: 'text', - hits: 'integer', - description: 'text', - timelion_sheet: 'text', - timelion_interval: 'keyword', - timelion_other_interval: 'keyword', - timelion_chart_height: 'integer', - timelion_columns: 'integer', - timelion_rows: 'integer', - version: 'integer', - }; - - // Order these fields to the top, the rest are alphabetical - static fieldOrder = ['title', 'description']; - // SavedSheet constructor. Usually you'd interact with an instance of this. - // ID is option, without it one will be generated on save. - constructor(id: string) { - super({ - type: SavedSheet.type, - mapping: SavedSheet.mapping, - - // if this is null/undefined then the SavedObject will be assigned the defaults - id, - - // default values that will get assigned if the doc is new - defaults: { - title: 'New TimeLion Sheet', - hits: 0, - description: '', - timelion_sheet: ['.es(*)'], - timelion_interval: 'auto', - timelion_chart_height: 275, - timelion_columns: config.get('timelion:default_columns') || 2, - timelion_rows: config.get('timelion:default_rows') || 2, - version: 1, - }, - }); - this.showInRecentlyAccessed = true; - this.getFullPath = () => `/app/timelion#/${this.id}`; - } - } - - return SavedSheet as unknown; -} diff --git a/src/plugins/timelion/public/services/saved_sheets.ts b/src/plugins/timelion/public/services/saved_sheets.ts deleted file mode 100644 index 373bb895c9806..0000000000000 --- a/src/plugins/timelion/public/services/saved_sheets.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectLoader } from '../../../saved_objects/public'; -import { createSavedSheetClass } from './_saved_sheet'; -import { RenderDeps } from '../application'; - -export function initSavedSheetService(app: angular.IModule, deps: RenderDeps) { - const savedObjectsClient = deps.core.savedObjects.client; - const SavedSheet = createSavedSheetClass(deps.plugins.savedObjects, deps.core.uiSettings); - - const savedSheetLoader = new SavedObjectLoader(SavedSheet, savedObjectsClient); - savedSheetLoader.urlFor = (id) => `#/${encodeURIComponent(id)}`; - // Customize loader properties since adding an 's' on type doesn't work for type 'timelion-sheet'. - savedSheetLoader.loaderProperties = { - name: 'timelion-sheet', - noun: 'Saved Sheets', - nouns: 'saved sheets', - }; - // This is the only thing that gets injected into controllers - app.service('savedSheets', function () { - return savedSheetLoader; - }); - - return savedSheetLoader; -} diff --git a/src/plugins/timelion/public/timelion_app_state.ts b/src/plugins/timelion/public/timelion_app_state.ts deleted file mode 100644 index 348a97583d37b..0000000000000 --- a/src/plugins/timelion/public/timelion_app_state.ts +++ /dev/null @@ -1,62 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { createStateContainer, syncState, IKbnUrlStateStorage } from '../../kibana_utils/public'; - -import { TimelionAppState, TimelionAppStateTransitions } from './types'; - -const STATE_STORAGE_KEY = '_a'; - -interface Arguments { - kbnUrlStateStorage: IKbnUrlStateStorage; - stateDefaults: TimelionAppState; -} - -export function initTimelionAppState({ stateDefaults, kbnUrlStateStorage }: Arguments) { - const urlState = kbnUrlStateStorage.get(STATE_STORAGE_KEY); - const initialState = { - ...stateDefaults, - ...urlState, - }; - - /* - make sure url ('_a') matches initial state - Initializing appState does two things - first it translates the defaults into AppState, - second it updates appState based on the url (the url trumps the defaults). This means if - we update the state format at all and want to handle BWC, we must not only migrate the - data stored with saved vis, but also any old state in the url. - */ - kbnUrlStateStorage.set(STATE_STORAGE_KEY, initialState, { replace: true }); - - const stateContainer = createStateContainer( - initialState, - { - set: (state) => (prop, value) => ({ ...state, [prop]: value }), - updateState: (state) => (newValues) => ({ ...state, ...newValues }), - } - ); - - const { start: startStateSync, stop: stopStateSync } = syncState({ - storageKey: STATE_STORAGE_KEY, - stateContainer: { - ...stateContainer, - set: (state) => { - if (state) { - // syncState utils requires to handle incoming "null" value - stateContainer.set(state); - } - }, - }, - stateStorage: kbnUrlStateStorage, - }); - - // start syncing the appState with the ('_a') url - startStateSync(); - - return { stateContainer, stopStateSync }; -} diff --git a/src/plugins/timelion/public/types.ts b/src/plugins/timelion/public/types.ts deleted file mode 100644 index bfdbd3878ec23..0000000000000 --- a/src/plugins/timelion/public/types.ts +++ /dev/null @@ -1,24 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export interface TimelionAppState { - sheet: string[]; - selected: number; - columns: number; - rows: number; - interval: string; -} - -export interface TimelionAppStateTransitions { - set: ( - state: TimelionAppState - ) => (prop: T, value: TimelionAppState[T]) => TimelionAppState; - updateState: ( - state: TimelionAppState - ) => (newValues: Partial) => TimelionAppState; -} diff --git a/src/plugins/timelion/server/config.ts b/src/plugins/timelion/server/config.ts deleted file mode 100644 index d74c4c237b2b7..0000000000000 --- a/src/plugins/timelion/server/config.ts +++ /dev/null @@ -1,21 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { schema, TypeOf } from '@kbn/config-schema'; - -export const configSchema = { - schema: schema.object({ - graphiteUrls: schema.maybe(schema.arrayOf(schema.string())), - enabled: schema.boolean({ defaultValue: true }), - ui: schema.object({ - enabled: schema.boolean({ defaultValue: true }), - }), - }), -}; - -export type TimelionConfigType = TypeOf; diff --git a/src/plugins/timelion/server/deprecations.ts b/src/plugins/timelion/server/deprecations.ts deleted file mode 100644 index 2358dd313b74f..0000000000000 --- a/src/plugins/timelion/server/deprecations.ts +++ /dev/null @@ -1,71 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - CoreStart, - SavedObjectsClient, - Logger, - GetDeprecationsContext, - DeprecationsDetails, -} from 'src/core/server'; - -export const getTimelionSheetsCount = async ( - savedObjectsClient: Pick -) => { - const { total } = await savedObjectsClient.find({ type: 'timelion-sheet', perPage: 1 }); - return total; -}; - -export const showWarningMessageIfTimelionSheetWasFound = async ( - core: CoreStart, - logger: Logger -) => { - const { savedObjects } = core; - const savedObjectsClient = savedObjects.createInternalRepository(); - const count = await getTimelionSheetsCount(savedObjectsClient); - if (count > 0) { - logger.warn( - 'Deprecated since 7.0, the Timelion app will be removed in 7.16. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' - ); - } -}; - -/** - * Deprecated since 7.0, the Timelion app will be removed in 8.0. - * To continue using your Timelion worksheets, migrate them to a dashboard. - * - * @link https://www.elastic.co/guide/en/kibana/master/timelion.html#timelion-deprecation - **/ -export async function getDeprecations({ - savedObjectsClient, -}: GetDeprecationsContext): Promise { - const deprecations: DeprecationsDetails[] = []; - const count = await getTimelionSheetsCount(savedObjectsClient); - - if (count > 0) { - deprecations.push({ - title: 'Found Timelion worksheets', - message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 7.16. To continue using your Timelion worksheets, migrate them to a dashboard.`, - documentationUrl: - 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', - level: 'warning', - correctiveActions: { - manualSteps: [ - 'Navigate to the Kibana Dashboard and click "Create dashboard".', - 'Select Timelion from the "New Visualization" window.', - 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.', - 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.', - 'In the toolbar, click Save.', - 'On the Save visualization window, enter the visualization Title, then click Save and return.', - ], - }, - }); - } - - return deprecations; -} diff --git a/src/plugins/timelion/server/index.ts b/src/plugins/timelion/server/index.ts deleted file mode 100644 index fb77df100766a..0000000000000 --- a/src/plugins/timelion/server/index.ts +++ /dev/null @@ -1,18 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server'; -import { TimelionPlugin } from './plugin'; -import { configSchema, TimelionConfigType } from './config'; - -export const config: PluginConfigDescriptor = { - schema: configSchema.schema, -}; - -export const plugin = (context: PluginInitializerContext) => - new TimelionPlugin(context); diff --git a/src/plugins/timelion/server/plugin.ts b/src/plugins/timelion/server/plugin.ts deleted file mode 100644 index edbba9b565ae4..0000000000000 --- a/src/plugins/timelion/server/plugin.ts +++ /dev/null @@ -1,74 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CoreSetup, CoreStart, Plugin, PluginInitializerContext, Logger } from 'src/core/server'; -import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; -import { TimelionConfigType } from './config'; -import { timelionSheetSavedObjectType } from './saved_objects'; -import { getDeprecations, showWarningMessageIfTimelionSheetWasFound } from './deprecations'; - -export class TimelionPlugin implements Plugin { - private logger: Logger; - - constructor(context: PluginInitializerContext) { - this.logger = context.logger.get(); - } - - public setup(core: CoreSetup) { - core.capabilities.registerProvider(() => ({ - timelion: { - save: true, - show: true, - }, - })); - core.savedObjects.registerType(timelionSheetSavedObjectType); - - core.uiSettings.register({ - 'timelion:showTutorial': { - name: i18n.translate('timelion.uiSettings.showTutorialLabel', { - defaultMessage: 'Show tutorial', - }), - value: false, - description: i18n.translate('timelion.uiSettings.showTutorialDescription', { - defaultMessage: 'Should I show the tutorial by default when entering the timelion app?', - }), - category: ['timelion'], - schema: schema.boolean(), - }, - 'timelion:default_columns': { - name: i18n.translate('timelion.uiSettings.defaultColumnsLabel', { - defaultMessage: 'Default columns', - }), - value: 2, - description: i18n.translate('timelion.uiSettings.defaultColumnsDescription', { - defaultMessage: 'Number of columns on a timelion sheet by default', - }), - category: ['timelion'], - schema: schema.number(), - }, - 'timelion:default_rows': { - name: i18n.translate('timelion.uiSettings.defaultRowsLabel', { - defaultMessage: 'Default rows', - }), - value: 2, - description: i18n.translate('timelion.uiSettings.defaultRowsDescription', { - defaultMessage: 'Number of rows on a timelion sheet by default', - }), - category: ['timelion'], - schema: schema.number(), - }, - }); - - core.deprecations.registerDeprecations({ getDeprecations }); - } - start(core: CoreStart) { - showWarningMessageIfTimelionSheetWasFound(core, this.logger); - } - stop() {} -} diff --git a/src/plugins/timelion/server/saved_objects/index.ts b/src/plugins/timelion/server/saved_objects/index.ts deleted file mode 100644 index 0dd070958d09a..0000000000000 --- a/src/plugins/timelion/server/saved_objects/index.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { timelionSheetSavedObjectType } from './timelion_sheet'; diff --git a/src/plugins/timelion/server/saved_objects/timelion_sheet.ts b/src/plugins/timelion/server/saved_objects/timelion_sheet.ts deleted file mode 100644 index 231e049280bb1..0000000000000 --- a/src/plugins/timelion/server/saved_objects/timelion_sheet.ts +++ /dev/null @@ -1,48 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectsType } from 'kibana/server'; - -export const timelionSheetSavedObjectType: SavedObjectsType = { - name: 'timelion-sheet', - hidden: false, - namespaceType: 'single', - management: { - icon: 'visTimelion', - defaultSearchField: 'title', - importableAndExportable: true, - getTitle(obj) { - return obj.attributes.title; - }, - getInAppUrl(obj) { - return { - path: `/app/timelion#/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'timelion.show', - }; - }, - }, - mappings: { - properties: { - description: { type: 'text' }, - hits: { type: 'integer' }, - kibanaSavedObjectMeta: { - properties: { - searchSourceJSON: { type: 'text' }, - }, - }, - timelion_chart_height: { type: 'integer' }, - timelion_columns: { type: 'integer' }, - timelion_interval: { type: 'keyword' }, - timelion_other_interval: { type: 'keyword' }, - timelion_rows: { type: 'integer' }, - timelion_sheet: { type: 'text' }, - title: { type: 'text' }, - version: { type: 'integer' }, - }, - }, -}; diff --git a/src/plugins/timelion/tsconfig.json b/src/plugins/timelion/tsconfig.json deleted file mode 100644 index 594901c3cc1ed..0000000000000 --- a/src/plugins/timelion/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./target/types", - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": [ - "public/**/*", - "server/**/*" - ], - "references": [ - { "path": "../../core/tsconfig.json" }, - { "path": "../data/tsconfig.json" }, - { "path": "../visualizations/tsconfig.json" }, - { "path": "../navigation/tsconfig.json" }, - { "path": "../vis_type_timelion/tsconfig.json" }, - { "path": "../saved_objects/tsconfig.json" }, - { "path": "../kibana_legacy/tsconfig.json" }, - ] -} diff --git a/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap b/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap index 26173cddb3716..bc6d28bd5c1c4 100644 --- a/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap +++ b/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap @@ -19,6 +19,7 @@ exports[`DefaultEditorAgg component should init with the default set of props 1` ; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_expression_input.tsx b/src/plugins/vis_type_timelion/public/components/timelion_expression_input.tsx index d518d9718d5e7..569ddf03c941b 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_expression_input.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_expression_input.tsx @@ -96,7 +96,7 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro
-
+
getTimelionVisualizationConfig(dependencies)); expressions.registerRenderer(getTimelionVisRenderer(dependencies)); visualizations.createBaseVisualization(getTimelionVisDefinition(dependencies)); - - return { - isUiEnabled: this.initializerContext.config.get().ui.enabled, - }; } public start(core: CoreStart, { data, charts }: TimelionVisStartDependencies) { diff --git a/src/plugins/vis_type_timelion/server/index.ts b/src/plugins/vis_type_timelion/server/index.ts index 35f4182a50a86..5c5cf8b481f94 100644 --- a/src/plugins/vis_type_timelion/server/index.ts +++ b/src/plugins/vis_type_timelion/server/index.ts @@ -10,19 +10,9 @@ import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../sr import { configSchema, ConfigSchema } from '../config'; import { TimelionPlugin } from './plugin'; -export { PluginSetupContract } from './plugin'; - export const config: PluginConfigDescriptor = { schema: configSchema, - exposeToBrowser: { - ui: true, - }, - deprecations: ({ renameFromRoot }) => [ - renameFromRoot('timelion_vis.enabled', 'vis_type_timelion.enabled'), - renameFromRoot('timelion.enabled', 'vis_type_timelion.enabled'), - renameFromRoot('timelion.graphiteUrls', 'vis_type_timelion.graphiteUrls'), - renameFromRoot('timelion.ui.enabled', 'vis_type_timelion.ui.enabled', { silent: true }), - ], }; + export const plugin = (initializerContext: PluginInitializerContext) => new TimelionPlugin(initializerContext); diff --git a/src/plugins/vis_type_timelion/server/plugin.ts b/src/plugins/vis_type_timelion/server/plugin.ts index fc23569b351e6..5bbb5dd1819c4 100644 --- a/src/plugins/vis_type_timelion/server/plugin.ts +++ b/src/plugins/vis_type_timelion/server/plugin.ts @@ -8,8 +8,6 @@ import { i18n } from '@kbn/i18n'; import { TypeOf } from '@kbn/config-schema'; -import { RecursiveReadonly } from '@kbn/utility-types'; -import { deepFreeze } from '@kbn/std'; import type { PluginStart, DataRequestHandlerContext } from '../../../../src/plugins/data/server'; import { CoreSetup, PluginInitializerContext, Plugin } from '../../../../src/core/server'; @@ -21,13 +19,6 @@ import { runRoute } from './routes/run'; import { ConfigManager } from './lib/config_manager'; import { getUiSettings } from './ui_settings'; -/** - * Describes public Timelion plugin contract returned at the `setup` stage. - */ -export interface PluginSetupContract { - uiEnabled: boolean; -} - export interface TimelionPluginStartDeps { data: PluginStart; } @@ -35,11 +26,10 @@ export interface TimelionPluginStartDeps { /** * Represents Timelion Plugin instance that will be managed by the Kibana plugin system. */ -export class TimelionPlugin - implements Plugin, void, TimelionPluginStartDeps> { +export class TimelionPlugin implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup): RecursiveReadonly { + public setup(core: CoreSetup): void { const config = this.initializerContext.config.get>(); const configManager = new ConfigManager(this.initializerContext.config); @@ -76,8 +66,6 @@ export class TimelionPlugin validateEsRoute(router); core.uiSettings.register(getUiSettings(config)); - - return deepFreeze({ uiEnabled: config.ui.enabled }); } public start() { diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap index abcbf1a4fd7d9..f50836e6ca8af 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap +++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap @@ -30,6 +30,7 @@ exports[`ValueAxesPanel component should init with the default set of props 1`] @@ -70,6 +72,7 @@ exports[`ValueAxesPanel component should init with the default set of props 1`] @@ -178,6 +182,7 @@ exports[`ValueAxesPanel component should init with the default set of props 1`] { - before(async () => { - await PageObjects.timelion.initTests(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - }); - - it('should show argument suggestions when function suggestion is selected', async () => { - await PageObjects.timelion.setExpression('.es'); - await PageObjects.timelion.clickSuggestion(); - const suggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(suggestions.length).to.eql(9); - expect(suggestions[0].includes('fit=')).to.eql(true); - }); - - it('should show argument value suggestions when argument is selected', async () => { - await PageObjects.timelion.setExpression('.legend'); - await PageObjects.timelion.clickSuggestion(); - const argumentSuggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(argumentSuggestions.length).to.eql(4); - expect(argumentSuggestions[1].includes('position=')).to.eql(true); - await PageObjects.timelion.clickSuggestion(1); - const valueSuggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(valueSuggestions.length).to.eql(5); - expect(valueSuggestions[0].includes('disable legend')).to.eql(true); - expect(valueSuggestions[1].includes('place legend in north east corner')).to.eql(true); - }); - - it('should display function suggestions filtered by function name', async () => { - await PageObjects.timelion.setExpression('.e'); - const suggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(suggestions.length).to.eql(2); - expect(suggestions[0].includes('.elasticsearch()')).to.eql(true); - expect(suggestions[1].includes('.es()')).to.eql(true); - }); - - describe('dynamic suggestions for argument values', () => { - describe('.es()', () => { - before(async () => { - await PageObjects.timelion.setExpression('.es'); - await PageObjects.timelion.clickSuggestion(); - }); - - it('should show index pattern suggestions for index argument', async () => { - await PageObjects.timelion.updateExpression('index='); - const suggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(suggestions.length).to.eql(1); - expect(suggestions[0].includes('logstash-*')).to.eql(true); - await PageObjects.timelion.clickSuggestion(); - }); - - it('should show field suggestions for timefield argument when index pattern set', async () => { - await PageObjects.timelion.updateExpression(',timefield='); - const suggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(suggestions.length).to.eql(4); - expect(suggestions[0].includes('@timestamp')).to.eql(true); - await PageObjects.timelion.clickSuggestion(); - }); - - it('should show field suggestions for split argument when index pattern set', async () => { - await PageObjects.timelion.updateExpression(',split='); - const suggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(suggestions.length).not.to.eql(0); - expect(suggestions[0].includes('@message.raw')).to.eql(true); - await PageObjects.timelion.clickSuggestion(10); - }); - - it('should show field suggestions for metric argument when index pattern set', async () => { - await PageObjects.timelion.updateExpression(',metric='); - await PageObjects.timelion.updateExpression('avg:'); - await PageObjects.timelion.clickSuggestion(0); - const suggestions = await PageObjects.timelion.getSuggestionItemsText(); - expect(suggestions.length).not.to.eql(0); - expect(suggestions[0].includes('avg:bytes')).to.eql(true); - }); - }); - }); - }); -} diff --git a/test/functional/apps/timelion/index.js b/test/functional/apps/timelion/index.js deleted file mode 100644 index b81a0e70d8a6d..0000000000000 --- a/test/functional/apps/timelion/index.js +++ /dev/null @@ -1,27 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export default function ({ getService, loadTestFile }) { - const browser = getService('browser'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - - describe('timelion app', function () { - this.tags('ciGroup1'); - - before(async function () { - log.debug('Starting timelion before method'); - await browser.setWindowSize(1280, 800); - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); - }); - - loadTestFile(require.resolve('./_expression_typeahead')); - }); -} diff --git a/test/functional/apps/visualize/_timelion.ts b/test/functional/apps/visualize/_timelion.ts index a3f2c87424244..ea8cb8b13ba49 100644 --- a/test/functional/apps/visualize/_timelion.ts +++ b/test/functional/apps/visualize/_timelion.ts @@ -10,11 +10,13 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const { timePicker, visChart, visEditor, visualize } = getPageObjects([ + const { timePicker, visChart, visEditor, visualize, timelion, common } = getPageObjects([ 'timePicker', 'visChart', 'visEditor', 'visualize', + 'timelion', + 'common', ]); const monacoEditor = getService('monacoEditor'); const kibanaServer = getService('kibanaServer'); @@ -230,6 +232,72 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); + describe('expression typeahead', () => { + it('should display function suggestions', async () => { + await monacoEditor.setCodeEditorValue(''); + await monacoEditor.typeCodeEditorValue('.e', 'timelionCodeEditor'); + // wait for monaco editor model will be updated with new value + await common.sleep(300); + let value = await monacoEditor.getCodeEditorValue(0); + expect(value).to.eql('.e'); + const suggestions = await timelion.getSuggestionItemsText(); + expect(suggestions.length).to.eql(2); + expect(suggestions[0].includes('es')).to.eql(true); + expect(suggestions[1].includes('elasticsearch')).to.eql(true); + await timelion.clickSuggestion(0); + // wait for monaco editor model will be updated with new value + await common.sleep(300); + value = await monacoEditor.getCodeEditorValue(0); + expect(value).to.eql('.es()'); + }); + + describe('dynamic suggestions for argument values', () => { + describe('.es()', () => { + it('should show index pattern suggestions for index argument', async () => { + await monacoEditor.setCodeEditorValue(''); + await monacoEditor.typeCodeEditorValue('.es(index=', 'timelionCodeEditor'); + // wait for index patterns will be loaded + await common.sleep(500); + const suggestions = await timelion.getSuggestionItemsText(); + expect(suggestions.length).not.to.eql(0); + expect(suggestions[0].includes('log')).to.eql(true); + }); + + it('should show field suggestions for timefield argument when index pattern set', async () => { + await monacoEditor.setCodeEditorValue(''); + await monacoEditor.typeCodeEditorValue( + '.es(index=logstash-*, timefield=', + 'timelionCodeEditor' + ); + const suggestions = await timelion.getSuggestionItemsText(); + expect(suggestions.length).to.eql(4); + expect(suggestions[0].includes('@timestamp')).to.eql(true); + }); + + it('should show field suggestions for split argument when index pattern set', async () => { + await monacoEditor.setCodeEditorValue(''); + await monacoEditor.typeCodeEditorValue( + '.es(index=logstash-*, timefield=@timestamp ,split=', + 'timelionCodeEditor' + ); + const suggestions = await timelion.getSuggestionItemsText(); + expect(suggestions.length).not.to.eql(0); + expect(suggestions[0].includes('@message.raw')).to.eql(true); + }); + + it('should show field suggestions for metric argument when index pattern set', async () => { + await monacoEditor.typeCodeEditorValue( + '.es(index=logstash-*, timefield=@timestamp ,metric=avg:', + 'timelionCodeEditor' + ); + const suggestions = await timelion.getSuggestionItemsText(); + expect(suggestions.length).not.to.eql(0); + expect(suggestions[0].includes('avg:bytes')).to.eql(true); + }); + }); + }); + }); + after( async () => await kibanaServer.uiSettings.update({ diff --git a/test/functional/config.js b/test/functional/config.js index 221c2e6c1f1c1..1c7f63d637d5e 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -24,7 +24,6 @@ export default async function ({ readConfigFile }) { require.resolve('./apps/home'), require.resolve('./apps/management'), require.resolve('./apps/saved_objects_management'), - require.resolve('./apps/timelion'), require.resolve('./apps/visualize'), ], pageObjects, @@ -90,9 +89,6 @@ export default async function ({ readConfigFile }) { settings: { pathname: '/app/management', }, - timelion: { - pathname: '/app/timelion', - }, console: { pathname: '/app/dev_tools', hash: '/console', 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 b6e225951c545..161d733e868a8 100644 --- a/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json +++ b/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json @@ -29,7 +29,6 @@ "search": "db2c00e39b36f40930a3b9fc71c823e1", "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -373,47 +372,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "dynamic": "false", "type": "object" diff --git a/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json b/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json index 45b2508d38033..451369d85acd8 100644 --- a/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json +++ b/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json index 5159b946e082f..2d86b863d47dc 100644 --- a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json +++ b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json @@ -29,7 +29,6 @@ "search": "db2c00e39b36f40930a3b9fc71c823e1", "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -331,47 +330,6 @@ "dynamic": "false", "type": "object" }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/discover/mappings.json b/test/functional/fixtures/es_archiver/discover/mappings.json index 93d724aa55603..33bc746c84c8c 100644 --- a/test/functional/fixtures/es_archiver/discover/mappings.json +++ b/test/functional/fixtures/es_archiver/discover/mappings.json @@ -29,7 +29,6 @@ "search": "db2c00e39b36f40930a3b9fc71c823e1", "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -346,47 +345,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/empty_kibana/mappings.json b/test/functional/fixtures/es_archiver/empty_kibana/mappings.json index 264096beb11ee..7082f43f45e99 100644 --- a/test/functional/fixtures/es_archiver/empty_kibana/mappings.json +++ b/test/functional/fixtures/es_archiver/empty_kibana/mappings.json @@ -146,48 +146,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json b/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json index 63cc283f96d32..0d41e0ce86c14 100644 --- a/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json +++ b/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json @@ -143,47 +143,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/kibana_sample_data_flights_index_pattern/mappings.json b/test/functional/fixtures/es_archiver/kibana_sample_data_flights_index_pattern/mappings.json index caa1a9d8ddc11..f980596200b25 100644 --- a/test/functional/fixtures/es_archiver/kibana_sample_data_flights_index_pattern/mappings.json +++ b/test/functional/fixtures/es_archiver/kibana_sample_data_flights_index_pattern/mappings.json @@ -19,7 +19,6 @@ "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "server": "ec97f1c5da1a19609a60874e5af1100c", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", @@ -245,47 +244,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/management/mappings.json b/test/functional/fixtures/es_archiver/management/mappings.json index 45b2508d38033..451369d85acd8 100644 --- a/test/functional/fixtures/es_archiver/management/mappings.json +++ b/test/functional/fixtures/es_archiver/management/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/mgmt/mappings.json b/test/functional/fixtures/es_archiver/mgmt/mappings.json index aefbd9d0ccc8a..f4962f9c47668 100644 --- a/test/functional/fixtures/es_archiver/mgmt/mappings.json +++ b/test/functional/fixtures/es_archiver/mgmt/mappings.json @@ -172,47 +172,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json index 45b2508d38033..451369d85acd8 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json index 05ca4d8e8307e..bb863dc24c585 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json @@ -358,47 +358,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/mappings.json index abec2eeb77492..d0101f16f85d0 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/mappings.json @@ -387,47 +387,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json index b2385a281dd23..9562b381a40f8 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json @@ -29,7 +29,6 @@ "search": "db2c00e39b36f40930a3b9fc71c823e1", "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -370,47 +369,6 @@ "dynamic": "false", "type": "object" }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json index d59f3b00d4818..780fdda5f7cbe 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json @@ -29,7 +29,6 @@ "search": "db2c00e39b36f40930a3b9fc71c823e1", "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -378,47 +377,6 @@ "dynamic": "false", "type": "object" }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json index 61763f55c1b6a..adcf4164668d6 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json @@ -389,47 +389,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json index b2385a281dd23..9562b381a40f8 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json @@ -29,7 +29,6 @@ "search": "db2c00e39b36f40930a3b9fc71c823e1", "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -370,47 +369,6 @@ "dynamic": "false", "type": "object" }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json index aba581867bb8a..0679717650af9 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json @@ -372,47 +372,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/timelion/mappings.json b/test/functional/fixtures/es_archiver/timelion/mappings.json index 45b2508d38033..451369d85acd8 100644 --- a/test/functional/fixtures/es_archiver/timelion/mappings.json +++ b/test/functional/fixtures/es_archiver/timelion/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json b/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json index 45b2508d38033..451369d85acd8 100644 --- a/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json index 45b2508d38033..451369d85acd8 100644 --- a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json index 5ac113e7e4b74..ec6a9ce7f13a1 100644 --- a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json @@ -155,48 +155,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/test/functional/page_objects/timelion_page.ts b/test/functional/page_objects/timelion_page.ts index 65584d98022be..bdfde3c8145e5 100644 --- a/test/functional/page_objects/timelion_page.ts +++ b/test/functional/page_objects/timelion_page.ts @@ -10,71 +10,21 @@ import { FtrService } from '../ftr_provider_context'; export class TimelionPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); - private readonly log = this.ctx.getService('log'); - private readonly common = this.ctx.getPageObject('common'); - private readonly esArchiver = this.ctx.getService('esArchiver'); - private readonly kibanaServer = this.ctx.getService('kibanaServer'); - - public async initTests() { - await this.kibanaServer.uiSettings.replace({ - defaultIndex: 'logstash-*', - }); - - this.log.debug('load kibana index'); - await this.esArchiver.load('test/functional/fixtures/es_archiver/timelion'); - - await this.common.navigateToApp('timelion'); - } - - public async setExpression(expression: string) { - const input = await this.testSubjects.find('timelionExpressionTextArea'); - await input.clearValue(); - await input.type(expression); - } - - public async updateExpression(updates: string) { - const input = await this.testSubjects.find('timelionExpressionTextArea'); - await input.type(updates); - await this.common.sleep(1000); - } - - public async getExpression() { - const input = await this.testSubjects.find('timelionExpressionTextArea'); - return input.getVisibleText(); - } public async getSuggestionItemsText() { - const elements = await this.testSubjects.findAll('timelionSuggestionListItem'); - return await Promise.all(elements.map(async (element) => await element.getVisibleText())); + const timelionCodeEditor = await this.testSubjects.find('timelionCodeEditor'); + const lists = await timelionCodeEditor.findAllByClassName('monaco-list-row'); + return await Promise.all(lists.map(async (element) => await element.getVisibleText())); } - public async clickSuggestion(suggestionIndex = 0, waitTime = 1000) { - const elements = await this.testSubjects.findAll('timelionSuggestionListItem'); - if (suggestionIndex > elements.length) { + public async clickSuggestion(suggestionIndex = 0) { + const timelionCodeEditor = await this.testSubjects.find('timelionCodeEditor'); + const lists = await timelionCodeEditor.findAllByCssSelector('.monaco-list-row'); + if (suggestionIndex > lists.length) { throw new Error( - `Unable to select suggestion ${suggestionIndex}, only ${elements.length} suggestions available.` + `Unable to select suggestion ${suggestionIndex}, only ${lists.length} suggestions available.` ); } - await elements[suggestionIndex].click(); - // Wait for timelion expression to be updated after clicking suggestions - await this.common.sleep(waitTime); - } - - public async saveTimelionSheet() { - await this.testSubjects.click('timelionSaveButton'); - await this.testSubjects.click('timelionSaveAsSheetButton'); - await this.testSubjects.click('timelionFinishSaveButton'); - await this.testSubjects.existOrFail('timelionSaveSuccessToast'); - await this.testSubjects.waitForDeleted('timelionSaveSuccessToast'); - } - - public async expectWriteControls() { - await this.testSubjects.existOrFail('timelionSaveButton'); - await this.testSubjects.existOrFail('timelionDeleteButton'); - } - - public async expectMissingWriteControls() { - await this.testSubjects.missingOrFail('timelionSaveButton'); - await this.testSubjects.missingOrFail('timelionDeleteButton'); + await lists[suggestionIndex].click(); } } diff --git a/test/functional/screenshots/baseline/area_chart.png b/test/functional/screenshots/baseline/area_chart.png index 28ce63c1bc41b..851f53499e94f 100644 Binary files a/test/functional/screenshots/baseline/area_chart.png and b/test/functional/screenshots/baseline/area_chart.png differ diff --git a/test/functional/screenshots/baseline/tsvb_dashboard.png b/test/functional/screenshots/baseline/tsvb_dashboard.png index a36cfffebf080..4b41887e27e24 100644 Binary files a/test/functional/screenshots/baseline/tsvb_dashboard.png and b/test/functional/screenshots/baseline/tsvb_dashboard.png differ diff --git a/test/functional/services/monaco_editor.ts b/test/functional/services/monaco_editor.ts index 90674e101fc4e..63a5a7105ddb8 100644 --- a/test/functional/services/monaco_editor.ts +++ b/test/functional/services/monaco_editor.ts @@ -11,6 +11,7 @@ import { FtrService } from '../ftr_provider_context'; export class MonacoEditorService extends FtrService { private readonly retry = this.ctx.getService('retry'); private readonly browser = this.ctx.getService('browser'); + private readonly testSubjects = this.ctx.getService('testSubjects'); public async getCodeEditorValue(nthIndex: number = 0) { let values: string[] = []; @@ -27,6 +28,12 @@ export class MonacoEditorService extends FtrService { return values[nthIndex] as string; } + public async typeCodeEditorValue(value: string, testSubjId: string) { + const editor = await this.testSubjects.find(testSubjId); + const textarea = await editor.findByCssSelector('textarea'); + textarea.type(value); + } + public async setCodeEditorValue(value: string, nthIndex = 0) { await this.retry.try(async () => { await this.browser.execute( diff --git a/test/new_visualize_flow/fixtures/es_archiver/kibana/mappings.json b/test/new_visualize_flow/fixtures/es_archiver/kibana/mappings.json index 9f5edaad0fe76..f010fcea90b3f 100644 --- a/test/new_visualize_flow/fixtures/es_archiver/kibana/mappings.json +++ b/test/new_visualize_flow/fixtures/es_archiver/kibana/mappings.json @@ -23,7 +23,6 @@ "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -366,47 +365,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/vars/workers.groovy b/vars/workers.groovy index ca1c6b57c18bb..d95c3fdbb1b44 100644 --- a/vars/workers.groovy +++ b/vars/workers.groovy @@ -20,7 +20,7 @@ def label(size) { case 'xl-highmem': return 'docker && tests-xl-highmem' case 'xxl': - return 'docker && tests-xxl && gobld/machineType:custom-64-270336' + return 'docker && tests-xxl && gobld/machineType:custom-64-327680' case 'n2-standard-16': return 'docker && linux && immutable && gobld/machineType:n2-standard-16' } diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index e5846560a6c98..76b360ce8b17f 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -134,7 +134,8 @@ export class ActionTypeRegistry { // Don't retry other kinds of errors return false; }, - createTaskRunner: (context: RunContext) => this.taskRunnerFactory.create(context), + createTaskRunner: (context: RunContext) => + this.taskRunnerFactory.create(context, actionType.maxAttempts), }, }); // No need to notify usage on basic action types diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 440de161490aa..ba7f750859d40 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -187,10 +187,12 @@ test('successfully executes as a task', async () => { const scheduleDelay = 10000; // milliseconds const scheduled = new Date(Date.now() - scheduleDelay); + const attempts = 1; await actionExecutor.execute({ ...executeParams, taskInfo: { scheduled, + attempts, }, }); diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 5dfe56cff5016..d265bca237c3b 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -44,6 +44,7 @@ export interface ActionExecutorContext { export interface TaskInfo { scheduled: Date; + attempts: number; } export interface ExecuteOptions { @@ -210,6 +211,7 @@ export class ActionExecutor { config: validatedConfig, secrets: validatedSecrets, isEphemeral, + taskInfo, }); } catch (err) { rawResult = { diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index cff92f874e0ef..85d819ba09b8a 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -136,6 +136,7 @@ test('executes the task by calling the executor with proper parameters, using gi }), taskInfo: { scheduled: new Date(), + attempts: 0, }, }); @@ -191,6 +192,7 @@ test('executes the task by calling the executor with proper parameters, using st }), taskInfo: { scheduled: new Date(), + attempts: 0, }, }); @@ -341,6 +343,7 @@ test('uses API key when provided', async () => { }), taskInfo: { scheduled: new Date(), + attempts: 0, }, }); @@ -401,6 +404,7 @@ test('uses relatedSavedObjects merged with references when provided', async () = }), taskInfo: { scheduled: new Date(), + attempts: 0, }, }); }); @@ -451,6 +455,7 @@ test('uses relatedSavedObjects as is when references are empty', async () => { }), taskInfo: { scheduled: new Date(), + attempts: 0, }, }); }); @@ -499,6 +504,7 @@ test('sanitizes invalid relatedSavedObjects when provided', async () => { relatedSavedObjects: [], taskInfo: { scheduled: new Date(), + attempts: 0, }, }); }); @@ -538,6 +544,7 @@ test(`doesn't use API key when not provided`, async () => { }), taskInfo: { scheduled: new Date(), + attempts: 0, }, }); @@ -549,9 +556,15 @@ test(`doesn't use API key when not provided`, async () => { }); test(`throws an error when license doesn't support the action type`, async () => { - const taskRunner = taskRunnerFactory.create({ - taskInstance: mockedTaskInstance, - }); + const taskRunner = taskRunnerFactory.create( + { + taskInstance: { + ...mockedTaskInstance, + attempts: 1, + }, + }, + 2 + ); mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', @@ -579,6 +592,138 @@ test(`throws an error when license doesn't support the action type`, async () => } catch (e) { expect(e instanceof ExecutorError).toEqual(true); expect(e.data).toEqual({}); - expect(e.retry).toEqual(false); + expect(e.retry).toEqual(true); } }); + +test(`treats errors as errors if the task is retryable`, async () => { + const taskRunner = taskRunnerFactory.create({ + taskInstance: { + ...mockedTaskInstance, + attempts: 0, + }, + }); + + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '3', + type: 'action_task_params', + attributes: { + actionId: '2', + params: { baz: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], + }); + mockedActionExecutor.execute.mockResolvedValueOnce({ + status: 'error', + actionId: '2', + message: 'Error message', + data: { foo: true }, + retry: false, + }); + + let err; + try { + await taskRunner.run(); + } catch (e) { + err = e; + } + expect(err).toBeDefined(); + expect(err instanceof ExecutorError).toEqual(true); + expect(err.data).toEqual({ foo: true }); + expect(err.retry).toEqual(false); + expect(taskRunnerFactoryInitializerParams.logger.error as jest.Mock).toHaveBeenCalledWith( + `Action '2' failed and will not retry: Error message` + ); +}); + +test(`treats errors as successes if the task is not retryable`, async () => { + const taskRunner = taskRunnerFactory.create({ + taskInstance: { + ...mockedTaskInstance, + attempts: 1, + }, + }); + + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '3', + type: 'action_task_params', + attributes: { + actionId: '2', + params: { baz: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], + }); + mockedActionExecutor.execute.mockResolvedValueOnce({ + status: 'error', + actionId: '2', + message: 'Error message', + data: { foo: true }, + retry: false, + }); + + let err; + try { + await taskRunner.run(); + } catch (e) { + err = e; + } + expect(err).toBeUndefined(); + expect(taskRunnerFactoryInitializerParams.logger.error as jest.Mock).toHaveBeenCalledWith( + `Action '2' failed and will not retry: Error message` + ); +}); + +test('treats errors as errors if the error is thrown instead of returned', async () => { + const taskRunner = taskRunnerFactory.create({ + taskInstance: { + ...mockedTaskInstance, + attempts: 0, + }, + }); + + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '3', + type: 'action_task_params', + attributes: { + actionId: '2', + params: { baz: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], + }); + mockedActionExecutor.execute.mockRejectedValueOnce({}); + + let err; + try { + await taskRunner.run(); + } catch (e) { + err = e; + } + expect(err).toBeDefined(); + expect(err instanceof ExecutorError).toEqual(true); + expect(err.data).toEqual({}); + expect(err.retry).toEqual(true); + expect(taskRunnerFactoryInitializerParams.logger.error as jest.Mock).toHaveBeenCalledWith( + `Action '2' failed and will retry: undefined` + ); +}); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 45ae6c1d5fae9..9a3856bbf7cee 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -22,7 +22,6 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; import { RunContext } from '../../../task_manager/server'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; -import { ActionTypeDisabledError } from './errors'; import { ActionTaskParams, ActionTypeRegistryContract, @@ -62,7 +61,7 @@ export class TaskRunnerFactory { this.taskRunnerContext = taskRunnerContext; } - public create({ taskInstance }: RunContext) { + public create({ taskInstance }: RunContext, maxAttempts: number = 1) { if (!this.isInitialized) { throw new Error('TaskRunnerFactory not initialized'); } @@ -78,6 +77,7 @@ export class TaskRunnerFactory { const taskInfo = { scheduled: taskInstance.runAt, + attempts: taskInstance.attempts, }; return { @@ -119,7 +119,14 @@ export class TaskRunnerFactory { basePathService.set(fakeRequest, path); - let executorResult: ActionTypeExecutorResult; + // Throwing an executor error means we will attempt to retry the task + // TM will treat a task as a failure if `attempts >= maxAttempts` + // so we need to handle that here to avoid TM persisting the failed task + const isRetryableBasedOnAttempts = taskInfo.attempts < (maxAttempts ?? 1); + const willRetryMessage = `and will retry`; + const willNotRetryMessage = `and will not retry`; + + let executorResult: ActionTypeExecutorResult | undefined; try { executorResult = await actionExecutor.execute({ params, @@ -131,20 +138,39 @@ export class TaskRunnerFactory { relatedSavedObjects: validatedRelatedSavedObjects(logger, relatedSavedObjects), }); } catch (e) { - if (e instanceof ActionTypeDisabledError) { - // We'll stop re-trying due to action being forbidden - throw new ExecutorError(e.message, {}, false); + logger.error( + `Action '${actionId}' failed ${ + isRetryableBasedOnAttempts ? willRetryMessage : willNotRetryMessage + }: ${e.message}` + ); + if (isRetryableBasedOnAttempts) { + // In order for retry to work, we need to indicate to task manager this task + // failed + throw new ExecutorError(e.message, {}, true); } - throw e; } - if (executorResult.status === 'error') { + if ( + executorResult && + executorResult?.status === 'error' && + executorResult?.retry !== undefined && + isRetryableBasedOnAttempts + ) { + logger.error( + `Action '${actionId}' failed ${ + !!executorResult.retry ? willRetryMessage : willNotRetryMessage + }: ${executorResult.message}` + ); // Task manager error handler only kicks in when an error thrown (at this time) // So what we have to do is throw when the return status is `error`. throw new ExecutorError( executorResult.message, executorResult.data, - executorResult.retry == null ? false : executorResult.retry + executorResult.retry as boolean | Date + ); + } else if (executorResult && executorResult?.status === 'error') { + logger.error( + `Action '${actionId}' failed ${willNotRetryMessage}: ${executorResult.message}` ); } diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 14e9e120a853a..64250ca77fba4 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -19,6 +19,7 @@ import { SavedObjectReference, } from '../../../../src/core/server'; import { ActionTypeExecutorResult } from '../common'; +import { TaskInfo } from './lib/action_executor'; export { ActionTypeExecutorResult } from '../common'; export { GetFieldsByIssueTypeResponse as JiraGetFieldsResponse } from './builtin_action_types/jira/types'; export { GetCommonFieldsResponse as ServiceNowGetFieldsResponse } from './builtin_action_types/servicenow/types'; @@ -59,6 +60,7 @@ export interface ActionTypeExecutorOptions { secrets: Secrets; params: Params; isEphemeral?: boolean; + taskInfo?: TaskInfo; } export interface ActionResult { diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index c3e21e02cdb7d..51f27916e015a 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -672,6 +672,14 @@ export class RulesClient { } public async delete({ id }: { id: string }) { + return await retryIfConflicts( + this.logger, + `rulesClient.delete('${id}')`, + async () => await this.deleteWithOCC({ id }) + ); + } + + private async deleteWithOCC({ id }: { id: string }) { let taskIdToRemove: string | undefined | null; let apiKeyToInvalidate: string | null = null; let attributes: RawAlert; diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md index 93f32111048c1..4d0edc27fe644 100644 --- a/x-pack/plugins/apm/dev_docs/testing.md +++ b/x-pack/plugins/apm/dev_docs/testing.md @@ -42,7 +42,7 @@ The API tests are located in `x-pack/test/apm_api_integration/`. node scripts/test/e2e [--trial] [--help] ``` -The E2E tests are located [here](../../ftr_e2e) +The E2E tests are located [here](../ftr_e2e) --- diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx index cbc72f918877c..144da47828bf7 100644 --- a/x-pack/plugins/apm/public/application/application.test.tsx +++ b/x-pack/plugins/apm/public/application/application.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { act } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { Observable } from 'rxjs'; -import { CoreStart } from 'src/core/public'; +import { CoreStart, DocLinksStart, HttpStart } from 'src/core/public'; import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { renderApp } from './'; @@ -85,6 +85,20 @@ describe('renderApp', () => { getEditAlertFlyout: jest.fn(), }, usageCollection: { reportUiCounter: () => {} }, + http: { + basePath: { + prepend: (path: string) => `/basepath${path}`, + get: () => `/basepath`, + }, + } as HttpStart, + docLinks: ({ + DOC_LINK_VERSION: '0', + ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', + links: { + apm: {}, + observability: { guide: '' }, + }, + } as unknown) as DocLinksStart, } as unknown) as ApmPluginStartDeps; jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined); diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx index a06520f1c5bfc..dd94cf4b175a6 100644 --- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx @@ -8,14 +8,20 @@ import { i18n } from '@kbn/i18n'; import { defaults, omit } from 'lodash'; import React from 'react'; -import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { asInteger } from '../../../../common/utils/formatters'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; import { ChartPreview } from '../chart_preview'; import { EnvironmentField, IsAboveField, ServiceField } from '../fields'; -import { AlertMetadata, getAbsoluteTimeRange } from '../helper'; +import { + AlertMetadata, + getIntervalAndTimeRange, + isNewApmRuleFromStackManagement, + TimeUnit, +} from '../helper'; +import { NewAlertEmptyPrompt } from '../new_alert_empty_prompt'; import { ServiceAlertTrigger } from '../service_alert_trigger'; export interface AlertParams { @@ -54,14 +60,20 @@ export function ErrorCountAlertTrigger(props: Props) { const { data } = useFetcher( (callApmApi) => { - if (params.windowSize && params.windowUnit) { + const { interval, start, end } = getIntervalAndTimeRange({ + windowSize: params.windowSize, + windowUnit: params.windowUnit as TimeUnit, + }); + if (interval && start && end) { return callApmApi({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', params: { query: { - ...getAbsoluteTimeRange(params.windowSize, params.windowUnit), environment: params.environment, serviceName: params.serviceName, + interval, + start, + end, }, }, }); @@ -75,6 +87,10 @@ export function ErrorCountAlertTrigger(props: Props) { ] ); + if (isNewApmRuleFromStackManagement(alertParams, metadata)) { + return ; + } + const fields = [ , ) => { + event.preventDefault(); + if (apmUrl && navigateToUrl) { + navigateToUrl(apmUrl); + } + }; + + return ( + + {i18n.translate('xpack.apm.NewAlertEmptyPrompt.goToApmLinkText', { + defaultMessage: 'Go to APM', + })} + , + ]} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx index 2a73cba0a63d5..dbbb7186de65c 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx @@ -9,10 +9,10 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { defaults, map, omit } from 'lodash'; import React from 'react'; -import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { CoreStart } from '../../../../../../../src/core/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { getDurationFormatter } from '../../../../common/utils/formatters'; import { useServiceTransactionTypesFetcher } from '../../../context/apm_service/use_service_transaction_types_fetcher'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; @@ -29,7 +29,13 @@ import { ServiceField, TransactionTypeField, } from '../fields'; -import { AlertMetadata, getAbsoluteTimeRange } from '../helper'; +import { + AlertMetadata, + getIntervalAndTimeRange, + isNewApmRuleFromStackManagement, + TimeUnit, +} from '../helper'; +import { NewAlertEmptyPrompt } from '../new_alert_empty_prompt'; import { ServiceAlertTrigger } from '../service_alert_trigger'; import { PopoverExpression } from '../service_alert_trigger/popover_expression'; @@ -77,9 +83,11 @@ export function TransactionDurationAlertTrigger(props: Props) { createCallApmApi(services as CoreStart); - const transactionTypes = useServiceTransactionTypesFetcher( - metadata?.serviceName - ); + const transactionTypes = useServiceTransactionTypesFetcher({ + serviceName: metadata?.serviceName, + start: metadata?.start, + end: metadata?.end, + }); const params = defaults( { @@ -104,16 +112,22 @@ export function TransactionDurationAlertTrigger(props: Props) { const { data } = useFetcher( (callApmApi) => { - if (params.windowSize && params.windowUnit) { + const { interval, start, end } = getIntervalAndTimeRange({ + windowSize: params.windowSize, + windowUnit: params.windowUnit as TimeUnit, + }); + if (interval && start && end) { return callApmApi({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', params: { query: { - ...getAbsoluteTimeRange(params.windowSize, params.windowUnit), aggregationType: params.aggregationType, environment: params.environment, serviceName: params.serviceName, transactionType: params.transactionType, + interval, + start, + end, }, }, }); @@ -146,6 +160,10 @@ export function TransactionDurationAlertTrigger(props: Props) { /> ); + if (isNewApmRuleFromStackManagement(alertParams, metadata)) { + return ; + } + if (!params.serviceName) { return null; } diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx index 519b18cd3a6b6..7b3c30da44c08 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx @@ -17,7 +17,8 @@ import { ServiceField, TransactionTypeField, } from '../fields'; -import { AlertMetadata } from '../helper'; +import { AlertMetadata, isNewApmRuleFromStackManagement } from '../helper'; +import { NewAlertEmptyPrompt } from '../new_alert_empty_prompt'; import { ServiceAlertTrigger } from '../service_alert_trigger'; import { PopoverExpression } from '../service_alert_trigger/popover_expression'; import { @@ -48,9 +49,11 @@ interface Props { export function TransactionDurationAnomalyAlertTrigger(props: Props) { const { alertParams, metadata, setAlertParams, setAlertProperty } = props; - const transactionTypes = useServiceTransactionTypesFetcher( - metadata?.serviceName - ); + const transactionTypes = useServiceTransactionTypesFetcher({ + serviceName: metadata?.serviceName, + start: metadata?.start, + end: metadata?.end, + }); const params = defaults( { @@ -71,6 +74,10 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { end: metadata?.end, }); + if (isNewApmRuleFromStackManagement(alertParams, metadata)) { + return ; + } + const fields = [ , { - if (params.windowSize && params.windowUnit) { + const { interval, start, end } = getIntervalAndTimeRange({ + windowSize: params.windowSize, + windowUnit: params.windowUnit as TimeUnit, + }); + if (interval && start && end) { return callApmApi({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', params: { query: { - ...getAbsoluteTimeRange(params.windowSize, params.windowUnit), environment: params.environment, serviceName: params.serviceName, transactionType: params.transactionType, + interval, + start, + end, }, }, }); @@ -95,6 +108,10 @@ export function TransactionErrorRateAlertTrigger(props: Props) { ] ); + if (isNewApmRuleFromStackManagement(alertParams, metadata)) { + return ; + } + const fields = [ , { diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap index 8fc51bd05be31..890c692096a66 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap @@ -86,7 +86,6 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` @@ -383,7 +382,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` @@ -552,7 +550,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` @@ -627,7 +624,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -644,7 +640,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
@@ -708,7 +703,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -735,7 +729,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` @@ -810,7 +803,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -827,7 +819,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
@@ -891,7 +882,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -918,7 +908,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` @@ -993,7 +982,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1010,7 +998,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
@@ -1074,7 +1061,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1101,7 +1087,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` @@ -1176,7 +1161,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1193,7 +1177,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
@@ -1257,7 +1240,6 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index c72c0cfbbceed..2cdb808622854 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -179,8 +179,6 @@ export function ServiceInventory() { canCreateJob && !userHasDismissedCallout; - const isLoading = mainStatisticsStatus === FETCH_STATUS.LOADING; - return ( <> @@ -192,17 +190,10 @@ export function ServiceInventory() { )} - ) - } + noItemsMessage={} /> diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx index 3e07e18f95a02..3a3ddcc558679 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx @@ -22,13 +22,9 @@ describe('NoServicesMessage', () => { describe(`when historicalDataFound is ${historicalDataFound}`, () => { it('renders', () => { expect(() => - render( - , - { wrapper: Wrapper } - ) + render(, { + wrapper: Wrapper, + }) ).not.toThrowError(); }); }); diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx index a2dc5feec44f8..872359262cf02 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx @@ -5,76 +5,35 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ErrorStatePrompt } from '../../shared/ErrorStatePrompt'; -import { useUpgradeAssistantHref } from '../../shared/Links/kibana'; -import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'; interface Props { - // any data submitted from APM agents found (not just in the given time range) - historicalDataFound: boolean; status: FETCH_STATUS | undefined; } -export function NoServicesMessage({ historicalDataFound, status }: Props) { - const upgradeAssistantHref = useUpgradeAssistantHref(); - - if (status === 'failure') { - return ; +export function NoServicesMessage({ status }: Props) { + if (status === FETCH_STATUS.LOADING) { + return null; } - if (historicalDataFound) { - return ( - - {i18n.translate('xpack.apm.servicesTable.notFoundLabel', { - defaultMessage: 'No services found', - })} -
- } - titleSize="s" - /> - ); + if (status === FETCH_STATUS.FAILURE) { + return ; } return ( - {i18n.translate('xpack.apm.servicesTable.noServicesLabel', { - defaultMessage: `Looks like you don't have any APM services installed. Let's add some!`, + {i18n.translate('xpack.apm.servicesTable.notFoundLabel', { + defaultMessage: 'No services found', })}
} titleSize="s" - body={ - -

- {i18n.translate('xpack.apm.servicesTable.7xUpgradeServerMessage', { - defaultMessage: `Upgrading from a pre-7.x version? Make sure you've also upgraded - your APM Server instance(s) to at least 7.0.`, - })} -

-

- {i18n.translate('xpack.apm.servicesTable.7xOldDataMessage', { - defaultMessage: - 'You may also have old data that needs to be migrated.', - })}{' '} - - {i18n.translate('xpack.apm.servicesTable.UpgradeAssistantLink', { - defaultMessage: - 'Learn more by visiting the Kibana Upgrade Assistant', - })} - - . -

-
- } - actions={} /> ); } diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index 8f2e921d4bd83..2ff3f2702cb53 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -134,28 +134,6 @@ describe('ServiceInventory', () => { expect(container.querySelectorAll('.euiTableRow')).toHaveLength(2); }); - it('should render getting started message, when list is empty and no historical data is found', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: false, - items: [], - }); - - const { findByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - - // wait for elements to be rendered - const gettingStartedMessage = await findByText( - "Looks like you don't have any APM services installed. Let's add some!" - ); - - expect(gettingStartedMessage).not.toBeEmptyDOMElement(); - }); - it('should render empty message, when list is empty and historical data is found', async () => { httpGet .mockResolvedValueOnce({ fallbackToTransactions: false }) diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx index 2a1ccd00e5a71..f4494f1841ba3 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx @@ -8,8 +8,10 @@ import { EuiPageHeaderProps, EuiPageTemplateProps } from '@elastic/eui'; import React from 'react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { useFetcher } from '../../../hooks/use_fetcher'; import { ApmPluginStartDeps } from '../../../plugin'; import { ApmEnvironmentFilter } from '../../shared/EnvironmentFilter'; +import { getNoDataConfig } from './no_data_config'; /* * This template contains: @@ -31,12 +33,25 @@ export function ApmMainTemplate({ children: React.ReactNode; } & EuiPageTemplateProps) { const { services } = useKibana(); + const { http, docLinks } = services; + const basePath = http?.basePath.get(); const ObservabilityPageTemplate = services.observability.navigation.PageTemplate; + const { data } = useFetcher((callApmApi) => { + return callApmApi({ endpoint: 'GET /api/apm/has_data' }); + }, []); + + const noDataConfig = getNoDataConfig({ + basePath, + docsLink: docLinks!.links.observability.guide, + hasData: data?.hasData, + }); + return ( ], diff --git a/x-pack/plugins/apm/public/components/routing/templates/no_data_config.ts b/x-pack/plugins/apm/public/components/routing/templates/no_data_config.ts new file mode 100644 index 0000000000000..868db57a0efdc --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/templates/no_data_config.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { KibanaPageTemplateProps } from '../../../../../../../src/plugins/kibana_react/public'; + +export function getNoDataConfig({ + docsLink, + basePath, + hasData, +}: { + docsLink: string; + basePath?: string; + hasData?: boolean; +}): KibanaPageTemplateProps['noDataConfig'] { + // Returns no data config when there is no historical data + if (hasData === false) { + return { + solution: i18n.translate('xpack.apm.noDataConfig.solutionName', { + defaultMessage: 'Observability', + }), + actions: { + beats: { + title: i18n.translate('xpack.apm.noDataConfig.beatsCard.title', { + defaultMessage: 'Add data with APM agents', + }), + description: i18n.translate( + 'xpack.apm.noDataConfig.beatsCard.description', + { + defaultMessage: + 'Use APM agents to collect APM data. We make it easy with agents for many popular languages.', + } + ), + href: basePath + `/app/home#/tutorial/apm`, + }, + }, + docsLink, + }; + } +} diff --git a/x-pack/plugins/apm/public/components/routing/templates/settings_template.test.tsx b/x-pack/plugins/apm/public/components/routing/templates/settings_template.test.tsx index d3efef4c88380..b601eb8ffcb2b 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/settings_template.test.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/settings_template.test.tsx @@ -11,7 +11,7 @@ import React, { ReactNode } from 'react'; import { SettingsTemplate } from './settings_template'; import { createMemoryHistory } from 'history'; import { MemoryRouter, RouteComponentProps } from 'react-router-dom'; -import { CoreStart } from 'kibana/public'; +import { CoreStart, DocLinksStart, HttpStart } from 'kibana/public'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; const { location } = createMemoryHistory(); @@ -25,6 +25,20 @@ const KibanaReactContext = createKibanaReactContext({ }, }, }, + http: { + basePath: { + prepend: (path: string) => `/basepath${path}`, + get: () => `/basepath`, + }, + } as HttpStart, + docLinks: ({ + DOC_LINK_VERSION: '0', + ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', + links: { + apm: {}, + observability: { guide: '' }, + }, + } as unknown) as DocLinksStart, } as Partial); function Wrapper({ children }: { children?: ReactNode }) { diff --git a/x-pack/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx index d859f89c8e097..c84287dad7a97 100644 --- a/x-pack/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx @@ -29,6 +29,7 @@ describe('TimestampTooltip', () => { 5 hours ago diff --git a/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts b/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts index b3551e7ccebcb..e361d72eb2ce8 100644 --- a/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts +++ b/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts @@ -19,6 +19,7 @@ import goIcon from './icons/go.svg'; import iosIcon from './icons/ios.svg'; import darkIosIcon from './icons/ios_dark.svg'; import javaIcon from './icons/java.svg'; +import lambdaIcon from './icons/lambda.svg'; import nodeJsIcon from './icons/nodejs.svg'; import ocamlIcon from './icons/ocaml.svg'; import openTelemetryIcon from './icons/opentelemetry.svg'; @@ -37,6 +38,7 @@ const agentIcons: { [key: string]: string } = { go: goIcon, ios: iosIcon, java: javaIcon, + lambda: lambdaIcon, nodejs: nodeJsIcon, ocaml: ocamlIcon, opentelemetry: openTelemetryIcon, diff --git a/x-pack/plugins/apm/public/components/shared/agent_icon/icons/lambda.svg b/x-pack/plugins/apm/public/components/shared/agent_icon/icons/lambda.svg new file mode 100644 index 0000000000000..2ecd8c5e916b4 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/agent_icon/icons/lambda.svg @@ -0,0 +1,4 @@ + + + + diff --git a/x-pack/plugins/apm/public/components/shared/sticky_properties/__snapshots__/sticky_properties.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/sticky_properties/__snapshots__/sticky_properties.test.tsx.snap index 020d5952c03e4..f8799874408e3 100644 --- a/x-pack/plugins/apm/public/components/shared/sticky_properties/__snapshots__/sticky_properties.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/shared/sticky_properties/__snapshots__/sticky_properties.test.tsx.snap @@ -29,6 +29,7 @@ exports[`StickyProperties should render entire component 1`] = ` } delay="regular" + display="inlineBlock" position="top" > @@ -39,6 +40,7 @@ exports[`StickyProperties should render entire component 1`] = ` @@ -64,6 +66,7 @@ exports[`StickyProperties should render entire component 1`] = ` } delay="regular" + display="inlineBlock" position="top" > @@ -93,6 +96,7 @@ exports[`StickyProperties should render entire component 1`] = ` } delay="regular" + display="inlineBlock" position="top" > @@ -122,6 +126,7 @@ exports[`StickyProperties should render entire component 1`] = ` } delay="regular" + display="inlineBlock" position="top" > diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index 6b423cac88887..d6b052a5dc884 100644 --- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -51,7 +51,11 @@ export function ApmServiceContextProvider({ end, }); - const transactionTypes = useServiceTransactionTypesFetcher(serviceName); + const transactionTypes = useServiceTransactionTypesFetcher({ + serviceName, + start, + end, + }); const transactionType = getTransactionType({ transactionType: query.transactionType, diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index ab1a2877cf5bf..c2e81cb0c92ae 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -5,19 +5,19 @@ * 2.0. */ -import { useApmParams } from '../../hooks/use_apm_params'; import { useFetcher } from '../../hooks/use_fetcher'; -import { useTimeRange } from '../../hooks/use_time_range'; const INITIAL_DATA = { transactionTypes: [] }; -export function useServiceTransactionTypesFetcher(serviceName?: string) { - const { - query: { rangeFrom, rangeTo }, - } = useApmParams('/services/{serviceName}'); - - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - +export function useServiceTransactionTypesFetcher({ + serviceName, + start, + end, +}: { + serviceName?: string; + start?: string; + end?: string; +}) { const { data = INITIAL_DATA } = useFetcher( (callApmApi) => { if (serviceName && start && end) { diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx index fbbb2e1ffedf4..c69623f92987a 100644 --- a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx @@ -80,7 +80,7 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { display="plain" textAlign="left" title={i18n.translate('xpack.apm.tutorial.apmServer.fleet.title', { - defaultMessage: 'Elastic APM (beta) now available in Fleet!', + defaultMessage: 'Elastic APM now available in Fleet!', })} description={i18n.translate( 'xpack.apm.tutorial.apmServer.fleet.message', diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts index 3a67076201efa..8e3a44d780630 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts @@ -6,17 +6,19 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; +import { rangeQuery } from '../../../../../observability/server'; import { - PROCESSOR_EVENT, SERVICE_NAME, - TRANSACTION_DURATION, TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeQuery } from '../../../../../observability/server'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { getBucketSize } from '../../helpers/get_bucket_size'; +import { + getDocumentTypeFilterForAggregatedTransactions, + getProcessorEventForAggregatedTransactions, + getSearchAggregatedTransactions, + getTransactionDurationFieldForAggregatedTransactions, +} from '../../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getTransactionDurationChartPreview({ @@ -26,43 +28,58 @@ export async function getTransactionDurationChartPreview({ alertParams: AlertParams; setup: Setup & SetupTimeRange; }) { + const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + ...setup, + kuery: '', + }); + const { apmEventClient, start, end } = setup; const { aggregationType, environment, serviceName, transactionType, + interval, } = alertParams; const query = { bool: { filter: [ - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), ...(transactionType ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] : []), ...rangeQuery(start, end), ...environmentQuery(environment), + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), ] as QueryDslQueryContainer[], }, }; - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); + const transactionDurationField = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); const aggs = { timeseries: { date_histogram: { field: '@timestamp', - fixed_interval: intervalString, + fixed_interval: interval, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, }, aggs: { agg: aggregationType === 'avg' - ? { avg: { field: TRANSACTION_DURATION } } + ? { avg: { field: transactionDurationField } } : { percentiles: { - field: TRANSACTION_DURATION, + field: transactionDurationField, percents: [aggregationType === '95th' ? 95 : 99], }, }, @@ -70,7 +87,13 @@ export async function getTransactionDurationChartPreview({ }, }; const params = { - apm: { events: [ProcessorEvent.transaction] }, + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, body: { size: 0, query, aggs }, }; const resp = await apmEventClient.search( diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts index 0ead50c709083..d15f82248dec5 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts @@ -10,7 +10,6 @@ import { ProcessorEvent } from '../../../../common/processor_event'; import { AlertParams } from '../../../routes/alerts/chart_preview'; import { rangeQuery } from '../../../../../observability/server'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getTransactionErrorCountChartPreview({ @@ -21,7 +20,7 @@ export async function getTransactionErrorCountChartPreview({ alertParams: AlertParams; }) { const { apmEventClient, start, end } = setup; - const { serviceName, environment } = alertParams; + const { serviceName, environment, interval } = alertParams; const query = { bool: { @@ -33,13 +32,15 @@ export async function getTransactionErrorCountChartPreview({ }, }; - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); - const aggs = { timeseries: { date_histogram: { field: '@timestamp', - fixed_interval: intervalString, + fixed_interval: interval, + extended_bounds: { + min: start, + max: end, + }, }, }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts index f4d8ffc2749c3..ed4a7a7208eeb 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts @@ -5,16 +5,18 @@ * 2.0. */ +import { rangeQuery } from '../../../../../observability/server'; import { - PROCESSOR_EVENT, SERVICE_NAME, TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../../common/processor_event'; -import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { rangeQuery } from '../../../../../observability/server'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { getBucketSize } from '../../helpers/get_bucket_size'; +import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { + getDocumentTypeFilterForAggregatedTransactions, + getProcessorEventForAggregatedTransactions, + getSearchAggregatedTransactions, +} from '../../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { calculateFailedTransactionRate, @@ -28,43 +30,58 @@ export async function getTransactionErrorRateChartPreview({ setup: Setup & SetupTimeRange; alertParams: AlertParams; }) { - const { apmEventClient, start, end } = setup; - const { serviceName, environment, transactionType } = alertParams; + const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + ...setup, + kuery: '', + }); - const query = { - bool: { - filter: [ - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...(transactionType - ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] - : []), - ...rangeQuery(start, end), - ...environmentQuery(environment), - ], - }, - }; + const { apmEventClient, start, end } = setup; + const { serviceName, environment, transactionType, interval } = alertParams; const outcomes = getOutcomeAggregation(); - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); - - const aggs = { - outcomes, - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), + ...(transactionType + ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] + : []), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + }, + aggs: { + outcomes, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: interval, + extended_bounds: { + min: start, + max: end, + }, + }, + aggs: { outcomes }, + }, }, - aggs: { outcomes }, }, }; - const params = { - apm: { events: [ProcessorEvent.transaction] }, - body: { size: 0, query, aggs }, - }; - const resp = await apmEventClient.search( 'get_transaction_error_rate_chart_preview', params diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts index 35f5721eee05c..46596d8ac864a 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts @@ -8,7 +8,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { createStaticIndexPattern } from './create_static_index_pattern'; import { Setup } from '../helpers/setup_request'; -import * as HistoricalAgentData from '../services/get_services/has_historical_agent_data'; +import * as HistoricalAgentData from '../../routes/historical_data/has_historical_agent_data'; import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client'; import { APMConfig } from '../..'; diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index 414414c6bfe65..6fa96de0b9413 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -8,7 +8,7 @@ import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../common/index_pattern_constants'; import apmIndexPattern from '../../tutorial/index_pattern.json'; -import { hasHistoricalAgentData } from '../services/get_services/has_historical_agent_data'; +import { hasHistoricalAgentData } from '../../routes/historical_data/has_historical_agent_data'; import { Setup } from '../helpers/setup_request'; import { APMRouteHandlerResources } from '../../routes/typings'; import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client.js'; diff --git a/x-pack/plugins/apm/server/lib/services/get_services/index.ts b/x-pack/plugins/apm/server/lib/services/get_services/index.ts index 61cb4a28586d7..d4b11880b56ab 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/index.ts @@ -6,12 +6,10 @@ */ import { Logger } from '@kbn/logging'; -import { isEmpty } from 'lodash'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getLegacyDataStatus } from './get_legacy_data_status'; import { getServicesItems } from './get_services_items'; -import { hasHistoricalAgentData } from './has_historical_agent_data'; export async function getServices({ environment, @@ -38,14 +36,8 @@ export async function getServices({ getLegacyDataStatus(setup), ]); - const noDataInCurrentTimeRange = isEmpty(items); - const hasHistoricalData = noDataInCurrentTimeRange - ? await hasHistoricalAgentData(setup) - : true; - return { items, - hasHistoricalData, hasLegacyData, }; }); diff --git a/x-pack/plugins/apm/server/lib/services/queries.test.ts b/x-pack/plugins/apm/server/lib/services/queries.test.ts index be5f280477a09..a4a32229cbd44 100644 --- a/x-pack/plugins/apm/server/lib/services/queries.test.ts +++ b/x-pack/plugins/apm/server/lib/services/queries.test.ts @@ -9,7 +9,7 @@ import { getServiceAgent } from './get_service_agent'; import { getServiceTransactionTypes } from './get_service_transaction_types'; import { getServicesItems } from './get_services/get_services_items'; import { getLegacyDataStatus } from './get_services/get_legacy_data_status'; -import { hasHistoricalAgentData } from './get_services/has_historical_agent_data'; +import { hasHistoricalAgentData } from '../../routes/historical_data/has_historical_agent_data'; import { SearchParamsMock, inspectSearchParams, diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts index 13879cb5fecb7..c28bca5048350 100644 --- a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts @@ -26,6 +26,9 @@ const alertParamsRt = t.intersection([ }), environmentRt, rangeRt, + t.type({ + interval: t.string, + }), ]); export type AlertParams = t.TypeOf; diff --git a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts index 941eb796d3ab3..9bc9108da9055 100644 --- a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts @@ -33,6 +33,7 @@ import { sourceMapsRouteRepository } from './source_maps'; import { traceRouteRepository } from './traces'; import { transactionRouteRepository } from './transactions'; import { APMRouteHandlerResources } from './typings'; +import { historicalDataRouteRepository } from './historical_data'; const getTypedGlobalApmServerRouteRepository = () => { const repository = createApmServerRouteRepository() @@ -56,7 +57,8 @@ const getTypedGlobalApmServerRouteRepository = () => { .merge(sourceMapsRouteRepository) .merge(apmFleetRouteRepository) .merge(backendsRouteRepository) - .merge(fallbackToTransactionsRouteRepository); + .merge(fallbackToTransactionsRouteRepository) + .merge(historicalDataRouteRepository); return repository; }; diff --git a/x-pack/plugins/apm/server/lib/services/get_services/has_historical_agent_data.ts b/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts similarity index 86% rename from x-pack/plugins/apm/server/lib/services/get_services/has_historical_agent_data.ts rename to x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts index 97b8a8fa5505b..13591b47a8584 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/has_historical_agent_data.ts +++ b/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { ProcessorEvent } from '../../../../common/processor_event'; -import { Setup } from '../../helpers/setup_request'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { Setup } from '../../lib/helpers/setup_request'; // Note: this logic is duplicated in tutorials/apm/envs/on_prem export async function hasHistoricalAgentData(setup: Setup) { diff --git a/x-pack/plugins/apm/server/routes/historical_data/index.ts b/x-pack/plugins/apm/server/routes/historical_data/index.ts new file mode 100644 index 0000000000000..6e574a435bc83 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/historical_data/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setupRequest } from '../../lib/helpers/setup_request'; +import { createApmServerRoute } from '../create_apm_server_route'; +import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; +import { hasHistoricalAgentData } from './has_historical_agent_data'; + +const hasDataRoute = createApmServerRoute({ + endpoint: 'GET /api/apm/has_data', + options: { tags: ['access:apm'] }, + handler: async (resources) => { + const setup = await setupRequest(resources); + const hasData = await hasHistoricalAgentData(setup); + return { hasData }; + }, +}); + +export const historicalDataRouteRepository = createApmServerRouteRepository().add( + hasDataRoute +); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts b/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts index 78fc82393994b..79bb75af677ae 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MAP_SAVED_OBJECT_TYPE } from '../../../../plugins/maps/common/constants'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../../plugins/maps/common'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../src/plugins/visualizations/common/constants'; import { LENS_EMBEDDABLE_TYPE } from '../../../../plugins/lens/common/constants'; import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../src/plugins/discover/common'; diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot index 19b44540943b3..a891b7ebe7686 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot @@ -64,7 +64,6 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` > @@ -92,7 +91,6 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` > @@ -125,7 +123,6 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` > @@ -158,7 +155,6 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` > @@ -251,7 +247,6 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` > @@ -279,7 +274,6 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` > @@ -312,7 +306,6 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` > @@ -345,7 +338,6 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` > diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot index 05ef9df1c8601..6ef6d19e446db 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot @@ -348,7 +348,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > @@ -376,7 +375,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > @@ -409,7 +407,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > @@ -442,7 +439,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > @@ -524,7 +520,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > @@ -552,7 +547,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > @@ -585,7 +579,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > @@ -618,7 +611,6 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` > diff --git a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot index fdf14191ece4c..9c9ed566e9482 100644 --- a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot @@ -22,7 +22,6 @@ exports[`Storyshots components/ExpressionInput default 1`] = ` > diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot b/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot index 0de7012aee1a3..7d05176b8147c 100644 --- a/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot @@ -443,7 +443,6 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = ` > @@ -471,7 +470,6 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = ` > @@ -619,7 +617,6 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = ` > @@ -647,7 +644,6 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = ` > @@ -795,7 +791,6 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = ` > @@ -823,7 +818,6 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = ` > diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_controls.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_controls.stories.storyshot index e70905298a58c..31ff283e7030f 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_controls.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_controls.stories.storyshot @@ -16,7 +16,6 @@ exports[`Storyshots components/SavedElementsModal/ElementControls has two button > @@ -45,7 +44,6 @@ exports[`Storyshots components/SavedElementsModal/ElementControls has two button > diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_grid.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_grid.stories.storyshot index e0ed2934f44b6..db5daa86b1386 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_grid.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/element_grid.stories.storyshot @@ -67,7 +67,6 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = ` > @@ -96,7 +95,6 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = ` > @@ -178,7 +176,6 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = ` > @@ -207,7 +204,6 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = ` > @@ -289,7 +285,6 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = ` > @@ -318,7 +313,6 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = ` > diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot index 372cd12db1b99..f019f9dc8f23d 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot @@ -294,7 +294,6 @@ exports[`Storyshots components/SavedElementsModal with custom elements 1`] = ` > @@ -323,7 +322,6 @@ exports[`Storyshots components/SavedElementsModal with custom elements 1`] = ` > @@ -405,7 +403,6 @@ exports[`Storyshots components/SavedElementsModal with custom elements 1`] = ` > @@ -434,7 +431,6 @@ exports[`Storyshots components/SavedElementsModal with custom elements 1`] = ` > @@ -516,7 +512,6 @@ exports[`Storyshots components/SavedElementsModal with custom elements 1`] = ` > @@ -545,7 +540,6 @@ exports[`Storyshots components/SavedElementsModal with custom elements 1`] = ` > @@ -764,7 +758,6 @@ exports[`Storyshots components/SavedElementsModal with text filter 1`] = ` > @@ -793,7 +786,6 @@ exports[`Storyshots components/SavedElementsModal with text filter 1`] = ` > diff --git a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx index 9cca858223078..4935647ca6810 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx @@ -12,7 +12,7 @@ import { ElementSettings as Component } from './element_settings.component'; import { State, PositionedElement } from '../../../../types'; interface Props { - selectedElementId: string; + selectedElementId: string | null; } const mapStateToProps = (state: State, { selectedElementId }: Props): StateProps => ({ diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx b/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx index 7976ad1f6d01a..8252455e9ebd8 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx @@ -6,7 +6,6 @@ */ import React, { FunctionComponent } from 'react'; -// @ts-expect-error unconverted component import { SidebarContent } from './sidebar_content'; interface Props { diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/index.ts b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/index.ts new file mode 100644 index 0000000000000..867f47ea3de65 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { SidebarContent } from './sidebar_content'; +export { SidebarContent as SidebarContentComponent } from './sidebar_content.component'; diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.component.tsx similarity index 58% rename from x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js rename to x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.component.tsx index 7292a98fa91ae..c469c2fda2776 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.component.tsx @@ -6,17 +6,19 @@ */ import React, { Fragment } from 'react'; -import { connect } from 'react-redux'; -import { compose, branch, renderComponent } from 'recompose'; import { EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { SidebarHeader } from '../../sidebar_header'; +import { MultiElementSettings } from '../multi_element_settings'; +import { GroupSettings } from '../group_settings'; +import { GlobalConfig } from '../global_config'; +import { ElementSettings } from '../element_settings'; -import { getSelectedToplevelNodes, getSelectedElementId } from '../../state/selectors/workpad'; -import { SidebarHeader } from '../sidebar_header'; -import { MultiElementSettings } from './multi_element_settings'; -import { GroupSettings } from './group_settings'; -import { GlobalConfig } from './global_config'; -import { ElementSettings } from './element_settings'; +interface SidebarContentProps { + commit?: Function; + selectedElementId: string | null; + selectedToplevelNodes: string[]; +} const strings = { getGroupedElementSidebarTitle: () => @@ -43,12 +45,7 @@ const strings = { }), }; -const mapStateToProps = (state) => ({ - selectedToplevelNodes: getSelectedToplevelNodes(state), - selectedElementId: getSelectedElementId(state), -}); - -const MultiElementSidebar = () => ( +const MultiElementSidebar: React.FC = () => ( @@ -56,38 +53,38 @@ const MultiElementSidebar = () => ( ); -const GroupedElementSidebar = () => ( +const GroupedElementSidebar: React.FC = () => ( - + ); -const SingleElementSidebar = ({ selectedElementId }) => ( +const SingleElementSidebar: React.FC<{ selectedElementId: string | null }> = ({ + selectedElementId, +}) => ( - + ); -const branches = [ - // multiple elements are selected - branch( - ({ selectedToplevelNodes }) => selectedToplevelNodes.length > 1, - renderComponent(MultiElementSidebar) - ), - // a single, grouped element is selected - branch( - ({ selectedToplevelNodes }) => - selectedToplevelNodes.length === 1 && selectedToplevelNodes[0].includes('group'), - renderComponent(GroupedElementSidebar) - ), - // a single element is selected - branch( - ({ selectedToplevelNodes }) => selectedToplevelNodes.length === 1, - renderComponent(SingleElementSidebar) - ), -]; +export const SidebarContent: React.FC = ({ + selectedToplevelNodes, + selectedElementId, +}) => { + if (selectedToplevelNodes.length > 1) { + return ; + } + + if (selectedToplevelNodes.length === 1 && selectedToplevelNodes[0].includes('group')) { + return ; + } -export const SidebarContent = compose(connect(mapStateToProps), ...branches)(GlobalConfig); + if (selectedToplevelNodes.length === 1) { + return ; + } + + return ; +}; diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.tsx b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.tsx new file mode 100644 index 0000000000000..e53f5d6d515df --- /dev/null +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useSelector } from 'react-redux'; +import { getSelectedToplevelNodes, getSelectedElementId } from '../../../state/selectors/workpad'; +import { State } from '../../../../types'; +import { SidebarContent as Component } from './sidebar_content.component'; + +interface SidebarContentProps { + commit?: Function; +} + +export const SidebarContent: React.FC = ({ commit }) => { + const selectedToplevelNodes = useSelector((state) => + getSelectedToplevelNodes(state) + ); + + const selectedElementId = useSelector((state) => + getSelectedElementId(state) + ); + + return ( + + ); +}; diff --git a/x-pack/plugins/canvas/public/components/sidebar_header/__stories__/__snapshots__/sidebar_header.stories.storyshot b/x-pack/plugins/canvas/public/components/sidebar_header/__stories__/__snapshots__/sidebar_header.stories.storyshot index e01f34abeb9f0..130f698b1152e 100644 --- a/x-pack/plugins/canvas/public/components/sidebar_header/__stories__/__snapshots__/sidebar_header.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/sidebar_header/__stories__/__snapshots__/sidebar_header.stories.storyshot @@ -55,7 +55,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = ` > @@ -83,7 +82,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = ` > @@ -111,7 +109,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = ` > @@ -139,7 +136,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = ` > diff --git a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot index 5d8efeafef69c..8ca3dc2a94d4e 100644 --- a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot @@ -35,7 +35,6 @@ exports[`Storyshots components/Variables/VarConfig default 1`] = ` > @@ -53,7 +52,6 @@ exports[`Storyshots components/Variables/VarConfig default 1`] = ` > diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__stories__/__snapshots__/simple_template.stories.storyshot b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__stories__/__snapshots__/simple_template.stories.storyshot index f4233e27345c0..ccdd0a8592f2b 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__stories__/__snapshots__/simple_template.stories.storyshot +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__stories__/__snapshots__/simple_template.stories.storyshot @@ -212,7 +212,6 @@ exports[`Storyshots arguments/SeriesStyle/components simple: no series 1`] = ` > diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx index 29131b4a3372a..13aab06640bd5 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx @@ -8,8 +8,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { htmlIdGenerator } from '@elastic/eui'; -import { LayerDescriptor } from '../../../../../../maps/common/descriptor_types'; -import { INITIAL_LOCATION } from '../../../../../../maps/common/constants'; +import { INITIAL_LOCATION, LayerDescriptor } from '../../../../../../maps/common'; import { MapEmbeddable, MapEmbeddableInput, diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/format_utils.ts b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/format_utils.ts index ddc9255b4834d..b314741e95284 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/format_utils.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/format_utils.ts @@ -8,7 +8,7 @@ import { Feature, Point } from 'geojson'; import { euiPaletteColorBlind } from '@elastic/eui'; import { DEFAULT_GEO_REGEX } from './geo_point_content'; -import { SOURCE_TYPES } from '../../../../../../../maps/common/constants'; +import { SOURCE_TYPES } from '../../../../../../../maps/common'; export const convertWKTGeoToLonLat = ( value: string | number diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx index b4c8c3c22f5a9..42245742f8b7d 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx @@ -13,10 +13,9 @@ import { ExpandedRowContent } from '../../stats_table/components/field_data_expa import { DocumentStatsTable } from '../../stats_table/components/field_data_expanded_row/document_stats'; import { ExamplesList } from '../../examples_list'; import { FieldVisConfig } from '../../stats_table/types'; -import { LayerDescriptor } from '../../../../../../../maps/common/descriptor_types'; import { useDataVisualizerKibana } from '../../../../kibana_context'; import { JOB_FIELD_TYPES } from '../../../../../../common'; -import { ES_GEO_FIELD_TYPE } from '../../../../../../../maps/common'; +import { ES_GEO_FIELD_TYPE, LayerDescriptor } from '../../../../../../../maps/common'; import { EmbeddedMapComponent } from '../../embedded_map'; export const GeoPointContentWithMap: FC<{ diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap index 769ebdeba9955..e69e2e7626718 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap @@ -4,6 +4,7 @@ exports[`FieldTypeIcon render component when type matches a field type 1`] = ` { action={action} /> ); - expect(wrapper.find('[data-test-subj="errorMessage"]').dive().text()).toContain( + expect(wrapper.find('[data-test-subj="errorMessage"]').childAt(0).text()).toContain( 'Error message' ); expect(wrapper.find('[data-test-subj="action"]')).toHaveLength(1); @@ -58,7 +58,7 @@ describe('ValidationStepPanel', () => { action={action} /> ); - expect(wrapper.find('[data-test-subj="errorMessage"]').dive().text()).toContain( + expect(wrapper.find('[data-test-subj="errorMessage"]').childAt(0).text()).toContain( 'Error message' ); expect(wrapper.find('[data-test-subj="action"]')).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx index 804c2d86ca099..dc19b526714a5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx @@ -7,7 +7,14 @@ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiMarkdownFormat, + EuiPanel, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; import { CrawlerDomainValidationStep } from '../../types'; @@ -26,9 +33,14 @@ export const ValidationStepPanel: React.FC = ({ action, }) => { const showErrorMessage = step.state === 'invalid' || step.state === 'warning'; + const styleOverride = showErrorMessage ? { paddingBottom: 0 } : {}; return ( - + @@ -41,13 +53,14 @@ export const ValidationStepPanel: React.FC = ({ {showErrorMessage && ( <> - -

{step.message}

-
+ + + {step.message || ''} + {action && ( <> - {action} + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts index e63469f985d7d..14d97c7dd3f4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts @@ -418,6 +418,16 @@ describe('RoleMappingsLogic', () => { expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); + + it('resets roleMapping state', () => { + mount({ + ...mappingsServerProps, + roleMapping: asRoleMapping, + }); + RoleMappingsLogic.actions.initializeRoleMappings(); + + expect(RoleMappingsLogic.values.roleMapping).toEqual(null); + }); }); describe('initializeRoleMapping', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts index 6dac9e481f7ef..7b7bcd864ebd0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts @@ -178,6 +178,7 @@ export const RoleMappingsLogic = kea roleMapping, + initializeRoleMappings: () => null, resetState: () => null, closeUsersAndRolesFlyout: () => null, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx index 3b6e2dc440a76..3dc5560a9eaba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { EuiBadge, EuiBasicTableColumn, EuiInMemoryTable, EuiTextColor } from '@elastic/eui'; import type { EuiSearchBarOnChangeArgs } from '@elastic/eui'; @@ -57,6 +57,8 @@ const noItemsPlaceholder = ; const invitationBadge = {INVITATION_PENDING_LABEL}; const deactivatedBadge = {DEACTIVATED_LABEL}; +type Users = Array>; + export const UsersTable: React.FC = ({ accessItemKey, singleUserRoleMappings, @@ -72,9 +74,13 @@ export const UsersTable: React.FC = ({ id: user.roleMapping.id, accessItems: (user.roleMapping as SharedRoleMapping)[accessItemKey], invitation: user.invitation, - })) as unknown) as Array>; + })) as unknown) as Users; + + const [items, setItems] = useState([] as Users); - const [items, setItems] = useState(users); + useEffect(() => { + setItems(users); + }, [singleUserRoleMappings]); const columns: Array> = [ { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx index 35ac8f1b85c05..025230d0b5c1a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx @@ -11,7 +11,6 @@ import { EuiCopy, EuiButton, EuiButtonIcon, - EuiToolTip, EuiSpacer, EuiCodeBlock, EuiPopover, @@ -30,19 +29,17 @@ export const StatusItem: React.FC = ({ details }) => { const closePopover = () => setIsPopoverOpen(false); const formattedDetails = details.join('\n'); - const tooltipPopoverTrigger = ( - - - + const popoverTrigger = ( + ); const infoPopover = ( - + { expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); + + it('resets roleMapping state', () => { + mount({ + ...mappingsServerProps, + roleMapping: wsRoleMapping, + }); + RoleMappingsLogic.actions.initializeRoleMappings(); + + expect(RoleMappingsLogic.values.roleMapping).toEqual(null); + }); }); describe('initializeRoleMapping', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts index 64cec51001647..55f82a07bf405 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts @@ -168,6 +168,7 @@ export const RoleMappingsLogic = kea roleMapping, + initializeRoleMappings: () => null, resetState: () => null, closeUsersAndRolesFlyout: () => null, }, diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts index 5c2277278bb13..a87d22b6b047a 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts @@ -26,7 +26,7 @@ export function registerOAuthAuthorizeRoute({ response_mode: schema.maybe(schema.string()), redirect_uri: schema.maybe(schema.string()), scope: schema.maybe(schema.string()), - state: schema.maybe(schema.string()), + state: schema.nullable(schema.string()), }), }, }, @@ -49,7 +49,7 @@ export function registerOAuthAuthorizeAcceptRoute({ response_type: schema.string(), redirect_uri: schema.maybe(schema.string()), scope: schema.maybe(schema.string()), - state: schema.maybe(schema.string()), + state: schema.nullable(schema.string()), }), }, }, @@ -72,7 +72,7 @@ export function registerOAuthAuthorizeDenyRoute({ response_type: schema.string(), redirect_uri: schema.maybe(schema.string()), scope: schema.maybe(schema.string()), - state: schema.maybe(schema.string()), + state: schema.nullable(schema.string()), }), }, }, diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts index 8a33342e71d02..667512ea13f65 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts @@ -17,6 +17,12 @@ const createClusterClientMock = () => { createIndexTemplate: jest.fn(), doesAliasExist: jest.fn(), createIndex: jest.fn(), + getExistingLegacyIndexTemplates: jest.fn(), + setLegacyIndexTemplateToHidden: jest.fn(), + getExistingIndices: jest.fn(), + setIndexToHidden: jest.fn(), + getExistingIndexAliases: jest.fn(), + setIndexAliasToHidden: jest.fn(), queryEventsBySavedObjects: jest.fn(), shutdown: jest.fn(), }; diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index ef43b9081f9ec..f4140298928b6 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -16,7 +16,7 @@ import { findOptionsSchema } from '../event_log_client'; import { delay } from '../lib/delay'; import { times } from 'lodash'; import { DeeplyMockedKeys } from '@kbn/utility-types/jest'; -import { RequestEvent } from '@elastic/elasticsearch'; +import { estypes, RequestEvent } from '@elastic/elasticsearch'; type MockedLogger = ReturnType; @@ -215,22 +215,39 @@ describe('doesIndexTemplateExist', () => { }); }); - test('should return true when call cluster returns true', async () => { + test('should return true when call cluster to legacy template API returns true', async () => { clusterClient.indices.existsTemplate.mockResolvedValue(asApiResponse(true)); + clusterClient.indices.existsIndexTemplate.mockResolvedValue(asApiResponse(false)); await expect(clusterClientAdapter.doesIndexTemplateExist('foo')).resolves.toEqual(true); }); - test('should return false when call cluster returns false', async () => { + test('should return true when call cluster to index template API returns true', async () => { clusterClient.indices.existsTemplate.mockResolvedValue(asApiResponse(false)); + clusterClient.indices.existsIndexTemplate.mockResolvedValue(asApiResponse(true)); + await expect(clusterClientAdapter.doesIndexTemplateExist('foo')).resolves.toEqual(true); + }); + + test('should return false when both call cluster calls returns false', async () => { + clusterClient.indices.existsTemplate.mockResolvedValue(asApiResponse(false)); + clusterClient.indices.existsIndexTemplate.mockResolvedValue(asApiResponse(false)); await expect(clusterClientAdapter.doesIndexTemplateExist('foo')).resolves.toEqual(false); }); - test('should throw error when call cluster throws an error', async () => { + test('should throw error when call cluster to legacy template API throws an error', async () => { clusterClient.indices.existsTemplate.mockRejectedValue(new Error('Fail')); await expect( clusterClientAdapter.doesIndexTemplateExist('foo') ).rejects.toThrowErrorMatchingInlineSnapshot( - `"error checking existance of index template: Fail"` + `"error checking existence of index template: Fail"` + ); + }); + + test('should throw error when call cluster to index template API throws an error', async () => { + clusterClient.indices.existsIndexTemplate.mockRejectedValue(new Error('Fail')); + await expect( + clusterClientAdapter.doesIndexTemplateExist('foo') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error checking existence of index template: Fail"` ); }); }); @@ -238,7 +255,7 @@ describe('doesIndexTemplateExist', () => { describe('createIndexTemplate', () => { test('should call cluster with given template', async () => { await clusterClientAdapter.createIndexTemplate('foo', { args: true }); - expect(clusterClient.indices.putTemplate).toHaveBeenCalledWith({ + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith({ name: 'foo', create: true, body: { args: true }, @@ -246,20 +263,274 @@ describe('createIndexTemplate', () => { }); test(`should throw error if index template still doesn't exist after error is thrown`, async () => { - clusterClient.indices.putTemplate.mockRejectedValueOnce(new Error('Fail')); + clusterClient.indices.putIndexTemplate.mockRejectedValueOnce(new Error('Fail')); clusterClient.indices.existsTemplate.mockResolvedValueOnce(asApiResponse(false)); + clusterClient.indices.existsIndexTemplate.mockResolvedValueOnce(asApiResponse(false)); await expect( clusterClientAdapter.createIndexTemplate('foo', { args: true }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"error creating index template: Fail"`); }); test('should not throw error if index template exists after error is thrown', async () => { - clusterClient.indices.putTemplate.mockRejectedValueOnce(new Error('Fail')); + clusterClient.indices.putIndexTemplate.mockRejectedValueOnce(new Error('Fail')); clusterClient.indices.existsTemplate.mockResolvedValueOnce(asApiResponse(true)); await clusterClientAdapter.createIndexTemplate('foo', { args: true }); }); }); +describe('getExistingLegacyIndexTemplates', () => { + test('should call cluster with given index template pattern', async () => { + await clusterClientAdapter.getExistingLegacyIndexTemplates('foo*'); + expect(clusterClient.indices.getTemplate).toHaveBeenCalledWith( + { + name: 'foo*', + }, + { ignore: [404] } + ); + }); + + test('should return templates when found', async () => { + const response = { + 'foo-bar-template': { + order: 0, + index_patterns: ['foo-bar-*'], + settings: { index: { number_of_shards: '1' } }, + mappings: { dynamic: false, properties: {} }, + aliases: {}, + }, + }; + clusterClient.indices.getTemplate.mockResolvedValue( + asApiResponse(response) + ); + await expect(clusterClientAdapter.getExistingLegacyIndexTemplates('foo*')).resolves.toEqual( + response + ); + }); + + test('should throw error when call cluster throws an error', async () => { + clusterClient.indices.getTemplate.mockRejectedValue(new Error('Fail')); + await expect( + clusterClientAdapter.getExistingLegacyIndexTemplates('foo*') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error getting existing legacy index templates: Fail"` + ); + }); +}); + +describe('setLegacyIndexTemplateToHidden', () => { + test('should call cluster with given index template name and template', async () => { + const currentTemplate = { + order: 0, + index_patterns: ['foo-bar-*'], + settings: { index: { number_of_shards: '1' } }, + mappings: { dynamic: false, properties: {} }, + aliases: {}, + }; + await clusterClientAdapter.setLegacyIndexTemplateToHidden('foo-bar-template', currentTemplate); + expect(clusterClient.indices.putTemplate).toHaveBeenCalledWith({ + name: 'foo-bar-template', + body: { + order: 0, + index_patterns: ['foo-bar-*'], + settings: { index: { number_of_shards: '1' }, 'index.hidden': true }, + mappings: { dynamic: false, properties: {} }, + aliases: {}, + }, + }); + }); + + test('should throw error when call cluster throws an error', async () => { + clusterClient.indices.putTemplate.mockRejectedValue(new Error('Fail')); + await expect( + clusterClientAdapter.setLegacyIndexTemplateToHidden('foo-bar-template', { + aliases: {}, + index_patterns: [], + mappings: {}, + order: 0, + settings: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error setting existing legacy index template foo-bar-template to hidden: Fail"` + ); + }); +}); + +describe('getExistingIndices', () => { + test('should call cluster with given index pattern', async () => { + await clusterClientAdapter.getExistingIndices('foo*'); + expect(clusterClient.indices.getSettings).toHaveBeenCalledWith( + { + index: 'foo*', + }, + { ignore: [404] } + ); + }); + + test('should return indices when found', async () => { + const response = { + 'foo-bar-000001': { + settings: { + index: { + number_of_shards: 1, + uuid: 'Ure4d9edQbCMtcmyy0ObrA', + }, + }, + }, + }; + clusterClient.indices.getSettings.mockResolvedValue( + asApiResponse(response) + ); + await expect(clusterClientAdapter.getExistingIndices('foo*')).resolves.toEqual(response); + }); + + test('should throw error when call cluster throws an error', async () => { + clusterClient.indices.getSettings.mockRejectedValue(new Error('Fail')); + await expect( + clusterClientAdapter.getExistingIndices('foo*') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error getting existing indices matching pattern foo*: Fail"` + ); + }); +}); + +describe('setIndexToHidden', () => { + test('should call cluster with given index name', async () => { + await clusterClientAdapter.setIndexToHidden('foo-bar-000001'); + expect(clusterClient.indices.putSettings).toHaveBeenCalledWith({ + index: 'foo-bar-000001', + body: { + settings: { + 'index.hidden': true, + }, + }, + }); + }); + + test('should throw error when call cluster throws an error', async () => { + clusterClient.indices.putSettings.mockRejectedValue(new Error('Fail')); + await expect( + clusterClientAdapter.setIndexToHidden('foo-bar-000001') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error setting existing index foo-bar-000001 to hidden: Fail"` + ); + }); +}); + +describe('getExistingIndexAliases', () => { + test('should call cluster with given index pattern', async () => { + await clusterClientAdapter.getExistingIndexAliases('foo*'); + expect(clusterClient.indices.getAlias).toHaveBeenCalledWith( + { + index: 'foo*', + }, + { ignore: [404] } + ); + }); + + test('should return aliases when found', async () => { + const response = { + 'foo-bar-000001': { + aliases: { + 'foo-bar': { + is_write_index: true, + }, + }, + }, + }; + clusterClient.indices.getAlias.mockResolvedValue( + asApiResponse(response) + ); + await expect(clusterClientAdapter.getExistingIndexAliases('foo*')).resolves.toEqual(response); + }); + + test('should throw error when call cluster throws an error', async () => { + clusterClient.indices.getAlias.mockRejectedValue(new Error('Fail')); + await expect( + clusterClientAdapter.getExistingIndexAliases('foo*') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error getting existing index aliases matching pattern foo*: Fail"` + ); + }); +}); + +describe('setIndexAliasToHidden', () => { + test('should call cluster with given index name and aliases', async () => { + await clusterClientAdapter.setIndexAliasToHidden('foo-bar-000001', { + aliases: { + 'foo-bar': { + is_write_index: true, + }, + }, + }); + expect(clusterClient.indices.updateAliases).toHaveBeenCalledWith({ + body: { + actions: [ + { + add: { + index: 'foo-bar-000001', + alias: 'foo-bar', + is_hidden: true, + is_write_index: true, + }, + }, + ], + }, + }); + }); + + test('should update multiple aliases at once and preserve existing alias settings', async () => { + await clusterClientAdapter.setIndexAliasToHidden('foo-bar-000001', { + aliases: { + 'foo-bar': { + is_write_index: true, + }, + 'foo-b': { + index_routing: 'index', + routing: 'route', + }, + }, + }); + expect(clusterClient.indices.updateAliases).toHaveBeenCalledWith({ + body: { + actions: [ + { + add: { + index: 'foo-bar-000001', + alias: 'foo-bar', + is_hidden: true, + is_write_index: true, + }, + }, + { + add: { + index: 'foo-bar-000001', + alias: 'foo-b', + is_hidden: true, + index_routing: 'index', + routing: 'route', + }, + }, + ], + }, + }); + }); + + test('should throw error when call cluster throws an error', async () => { + clusterClient.indices.updateAliases.mockRejectedValue(new Error('Fail')); + await expect( + clusterClientAdapter.setIndexAliasToHidden('foo-bar-000001', { + aliases: { + 'foo-bar': { + is_write_index: true, + }, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error setting existing index aliases for index foo-bar-000001 to is_hidden: Fail"` + ); + }); +}); + describe('doesAliasExist', () => { test('should call cluster with proper arguments', async () => { await clusterClientAdapter.doesAliasExist('foo'); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 47bd29cf4b08a..7eb3328dddb6b 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -7,7 +7,7 @@ import { Subject } from 'rxjs'; import { bufferTime, filter as rxFilter, switchMap } from 'rxjs/operators'; -import { reject, isUndefined, isNumber } from 'lodash'; +import { reject, isUndefined, isNumber, pick } from 'lodash'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { Logger, ElasticsearchClient } from 'src/core/server'; import util from 'util'; @@ -163,17 +163,23 @@ export class ClusterClientAdapter { try { const esClient = await this.elasticsearchClientPromise; - const { body } = await esClient.indices.existsTemplate({ name }); - return body as boolean; + const { body: legacyResult } = await esClient.indices.existsTemplate({ name }); + const { body: indexTemplateResult } = await esClient.indices.existsIndexTemplate({ name }); + return (legacyResult as boolean) || (indexTemplateResult as boolean); } catch (err) { - throw new Error(`error checking existance of index template: ${err.message}`); + throw new Error(`error checking existence of index template: ${err.message}`); } } public async createIndexTemplate(name: string, template: Record): Promise { try { const esClient = await this.elasticsearchClientPromise; - await esClient.indices.putTemplate({ name, body: template, create: true }); + await esClient.indices.putIndexTemplate({ + name, + body: template, + // @ts-expect-error doesn't exist in @elastic/elasticsearch + create: true, + }); } catch (err) { // The error message doesn't have a type attribute we can look to guarantee it's due // to the template already existing (only long message) so we'll check ourselves to see @@ -188,6 +194,128 @@ export class ClusterClientAdapter { + try { + const esClient = await this.elasticsearchClientPromise; + const { body: templates } = await esClient.indices.getTemplate( + { name: indexTemplatePattern }, + { ignore: [404] } + ); + return templates; + } catch (err) { + throw new Error(`error getting existing legacy index templates: ${err.message}`); + } + } + + public async setLegacyIndexTemplateToHidden( + indexTemplateName: string, + currentIndexTemplate: estypes.IndicesTemplateMapping + ): Promise { + try { + const esClient = await this.elasticsearchClientPromise; + await esClient.indices.putTemplate({ + name: indexTemplateName, + body: { + ...currentIndexTemplate, + settings: { + ...currentIndexTemplate.settings, + 'index.hidden': true, + }, + }, + }); + } catch (err) { + throw new Error( + `error setting existing legacy index template ${indexTemplateName} to hidden: ${err.message}` + ); + } + } + + public async getExistingIndices( + indexPattern: string + ): Promise { + try { + const esClient = await this.elasticsearchClientPromise; + const { body: indexSettings } = await esClient.indices.getSettings( + { index: indexPattern }, + { ignore: [404] } + ); + return indexSettings; + } catch (err) { + throw new Error( + `error getting existing indices matching pattern ${indexPattern}: ${err.message}` + ); + } + } + + public async setIndexToHidden(indexName: string): Promise { + try { + const esClient = await this.elasticsearchClientPromise; + await esClient.indices.putSettings({ + index: indexName, + body: { + settings: { + 'index.hidden': true, + }, + }, + }); + } catch (err) { + throw new Error(`error setting existing index ${indexName} to hidden: ${err.message}`); + } + } + + public async getExistingIndexAliases( + indexPattern: string + ): Promise { + try { + const esClient = await this.elasticsearchClientPromise; + const { body: indexAliases } = await esClient.indices.getAlias( + { index: indexPattern }, + { ignore: [404] } + ); + return indexAliases; + } catch (err) { + throw new Error( + `error getting existing index aliases matching pattern ${indexPattern}: ${err.message}` + ); + } + } + + public async setIndexAliasToHidden( + indexName: string, + currentAliases: estypes.IndicesGetAliasIndexAliases + ): Promise { + try { + const esClient = await this.elasticsearchClientPromise; + await esClient.indices.updateAliases({ + body: { + actions: Object.keys(currentAliases.aliases).map((aliasName) => { + const existingAliasOptions = pick(currentAliases.aliases[aliasName], [ + 'is_write_index', + 'filter', + 'index_routing', + 'routing', + 'search_routing', + ]); + return { + add: { + ...existingAliasOptions, + index: indexName, + alias: aliasName, + is_hidden: true, + }, + }; + }), + }, + }); + } catch (err) { + throw new Error( + `error setting existing index aliases for index ${indexName} to is_hidden: ${err.message}` + ); + } + } + public async doesAliasExist(name: string): Promise { try { const esClient = await this.elasticsearchClientPromise; diff --git a/x-pack/plugins/event_log/server/es/context.test.ts b/x-pack/plugins/event_log/server/es/context.test.ts index 9589ab3c4aebf..0fe63ffb6217a 100644 --- a/x-pack/plugins/event_log/server/es/context.test.ts +++ b/x-pack/plugins/event_log/server/es/context.test.ts @@ -64,6 +64,7 @@ describe('createEsContext', () => { elasticsearchClientPromise: Promise.resolve(elasticsearchClient), }); elasticsearchClient.indices.existsTemplate.mockResolvedValue(asApiResponse(false)); + elasticsearchClient.indices.existsIndexTemplate.mockResolvedValue(asApiResponse(false)); elasticsearchClient.indices.existsAlias.mockResolvedValue(asApiResponse(false)); const doesAliasExist = await context.esAdapter.doesAliasExist(context.esNames.alias); expect(doesAliasExist).toBeFalsy(); diff --git a/x-pack/plugins/event_log/server/es/documents.test.ts b/x-pack/plugins/event_log/server/es/documents.test.ts index 121fcc42f37e4..6df5a1334f167 100644 --- a/x-pack/plugins/event_log/server/es/documents.test.ts +++ b/x-pack/plugins/event_log/server/es/documents.test.ts @@ -25,10 +25,11 @@ describe('getIndexTemplate()', () => { test('returns the correct details of the index template', () => { const indexTemplate = getIndexTemplate(esNames); expect(indexTemplate.index_patterns).toEqual([esNames.indexPatternWithVersion]); - expect(indexTemplate.settings.number_of_shards).toBeGreaterThanOrEqual(0); - expect(indexTemplate.settings.auto_expand_replicas).toBe('0-1'); - expect(indexTemplate.settings['index.lifecycle.name']).toBe(esNames.ilmPolicy); - expect(indexTemplate.settings['index.lifecycle.rollover_alias']).toBe(esNames.alias); - expect(indexTemplate.mappings).toMatchObject({}); + expect(indexTemplate.template.settings.number_of_shards).toBeGreaterThanOrEqual(0); + expect(indexTemplate.template.settings.auto_expand_replicas).toBe('0-1'); + expect(indexTemplate.template.settings['index.lifecycle.name']).toBe(esNames.ilmPolicy); + expect(indexTemplate.template.settings['index.lifecycle.rollover_alias']).toBe(esNames.alias); + expect(indexTemplate.template.settings['index.hidden']).toBe(true); + expect(indexTemplate.template.mappings).toMatchObject({}); }); }); diff --git a/x-pack/plugins/event_log/server/es/documents.ts b/x-pack/plugins/event_log/server/es/documents.ts index 8594fca603d18..c4ffda5f51ebe 100644 --- a/x-pack/plugins/event_log/server/es/documents.ts +++ b/x-pack/plugins/event_log/server/es/documents.ts @@ -12,13 +12,16 @@ import mappings from '../../generated/mappings.json'; export function getIndexTemplate(esNames: EsNames) { const indexTemplateBody = { index_patterns: [esNames.indexPatternWithVersion], - settings: { - number_of_shards: 1, - auto_expand_replicas: '0-1', - 'index.lifecycle.name': esNames.ilmPolicy, - 'index.lifecycle.rollover_alias': esNames.alias, + template: { + settings: { + number_of_shards: 1, + auto_expand_replicas: '0-1', + 'index.lifecycle.name': esNames.ilmPolicy, + 'index.lifecycle.rollover_alias': esNames.alias, + 'index.hidden': true, + }, + mappings, }, - mappings, }; return indexTemplateBody; diff --git a/x-pack/plugins/event_log/server/es/init.test.ts b/x-pack/plugins/event_log/server/es/init.test.ts index 074ceea1f4a24..bfdf0c17e5035 100644 --- a/x-pack/plugins/event_log/server/es/init.test.ts +++ b/x-pack/plugins/event_log/server/es/init.test.ts @@ -13,6 +13,327 @@ describe('initializeEs', () => { beforeEach(() => { esContext = contextMock.create(); + esContext.esAdapter.getExistingLegacyIndexTemplates.mockResolvedValue({}); + esContext.esAdapter.getExistingIndices.mockResolvedValue({}); + esContext.esAdapter.getExistingIndexAliases.mockResolvedValue({}); + }); + + test(`should update existing index templates if any exist and are not hidden`, async () => { + const testTemplate = { + order: 0, + index_patterns: ['foo-bar-*'], + settings: { + index: { + lifecycle: { + name: 'foo-bar-policy', + rollover_alias: 'foo-bar-1', + }, + number_of_shards: '1', + auto_expand_replicas: '0-1', + }, + }, + mappings: {}, + aliases: {}, + }; + esContext.esAdapter.getExistingLegacyIndexTemplates.mockResolvedValue({ + 'foo-bar-template': testTemplate, + }); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingLegacyIndexTemplates).toHaveBeenCalled(); + expect(esContext.esAdapter.setLegacyIndexTemplateToHidden).toHaveBeenCalledWith( + 'foo-bar-template', + testTemplate + ); + }); + + test(`should not update existing index templates if any exist and are already hidden`, async () => { + const testTemplate = { + order: 0, + index_patterns: ['foo-bar-*'], + settings: { + index: { + lifecycle: { + name: 'foo-bar-policy', + rollover_alias: 'foo-bar-1', + }, + hidden: 'true', + number_of_shards: '1', + auto_expand_replicas: '0-1', + }, + }, + mappings: {}, + aliases: {}, + }; + esContext.esAdapter.getExistingLegacyIndexTemplates.mockResolvedValue({ + 'foo-bar-template': testTemplate, + }); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingLegacyIndexTemplates).toHaveBeenCalled(); + expect(esContext.esAdapter.setLegacyIndexTemplateToHidden).not.toHaveBeenCalled(); + }); + + test(`should continue initialization if getting existing index templates throws an error`, async () => { + esContext.esAdapter.getExistingLegacyIndexTemplates.mockRejectedValue(new Error('Fail')); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingLegacyIndexTemplates).toHaveBeenCalled(); + expect(esContext.logger.error).toHaveBeenCalledWith( + `error getting existing index templates - Fail` + ); + expect(esContext.esAdapter.setLegacyIndexTemplateToHidden).not.toHaveBeenCalled(); + expect(esContext.esAdapter.doesIlmPolicyExist).toHaveBeenCalled(); + }); + + test(`should continue initialization if updating existing index templates throws an error`, async () => { + const testTemplate = { + order: 0, + index_patterns: ['foo-bar-*'], + settings: { + index: { + lifecycle: { + name: 'foo-bar-policy', + rollover_alias: 'foo-bar-1', + }, + number_of_shards: '1', + auto_expand_replicas: '0-1', + }, + }, + mappings: {}, + aliases: {}, + }; + esContext.esAdapter.getExistingLegacyIndexTemplates.mockResolvedValue({ + 'foo-bar-template': testTemplate, + 'another-test-template': testTemplate, + }); + esContext.esAdapter.setLegacyIndexTemplateToHidden.mockRejectedValueOnce(new Error('Fail')); + esContext.esAdapter.setLegacyIndexTemplateToHidden.mockResolvedValueOnce(); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingLegacyIndexTemplates).toHaveBeenCalled(); + expect(esContext.esAdapter.setLegacyIndexTemplateToHidden).toHaveBeenCalledWith( + 'foo-bar-template', + testTemplate + ); + expect(esContext.esAdapter.setLegacyIndexTemplateToHidden).toHaveBeenCalledWith( + 'another-test-template', + testTemplate + ); + expect(esContext.logger.error).toHaveBeenCalledTimes(1); + expect(esContext.logger.error).toHaveBeenCalledWith( + `error setting existing \"foo-bar-template\" index template to hidden - Fail` + ); + expect(esContext.esAdapter.doesIlmPolicyExist).toHaveBeenCalled(); + }); + + test(`should update existing index settings if any exist and are not hidden`, async () => { + const testSettings = { + settings: { + index: { + lifecycle: { + name: 'foo-bar-policy', + rollover_alias: 'foo-bar-1', + }, + routing: { + allocation: { + include: { + _tier_preference: 'data_content', + }, + }, + }, + number_of_shards: '1', + auto_expand_replicas: '0-1', + provided_name: '.kibana-event-log-7.15.0-000001', + creation_date: '1630439186791', + number_of_replicas: '0', + uuid: 'Ure4d9edQbCMtcmyy0ObrA', + version: { + created: '7150099', + }, + }, + }, + }; + esContext.esAdapter.getExistingIndices.mockResolvedValue({ + 'foo-bar-000001': testSettings, + }); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndices).toHaveBeenCalled(); + expect(esContext.esAdapter.setIndexToHidden).toHaveBeenCalledWith('foo-bar-000001'); + }); + + test(`should not update existing index settings if any exist and are already hidden`, async () => { + const testSettings = { + settings: { + index: { + lifecycle: { + name: 'foo-bar-policy', + rollover_alias: 'foo-bar-1', + }, + routing: { + allocation: { + include: { + _tier_preference: 'data_content', + }, + }, + }, + hidden: 'true', + number_of_shards: '1', + auto_expand_replicas: '0-1', + provided_name: '.kibana-event-log-7.15.0-000001', + creation_date: '1630439186791', + number_of_replicas: '0', + uuid: 'Ure4d9edQbCMtcmyy0ObrA', + version: { + created: '7150099', + }, + }, + }, + }; + esContext.esAdapter.getExistingIndices.mockResolvedValue({ + 'foo-bar-000001': testSettings, + }); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndices).toHaveBeenCalled(); + expect(esContext.esAdapter.setIndexToHidden).not.toHaveBeenCalled(); + }); + + test(`should continue initialization if getting existing index settings throws an error`, async () => { + esContext.esAdapter.getExistingIndices.mockRejectedValue(new Error('Fail')); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndices).toHaveBeenCalled(); + expect(esContext.logger.error).toHaveBeenCalledWith(`error getting existing indices - Fail`); + expect(esContext.esAdapter.setIndexToHidden).not.toHaveBeenCalled(); + expect(esContext.esAdapter.doesIlmPolicyExist).toHaveBeenCalled(); + }); + + test(`should continue initialization if updating existing index settings throws an error`, async () => { + const testSettings = { + settings: { + index: { + lifecycle: { + name: 'foo-bar-policy', + rollover_alias: 'foo-bar-1', + }, + routing: { + allocation: { + include: { + _tier_preference: 'data_content', + }, + }, + }, + number_of_shards: '1', + auto_expand_replicas: '0-1', + provided_name: '.kibana-event-log-7.15.0-000001', + creation_date: '1630439186791', + number_of_replicas: '0', + uuid: 'Ure4d9edQbCMtcmyy0ObrA', + version: { + created: '7150099', + }, + }, + }, + }; + esContext.esAdapter.getExistingIndices.mockResolvedValue({ + 'foo-bar-000001': testSettings, + 'foo-bar-000002': testSettings, + }); + + esContext.esAdapter.setIndexToHidden.mockRejectedValueOnce(new Error('Fail')); + esContext.esAdapter.setIndexToHidden.mockResolvedValueOnce(); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndices).toHaveBeenCalled(); + expect(esContext.logger.error).toHaveBeenCalledTimes(1); + expect(esContext.logger.error).toHaveBeenCalledWith( + `error setting existing \"foo-bar-000001\" index to hidden - Fail` + ); + expect(esContext.esAdapter.doesIlmPolicyExist).toHaveBeenCalled(); + }); + + test(`should update existing index aliases if any exist and are not hidden`, async () => { + const testAliases = { + aliases: { + 'foo-bar': { + is_write_index: true, + }, + }, + }; + esContext.esAdapter.getExistingIndexAliases.mockResolvedValue({ + 'foo-bar-000001': testAliases, + }); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndexAliases).toHaveBeenCalled(); + expect(esContext.esAdapter.setIndexAliasToHidden).toHaveBeenCalledWith( + 'foo-bar-000001', + testAliases + ); + }); + + test(`should not update existing index aliases if any exist and are already hidden`, async () => { + const testAliases = { + aliases: { + 'foo-bar': { + is_write_index: true, + is_hidden: true, + }, + }, + }; + esContext.esAdapter.getExistingIndexAliases.mockResolvedValue({ + 'foo-bar-000001': testAliases, + }); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndexAliases).toHaveBeenCalled(); + expect(esContext.esAdapter.setIndexAliasToHidden).not.toHaveBeenCalled(); + }); + + test(`should continue initialization if getting existing index aliases throws an error`, async () => { + esContext.esAdapter.getExistingIndexAliases.mockRejectedValue(new Error('Fail')); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndexAliases).toHaveBeenCalled(); + expect(esContext.logger.error).toHaveBeenCalledWith( + `error getting existing index aliases - Fail` + ); + expect(esContext.esAdapter.setIndexAliasToHidden).not.toHaveBeenCalled(); + expect(esContext.esAdapter.doesIlmPolicyExist).toHaveBeenCalled(); + }); + + test(`should continue initialization if updating existing index aliases throws an error`, async () => { + const testAliases = { + aliases: { + 'foo-bar': { + is_write_index: true, + }, + }, + }; + esContext.esAdapter.getExistingIndexAliases.mockResolvedValue({ + 'foo-bar-000001': testAliases, + 'foo-bar-000002': testAliases, + }); + esContext.esAdapter.setIndexAliasToHidden.mockRejectedValueOnce(new Error('Fail')); + esContext.esAdapter.setIndexAliasToHidden.mockResolvedValueOnce(); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndexAliases).toHaveBeenCalled(); + expect(esContext.esAdapter.setIndexAliasToHidden).toHaveBeenCalledWith( + 'foo-bar-000001', + testAliases + ); + expect(esContext.esAdapter.setIndexAliasToHidden).toHaveBeenCalledWith( + 'foo-bar-000002', + testAliases + ); + expect(esContext.logger.error).toHaveBeenCalledTimes(1); + expect(esContext.logger.error).toHaveBeenCalledWith( + `error setting existing \"foo-bar-000001\" index aliases - Fail` + ); + expect(esContext.esAdapter.doesIlmPolicyExist).toHaveBeenCalled(); }); test(`should create ILM policy if it doesn't exist`, async () => { diff --git a/x-pack/plugins/event_log/server/es/init.ts b/x-pack/plugins/event_log/server/es/init.ts index 484dfdc95a72c..e2769e39b28ff 100644 --- a/x-pack/plugins/event_log/server/es/init.ts +++ b/x-pack/plugins/event_log/server/es/init.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { IndicesAlias, IndicesIndexStatePrefixedSettings } from '@elastic/elasticsearch/api/types'; +import { estypes } from '@elastic/elasticsearch'; import { getIlmPolicy, getIndexTemplate } from './documents'; import { EsContext } from './context'; @@ -25,6 +27,7 @@ export async function initializeEs(esContext: EsContext): Promise { async function initializeEsResources(esContext: EsContext) { const steps = new EsInitializationSteps(esContext); + await steps.setExistingAssetsToHidden(); await steps.createIlmPolicyIfNotExists(); await steps.createIndexTemplateIfNotExists(); await steps.createInitialIndexIfNotExists(); @@ -35,6 +38,122 @@ class EsInitializationSteps { this.esContext = esContext; } + async setExistingIndexTemplatesToHidden() { + let indexTemplates: estypes.IndicesGetTemplateResponse = {}; + try { + // look up existing index templates and update index.hidden to true if that + // setting is currently false or undefined + + // since we are updating to the new index template API and converting new event log + // indices to hidden in the same PR, we only need to use the legacy template API to + // look for and update existing event log indices. + indexTemplates = await this.esContext.esAdapter.getExistingLegacyIndexTemplates( + this.esContext.esNames.indexPattern + ); + } catch (err) { + // errors when trying to get existing index templates + // should not block the rest of initialization, log the error and move on + this.esContext.logger.error(`error getting existing index templates - ${err.message}`); + } + + Object.keys(indexTemplates).forEach(async (indexTemplateName: string) => { + try { + const hidden: string | boolean = indexTemplates[indexTemplateName]?.settings?.index?.hidden; + // Check to see if this index template is hidden + if (hidden !== true && hidden !== 'true') { + this.esContext.logger.debug( + `setting existing "${indexTemplateName}" index template to hidden.` + ); + + await this.esContext.esAdapter.setLegacyIndexTemplateToHidden( + indexTemplateName, + indexTemplates[indexTemplateName] + ); + } + } catch (err) { + // errors when trying to update existing index templates to hidden + // should not block the rest of initialization, log the error and move on + this.esContext.logger.error( + `error setting existing "${indexTemplateName}" index template to hidden - ${err.message}` + ); + } + }); + } + + async setExistingIndicesToHidden() { + let indices: estypes.IndicesGetSettingsResponse = {}; + try { + // look up existing indices and update index.hidden to true if that + // setting is currently false or undefined + indices = await this.esContext.esAdapter.getExistingIndices( + this.esContext.esNames.indexPattern + ); + } catch (err) { + // errors when trying to get existing indices + // should not block the rest of initialization, log the error and move on + this.esContext.logger.error(`error getting existing indices - ${err.message}`); + } + + Object.keys(indices).forEach(async (indexName: string) => { + try { + const hidden: string | boolean | undefined = (indices[indexName] + ?.settings as IndicesIndexStatePrefixedSettings)?.index?.hidden; + + // Check to see if this index template is hidden + if (hidden !== true && hidden !== 'true') { + this.esContext.logger.debug(`setting existing ${indexName} index to hidden.`); + await this.esContext.esAdapter.setIndexToHidden(indexName); + } + } catch (err) { + // errors when trying to update existing indices to hidden + // should not block the rest of initialization, log the error and move on + this.esContext.logger.error( + `error setting existing "${indexName}" index to hidden - ${err.message}` + ); + } + }); + } + + async setExistingIndexAliasesToHidden() { + let indexAliases: estypes.IndicesGetAliasResponse = {}; + try { + // Look up existing index aliases and update index.is_hidden to true if that + // setting is currently false or undefined + indexAliases = await this.esContext.esAdapter.getExistingIndexAliases( + this.esContext.esNames.indexPattern + ); + } catch (err) { + // errors when trying to get existing index aliases + // should not block the rest of initialization, log the error and move on + this.esContext.logger.error(`error getting existing index aliases - ${err.message}`); + } + Object.keys(indexAliases).forEach(async (indexName: string) => { + try { + const aliases = indexAliases[indexName]?.aliases; + const hasNotHiddenAliases: boolean = Object.keys(aliases).some((alias: string) => { + return (aliases[alias] as IndicesAlias)?.is_hidden !== true; + }); + + if (hasNotHiddenAliases) { + this.esContext.logger.debug(`setting existing "${indexName}" index aliases to hidden.`); + await this.esContext.esAdapter.setIndexAliasToHidden(indexName, indexAliases[indexName]); + } + } catch (err) { + // errors when trying to set existing index aliases to is_hidden + // should not block the rest of initialization, log the error and move on + this.esContext.logger.error( + `error setting existing "${indexName}" index aliases - ${err.message}` + ); + } + }); + } + + async setExistingAssetsToHidden(): Promise { + await this.setExistingIndexTemplatesToHidden(); + await this.setExistingIndicesToHidden(); + await this.setExistingIndexAliasesToHidden(); + } + async createIlmPolicyIfNotExists(): Promise { const exists = await this.esContext.esAdapter.doesIlmPolicyExist( this.esContext.esNames.ilmPolicy @@ -67,6 +186,7 @@ class EsInitializationSteps { aliases: { [this.esContext.esNames.alias]: { is_write_index: true, + is_hidden: true, }, }, }); diff --git a/x-pack/plugins/features/kibana.json b/x-pack/plugins/features/kibana.json index 867f9ced7ed88..550b3899061d4 100644 --- a/x-pack/plugins/features/kibana.json +++ b/x-pack/plugins/features/kibana.json @@ -7,7 +7,6 @@ "version": "8.0.0", "kibanaVersion": "kibana", "requiredPlugins": ["licensing"], - "optionalPlugins": ["visTypeTimelion"], "configPath": ["xpack", "features"], "server": true, "ui": true, diff --git a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap index 954701f0087f4..85eb1f5b71e71 100644 --- a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap @@ -551,7 +551,6 @@ Array [ "index-pattern", "search", "visualization", - "timelion-sheet", "canvas-workpad", "lens", "map", @@ -584,7 +583,6 @@ Array [ "index-pattern", "search", "visualization", - "timelion-sheet", "canvas-workpad", "lens", "map", @@ -857,54 +855,6 @@ Array [ ] `; -exports[`buildOSSFeatures with a basic license returns the timelion feature augmented with appropriate sub feature privileges 1`] = ` -Array [ - Object { - "privilege": Object { - "app": Array [ - "timelion", - "kibana", - ], - "catalogue": Array [ - "timelion", - ], - "savedObject": Object { - "all": Array [ - "timelion-sheet", - ], - "read": Array [ - "index-pattern", - ], - }, - "ui": Array [ - "save", - ], - }, - "privilegeId": "all", - }, - Object { - "privilege": Object { - "app": Array [ - "timelion", - "kibana", - ], - "catalogue": Array [ - "timelion", - ], - "savedObject": Object { - "all": Array [], - "read": Array [ - "index-pattern", - "timelion-sheet", - ], - }, - "ui": Array [], - }, - "privilegeId": "read", - }, -] -`; - exports[`buildOSSFeatures with a basic license returns the visualize feature augmented with appropriate sub feature privileges 1`] = ` Array [ Object { @@ -1081,7 +1031,6 @@ Array [ "index-pattern", "search", "visualization", - "timelion-sheet", "canvas-workpad", "lens", "map", @@ -1114,7 +1063,6 @@ Array [ "index-pattern", "search", "visualization", - "timelion-sheet", "canvas-workpad", "lens", "map", @@ -1387,54 +1335,6 @@ Array [ ] `; -exports[`buildOSSFeatures with a enterprise license returns the timelion feature augmented with appropriate sub feature privileges 1`] = ` -Array [ - Object { - "privilege": Object { - "app": Array [ - "timelion", - "kibana", - ], - "catalogue": Array [ - "timelion", - ], - "savedObject": Object { - "all": Array [ - "timelion-sheet", - ], - "read": Array [ - "index-pattern", - ], - }, - "ui": Array [ - "save", - ], - }, - "privilegeId": "all", - }, - Object { - "privilege": Object { - "app": Array [ - "timelion", - "kibana", - ], - "catalogue": Array [ - "timelion", - ], - "savedObject": Object { - "all": Array [], - "read": Array [ - "index-pattern", - "timelion-sheet", - ], - }, - "ui": Array [], - }, - "privilegeId": "read", - }, -] -`; - exports[`buildOSSFeatures with a enterprise license returns the visualize feature augmented with appropriate sub feature privileges 1`] = ` Array [ Object { diff --git a/x-pack/plugins/features/server/oss_features.test.ts b/x-pack/plugins/features/server/oss_features.test.ts index 39bb12d90ea1c..7a1c5951192ec 100644 --- a/x-pack/plugins/features/server/oss_features.test.ts +++ b/x-pack/plugins/features/server/oss_features.test.ts @@ -11,52 +11,10 @@ import { KibanaFeature } from '.'; import { LicenseType, LICENSE_TYPE } from '../../licensing/server'; describe('buildOSSFeatures', () => { - it('returns features including timelion', () => { - expect( - buildOSSFeatures({ - savedObjectTypes: ['foo', 'bar'], - includeTimelion: true, - includeReporting: false, - }).map((f) => f.id) - ).toMatchInlineSnapshot(` -Array [ - "discover", - "visualize", - "dashboard", - "dev_tools", - "advancedSettings", - "indexPatterns", - "savedObjectsManagement", - "timelion", -] -`); - }); - - it('returns features excluding timelion', () => { - expect( - buildOSSFeatures({ - savedObjectTypes: ['foo', 'bar'], - includeTimelion: false, - includeReporting: false, - }).map((f) => f.id) - ).toMatchInlineSnapshot(` -Array [ - "discover", - "visualize", - "dashboard", - "dev_tools", - "advancedSettings", - "indexPatterns", - "savedObjectsManagement", -] -`); - }); - it('returns features including reporting subfeatures', () => { expect( buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], - includeTimelion: false, includeReporting: true, }).map(({ id, subFeatures }) => ({ id, subFeatures })) ).toMatchSnapshot(); @@ -66,7 +24,6 @@ Array [ expect( buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], - includeTimelion: false, includeReporting: false, }).map(({ id, subFeatures }) => ({ id, subFeatures })) ).toMatchSnapshot(); @@ -74,7 +31,6 @@ Array [ const features = buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], - includeTimelion: true, includeReporting: false, }); features.forEach((featureConfig) => { diff --git a/x-pack/plugins/features/server/oss_features.ts b/x-pack/plugins/features/server/oss_features.ts index 14265948c2dee..7c8432b8d9ec7 100644 --- a/x-pack/plugins/features/server/oss_features.ts +++ b/x-pack/plugins/features/server/oss_features.ts @@ -11,13 +11,11 @@ import type { KibanaFeatureConfig, SubFeatureConfig } from '../common'; export interface BuildOSSFeaturesParams { savedObjectTypes: string[]; - includeTimelion: boolean; includeReporting: boolean; } export const buildOSSFeatures = ({ savedObjectTypes, - includeTimelion, includeReporting, }: BuildOSSFeaturesParams): KibanaFeatureConfig[] => { return [ @@ -203,7 +201,6 @@ export const buildOSSFeatures = ({ 'index-pattern', 'search', 'visualization', - 'timelion-sheet', 'canvas-workpad', 'lens', 'map', @@ -221,7 +218,6 @@ export const buildOSSFeatures = ({ 'index-pattern', 'search', 'visualization', - 'timelion-sheet', 'canvas-workpad', 'lens', 'map', @@ -450,39 +446,9 @@ export const buildOSSFeatures = ({ }, }, }, - ...(includeTimelion ? [timelionFeature] : []), ] as KibanaFeatureConfig[]; }; -const timelionFeature: KibanaFeatureConfig = { - id: 'timelion', - name: 'Timelion', - order: 350, - category: DEFAULT_APP_CATEGORIES.kibana, - app: ['timelion', 'kibana'], - catalogue: ['timelion'], - privileges: { - all: { - app: ['timelion', 'kibana'], - catalogue: ['timelion'], - savedObject: { - all: ['timelion-sheet'], - read: ['index-pattern'], - }, - ui: ['save'], - }, - read: { - app: ['timelion', 'kibana'], - catalogue: ['timelion'], - savedObject: { - all: [], - read: ['index-pattern', 'timelion-sheet'], - }, - ui: [], - }, - }, -}; - const reportingPrivilegeGroupName = i18n.translate( 'xpack.features.ossFeatures.reporting.reportingTitle', { diff --git a/x-pack/plugins/features/server/plugin.test.ts b/x-pack/plugins/features/server/plugin.test.ts index ba809187a549e..e5940bc73ae97 100644 --- a/x-pack/plugins/features/server/plugin.test.ts +++ b/x-pack/plugins/features/server/plugin.test.ts @@ -46,36 +46,7 @@ describe('Features Plugin', () => { it('returns OSS + registered kibana features', async () => { const plugin = new FeaturesPlugin(initContext); - const { registerKibanaFeature } = await plugin.setup(coreSetup, {}); - registerKibanaFeature({ - id: 'baz', - name: 'baz', - app: [], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }); - - const { getKibanaFeatures } = plugin.start(coreStart); - - expect(getKibanaFeatures().map((f) => f.id)).toMatchInlineSnapshot(` - Array [ - "baz", - "discover", - "visualize", - "dashboard", - "dev_tools", - "advancedSettings", - "indexPatterns", - "savedObjectsManagement", - ] - `); - }); - - it('returns OSS + registered kibana features with timelion when available', async () => { - const plugin = new FeaturesPlugin(initContext); - const { registerKibanaFeature: registerFeature } = await plugin.setup(coreSetup, { - visTypeTimelion: { uiEnabled: true }, - }); + const { registerKibanaFeature: registerFeature } = await plugin.setup(coreSetup); registerFeature({ id: 'baz', name: 'baz', @@ -96,7 +67,6 @@ describe('Features Plugin', () => { "advancedSettings", "indexPatterns", "savedObjectsManagement", - "timelion", ] `); }); @@ -105,7 +75,7 @@ describe('Features Plugin', () => { typeRegistry.isHidden.mockReturnValueOnce(true); typeRegistry.isHidden.mockReturnValueOnce(false); const plugin = new FeaturesPlugin(initContext); - await plugin.setup(coreSetup, {}); + await plugin.setup(coreSetup); const { getKibanaFeatures } = plugin.start(coreStart); const soTypes = @@ -120,7 +90,7 @@ describe('Features Plugin', () => { it('returns registered elasticsearch features', async () => { const plugin = new FeaturesPlugin(initContext); - const { registerElasticsearchFeature } = await plugin.setup(coreSetup, {}); + const { registerElasticsearchFeature } = await plugin.setup(coreSetup); registerElasticsearchFeature({ id: 'baz', privileges: [ @@ -142,7 +112,7 @@ describe('Features Plugin', () => { it('registers a capabilities provider', async () => { const plugin = new FeaturesPlugin(initContext); - await plugin.setup(coreSetup, {}); + await plugin.setup(coreSetup); expect(coreSetup.capabilities.registerProvider).toHaveBeenCalledTimes(1); expect(coreSetup.capabilities.registerProvider).toHaveBeenCalledWith(expect.any(Function)); diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts index b1f540031f6dc..c1fbc4b8a5edb 100644 --- a/x-pack/plugins/features/server/plugin.ts +++ b/x-pack/plugins/features/server/plugin.ts @@ -81,10 +81,6 @@ export interface PluginStartContract { getKibanaFeatures(): KibanaFeature[]; } -interface TimelionSetupContract { - uiEnabled: boolean; -} - /** * Represents Features Plugin instance that will be managed by the Kibana plugin system. */ @@ -93,19 +89,13 @@ export class FeaturesPlugin Plugin, RecursiveReadonly> { private readonly logger: Logger; private readonly featureRegistry: FeatureRegistry = new FeatureRegistry(); - private isTimelionEnabled: boolean = false; private isReportingEnabled: boolean = false; constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); } - public setup( - core: CoreSetup, - { visTypeTimelion }: { visTypeTimelion?: TimelionSetupContract } - ): RecursiveReadonly { - this.isTimelionEnabled = visTypeTimelion !== undefined && visTypeTimelion.uiEnabled; - + public setup(core: CoreSetup): RecursiveReadonly { defineRoutes({ router: core.http.createRouter(), featureRegistry: this.featureRegistry, @@ -160,14 +150,8 @@ export class FeaturesPlugin new Set([...savedObjectVisibleTypes, ...savedObjectImportableAndExportableHiddenTypes]) ); - this.logger.debug( - `Registering OSS features with SO types: ${savedObjectTypes.join(', ')}. "includeTimelion": ${ - this.isTimelionEnabled - }.` - ); const features = buildOSSFeatures({ savedObjectTypes, - includeTimelion: this.isTimelionEnabled, includeReporting: this.isReportingEnabled, }); diff --git a/x-pack/plugins/fleet/server/index.test.ts b/x-pack/plugins/fleet/server/index.test.ts new file mode 100644 index 0000000000000..924fecc311073 --- /dev/null +++ b/x-pack/plugins/fleet/server/index.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; + +import { config } from '.'; + +const applyConfigDeprecations = (settings: Record = {}) => { + if (!config.deprecations) { + throw new Error('Config is not valid no deprecations'); + } + const deprecations = config.deprecations(configDeprecationFactory); + const deprecationMessages: string[] = []; + const migrated = applyDeprecations( + settings, + deprecations.map((deprecation) => ({ + deprecation, + path: '', + })), + () => ({ message }) => deprecationMessages.push(message) + ); + return { + messages: deprecationMessages, + migrated: migrated.config, + }; +}; + +describe('Config depreciation test', () => { + it('should migrate old xpack.ingestManager.fleet settings to xpack.fleet.agents', () => { + const { migrated } = applyConfigDeprecations({ + xpack: { + ingestManager: { + fleet: { enabled: true, elasticsearch: { host: 'http://testes.fr:9200' } }, + }, + }, + }); + + expect(migrated).toMatchInlineSnapshot(` + Object { + "xpack": Object { + "fleet": Object { + "agents": Object { + "elasticsearch": Object { + "hosts": Array [ + "http://testes.fr:9200", + ], + }, + "enabled": true, + }, + }, + }, + } + `); + }); + + it('should support mixing xpack.ingestManager config and xpack.fleet config', () => { + const { migrated } = applyConfigDeprecations({ + xpack: { + ingestManager: { registryUrl: 'http://registrytest.fr' }, + fleet: { registryProxyUrl: 'http://registryProxy.fr' }, + }, + }); + + expect(migrated).toMatchInlineSnapshot(` + Object { + "xpack": Object { + "fleet": Object { + "registryProxyUrl": "http://registryProxy.fr", + "registryUrl": "http://registrytest.fr", + }, + }, + } + `); + }); +}); diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 8841c897fcb2a..21cdf659f2f5a 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -38,9 +38,37 @@ export const config: PluginConfigDescriptor = { epm: true, agents: true, }, - deprecations: ({ renameFromRoot, unused }) => [ - renameFromRoot('xpack.ingestManager', 'xpack.fleet'), - renameFromRoot('xpack.fleet.fleet', 'xpack.fleet.agents'), + deprecations: ({ renameFromRoot, unused, unusedFromRoot }) => [ + // Fleet plugin was named ingestManager before + renameFromRoot('xpack.ingestManager.enabled', 'xpack.fleet.enabled'), + renameFromRoot('xpack.ingestManager.registryUrl', 'xpack.fleet.registryUrl'), + renameFromRoot('xpack.ingestManager.registryProxyUrl', 'xpack.fleet.registryProxyUrl'), + renameFromRoot('xpack.ingestManager.fleet', 'xpack.ingestManager.agents'), + renameFromRoot('xpack.ingestManager.agents.enabled', 'xpack.fleet.agents.enabled'), + renameFromRoot('xpack.ingestManager.agents.elasticsearch', 'xpack.fleet.agents.elasticsearch'), + renameFromRoot( + 'xpack.ingestManager.agents.tlsCheckDisabled', + 'xpack.fleet.agents.tlsCheckDisabled' + ), + renameFromRoot( + 'xpack.ingestManager.agents.pollingRequestTimeout', + 'xpack.fleet.agents.pollingRequestTimeout' + ), + renameFromRoot( + 'xpack.ingestManager.agents.maxConcurrentConnections', + 'xpack.fleet.agents.maxConcurrentConnections' + ), + renameFromRoot('xpack.ingestManager.agents.kibana', 'xpack.fleet.agents.kibana'), + renameFromRoot( + 'xpack.ingestManager.agents.agentPolicyRolloutRateLimitIntervalMs', + 'xpack.fleet.agents.agentPolicyRolloutRateLimitIntervalMs' + ), + renameFromRoot( + 'xpack.ingestManager.agents.agentPolicyRolloutRateLimitRequestPerInterval', + 'xpack.fleet.agents.agentPolicyRolloutRateLimitRequestPerInterval' + ), + unusedFromRoot('xpack.ingestManager'), + // Unused settings before Fleet server exists unused('agents.kibana'), unused('agents.maxConcurrentConnections'), unused('agents.agentPolicyRolloutRateLimitIntervalMs'), @@ -48,6 +76,7 @@ export const config: PluginConfigDescriptor = { unused('agents.pollingRequestTimeout'), unused('agents.tlsCheckDisabled'), unused('agents.fleetServerEnabled'), + // Renaming elasticsearch.host => elasticsearch.hosts (fullConfig, fromPath, addDeprecation) => { const oldValue = fullConfig?.xpack?.fleet?.agents?.elasticsearch?.host; if (oldValue) { diff --git a/x-pack/plugins/graph/kibana.json b/x-pack/plugins/graph/kibana.json index cc732e67995ba..410a5e2c160d6 100644 --- a/x-pack/plugins/graph/kibana.json +++ b/x-pack/plugins/graph/kibana.json @@ -4,12 +4,29 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["licensing", "data", "navigation", "savedObjects", "kibanaLegacy"], - "optionalPlugins": ["home", "features"], - "configPath": ["xpack", "graph"], - "requiredBundles": ["kibanaUtils", "kibanaReact", "home"], + "requiredPlugins": [ + "licensing", + "data", + "navigation", + "savedObjects", + "kibanaLegacy" + ], + "optionalPlugins": [ + "home", + "features", + "spaces" + ], + "configPath": [ + "xpack", + "graph" + ], + "requiredBundles": [ + "kibanaUtils", + "kibanaReact", + "home" + ], "owner": { "name": "Data Discovery", "githubTeam": "kibana-data-discovery" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts index 7461a7b5fc172..fc6c6170509d9 100644 --- a/x-pack/plugins/graph/public/application.ts +++ b/x-pack/plugins/graph/public/application.ts @@ -31,6 +31,7 @@ import './index.scss'; import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public'; import { GraphSavePolicy } from './types'; import { graphRouter } from './router'; +import { SpacesApi } from '../../spaces/public'; /** * These are dependencies of the Graph app besides the base dependencies @@ -63,6 +64,7 @@ export interface GraphDependencies { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; uiSettings: IUiSettingsClient; history: ScopedHistory; + spaces?: SpacesApi; } export type GraphServices = Omit; diff --git a/x-pack/plugins/graph/public/apps/workspace_route.tsx b/x-pack/plugins/graph/public/apps/workspace_route.tsx index f4158238c33c6..55f481bf504f1 100644 --- a/x-pack/plugins/graph/public/apps/workspace_route.tsx +++ b/x-pack/plugins/graph/public/apps/workspace_route.tsx @@ -8,7 +8,7 @@ import React, { useMemo, useRef, useState } from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { Provider } from 'react-redux'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { showSaveModal } from '../../../../../src/plugins/saved_objects/public'; import { Workspace } from '../types'; @@ -40,6 +40,7 @@ export const WorkspaceRoute = ({ getBasePath, addBasePath, setHeaderActionMenu, + spaces, indexPatterns: getIndexPatternProvider, }, }: WorkspaceRouteProps) => { @@ -56,7 +57,6 @@ export const WorkspaceRoute = ({ */ const [renderCounter, setRenderCounter] = useState(0); const history = useHistory(); - const urlQuery = new URLSearchParams(useLocation().search).get('query'); const indexPatternProvider = useMemo( () => createCachedIndexPatternProvider(getIndexPatternProvider.get), @@ -114,22 +114,27 @@ export const WorkspaceRoute = ({ }) ); - const { savedWorkspace, indexPatterns } = useWorkspaceLoader({ + const loaded = useWorkspaceLoader({ workspaceRef, store, savedObjectsClient, - toastNotifications, + spaces, + coreStart, }); - if (!savedWorkspace || !indexPatterns) { + if (!loaded) { return null; } + const { savedWorkspace, indexPatterns, sharingSavedObjectProps } = loaded; + return ( diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx new file mode 100644 index 0000000000000..c535c9e32d335 --- /dev/null +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { WorkspaceLayoutComponent } from '.'; +import { coreMock } from 'src/core/public/mocks'; +import { spacesPluginMock } from '../../../../spaces/public/mocks'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../src/plugins/navigation/public'; +import { GraphSavePolicy, GraphWorkspaceSavedObject, IndexPatternProvider } from '../../types'; +import { OverlayStart, Capabilities } from 'kibana/public'; +import { SharingSavedObjectProps } from '../../helpers/use_workspace_loader'; + +jest.mock('react-router-dom', () => { + const useLocation = () => ({ + search: '?query={}', + }); + return { + useLocation, + }; +}); + +describe('workspace_layout', () => { + const defaultProps = { + renderCounter: 1, + loading: false, + savedWorkspace: { id: 'test' } as GraphWorkspaceSavedObject, + hasFields: true, + overlays: {} as OverlayStart, + workspaceInitialized: true, + indexPatterns: [], + indexPatternProvider: {} as IndexPatternProvider, + capabilities: {} as Capabilities, + coreStart: coreMock.createStart(), + graphSavePolicy: 'configAndDataWithConsent' as GraphSavePolicy, + navigation: {} as NavigationStart, + canEditDrillDownUrls: true, + setHeaderActionMenu: jest.fn(), + sharingSavedObjectProps: { + outcome: 'exactMatch', + aliasTargetId: '', + } as SharingSavedObjectProps, + spaces: spacesPluginMock.createStartContract(), + }; + it('should display conflict notification if outcome is conflict', () => { + shallow( + + ); + expect(defaultProps.spaces.ui.components.getLegacyUrlConflict).toHaveBeenCalledWith({ + currentObjectId: 'test', + objectNoun: 'Graph', + otherObjectId: 'conflictId', + otherObjectPath: '#/workspace/conflictId?query={}', + }); + }); +}); diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx index 70e5b82ec6526..5426ae9228518 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx @@ -9,6 +9,7 @@ import React, { Fragment, memo, useCallback, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; import { connect } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { SearchBar } from '../search_bar'; import { GraphState, @@ -33,6 +34,8 @@ import { GraphServices } from '../../application'; import { ControlPanel } from '../control_panel'; import { GraphVisualization } from '../graph_visualization'; import { colorChoices } from '../../helpers/style_choices'; +import { SharingSavedObjectProps } from '../../helpers/use_workspace_loader'; +import { getEditUrl } from '../../services/url'; /** * Each component, which depends on `worksapce` @@ -51,6 +54,7 @@ type WorkspaceLayoutProps = Pick< | 'coreStart' | 'canEditDrillDownUrls' | 'overlays' + | 'spaces' > & { renderCounter: number; workspace?: Workspace; @@ -58,7 +62,7 @@ type WorkspaceLayoutProps = Pick< indexPatterns: IndexPatternSavedObject[]; savedWorkspace: GraphWorkspaceSavedObject; indexPatternProvider: IndexPatternProvider; - urlQuery: string | null; + sharingSavedObjectProps?: SharingSavedObjectProps; }; interface WorkspaceLayoutStateProps { @@ -66,7 +70,7 @@ interface WorkspaceLayoutStateProps { hasFields: boolean; } -const WorkspaceLayoutComponent = ({ +export const WorkspaceLayoutComponent = ({ renderCounter, workspace, loading, @@ -81,8 +85,9 @@ const WorkspaceLayoutComponent = ({ graphSavePolicy, navigation, canEditDrillDownUrls, - urlQuery, setHeaderActionMenu, + sharingSavedObjectProps, + spaces, }: WorkspaceLayoutProps & WorkspaceLayoutStateProps) => { const [currentIndexPattern, setCurrentIndexPattern] = useState(); const [showInspect, setShowInspect] = useState(false); @@ -90,6 +95,10 @@ const WorkspaceLayoutComponent = ({ const [mergeCandidates, setMergeCandidates] = useState([]); const [control, setControl] = useState('none'); const selectedNode = useRef(undefined); + + const search = useLocation().search; + const urlQuery = new URLSearchParams(search).get('query'); + const isInitialized = Boolean(workspaceInitialized || savedWorkspace.id); const selectSelected = useCallback((node: WorkspaceNode) => { @@ -154,6 +163,27 @@ const WorkspaceLayoutComponent = ({ [] ); + const getLegacyUrlConflictCallout = useCallback(() => { + // This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario + const currentObjectId = savedWorkspace.id; + if (spaces && sharingSavedObjectProps?.outcome === 'conflict' && currentObjectId) { + // We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a + // callout with a warning for the user, and provide a way for them to navigate to the other object. + const otherObjectId = sharingSavedObjectProps?.aliasTargetId!; // This is always defined if outcome === 'conflict' + const otherObjectPath = + getEditUrl(coreStart.http.basePath.prepend, { id: otherObjectId }) + search; + return spaces.ui.components.getLegacyUrlConflict({ + objectNoun: i18n.translate('xpack.graph.legacyUrlConflict.objectNoun', { + defaultMessage: 'Graph', + }), + currentObjectId, + otherObjectId, + otherObjectPath, + }); + } + return null; + }, [savedWorkspace.id, sharingSavedObjectProps, spaces, coreStart.http, search]); + return ( - {isInitialized && }
+ {getLegacyUrlConflictCallout()} {!isInitialized && (
savedWorkspaceType, + ...defaultsProps, + } as GraphWorkspaceSavedObject, + }; +} + export async function getSavedWorkspace( savedObjectsClient: SavedObjectsClientContract, - id?: string + id: string ) { - const savedObject = { - id, - displayName: 'graph workspace', - getEsType: () => savedWorkspaceType, - } as { [key: string]: any }; - - if (!id) { - assign(savedObject, defaultsProps); - return Promise.resolve(savedObject); - } + const resolveResult = await savedObjectsClient.resolve>( + savedWorkspaceType, + id + ); - const resp = await savedObjectsClient.get>(savedWorkspaceType, id); - savedObject._source = cloneDeep(resp.attributes); + const resp = resolveResult.saved_object; if (!resp._version) { throw new SavedObjectNotFound(savedWorkspaceType, id || ''); } + const savedObject = { + id, + displayName: 'graph workspace', + getEsType: () => savedWorkspaceType, + _source: cloneDeep(resp.attributes), + } as GraphWorkspaceSavedObject; + // assign the defaults to the response defaults(savedObject._source, defaultsProps); @@ -120,7 +130,15 @@ export async function getSavedWorkspace( injectReferences(savedObject, resp.references); } - return savedObject as GraphWorkspaceSavedObject; + const sharingSavedObjectProps = { + outcome: resolveResult.outcome, + aliasTargetId: resolveResult.alias_target_id, + }; + + return { + savedObject, + sharingSavedObjectProps, + }; } export function deleteSavedWorkspace( diff --git a/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx new file mode 100644 index 0000000000000..db80289d0d32d --- /dev/null +++ b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { mount } from 'enzyme'; +import { useWorkspaceLoader, UseWorkspaceLoaderProps } from './use_workspace_loader'; +import { coreMock } from 'src/core/public/mocks'; +import { spacesPluginMock } from '../../../spaces/public/mocks'; +import { createMockGraphStore } from '../state_management/mocks'; +import { Workspace } from '../types'; +import { SavedObjectsClientCommon } from 'src/plugins/data/common'; +import { act } from 'react-dom/test-utils'; + +jest.mock('react-router-dom', () => { + const useLocation = () => ({ + search: '?query={}', + }); + + const replaceFn = jest.fn(); + + const useHistory = () => ({ + replace: replaceFn, + }); + return { + useHistory, + useLocation, + useParams: () => ({ + id: '1', + }), + }; +}); + +const mockSavedObjectsClient = ({ + resolve: jest.fn().mockResolvedValue({ + saved_object: { id: 10, _version: '7.15.0', attributes: { wsState: '{}' } }, + outcome: 'exactMatch', + }), + find: jest.fn().mockResolvedValue({ title: 'test' }), +} as unknown) as SavedObjectsClientCommon; + +async function setup(props: UseWorkspaceLoaderProps) { + const returnVal = {}; + function TestComponent() { + Object.assign(returnVal, useWorkspaceLoader(props)); + return null; + } + await act(async () => { + const promise = Promise.resolve(); + mount(); + await act(() => promise); + }); + return returnVal; +} + +describe('use_workspace_loader', () => { + const defaultProps = { + workspaceRef: { current: {} as Workspace }, + store: createMockGraphStore({}).store, + savedObjectsClient: mockSavedObjectsClient, + coreStart: coreMock.createStart(), + spaces: spacesPluginMock.createStartContract(), + }; + + it('should not redirect if outcome is exactMatch', async () => { + await act(async () => { + await setup((defaultProps as unknown) as UseWorkspaceLoaderProps); + }); + expect(defaultProps.spaces.ui.redirectLegacyUrl).not.toHaveBeenCalled(); + expect(defaultProps.store.dispatch).toHaveBeenCalled(); + }); + it('should redirect if outcome is aliasMatch', async () => { + const props = { + ...defaultProps, + spaces: spacesPluginMock.createStartContract(), + savedObjectsClient: { + ...mockSavedObjectsClient, + resolve: jest.fn().mockResolvedValue({ + saved_object: { id: 10, _version: '7.15.0', attributes: { wsState: '{}' } }, + outcome: 'aliasMatch', + alias_target_id: 'aliasTargetId', + }), + }, + }; + await act(async () => { + await setup((props as unknown) as UseWorkspaceLoaderProps); + }); + expect(props.spaces.ui.redirectLegacyUrl).toHaveBeenCalledWith( + '#/workspace/aliasTargetId?query={}', + 'Graph' + ); + }); +}); diff --git a/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts b/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts index 8b91546d52446..b9abf31e084fe 100644 --- a/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts +++ b/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts @@ -5,35 +5,48 @@ * 2.0. */ -import { SavedObjectsClientContract, ToastsStart } from 'kibana/public'; +import { SavedObjectsClientContract } from 'kibana/public'; import { useEffect, useState } from 'react'; import { useHistory, useLocation, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import { CoreStart } from 'kibana/public'; import { GraphStore } from '../state_management'; import { GraphWorkspaceSavedObject, IndexPatternSavedObject, Workspace } from '../types'; -import { getSavedWorkspace } from './saved_workspace_utils'; - -interface UseWorkspaceLoaderProps { +import { getEmptyWorkspace, getSavedWorkspace } from './saved_workspace_utils'; +import { getEditUrl } from '../services/url'; +import { SpacesApi } from '../../../spaces/public'; +export interface UseWorkspaceLoaderProps { store: GraphStore; workspaceRef: React.MutableRefObject; savedObjectsClient: SavedObjectsClientContract; - toastNotifications: ToastsStart; + coreStart: CoreStart; + spaces?: SpacesApi; } interface WorkspaceUrlParams { id?: string; } +export interface SharingSavedObjectProps { + outcome?: 'aliasMatch' | 'exactMatch' | 'conflict'; + aliasTargetId?: string; +} + +interface WorkspaceLoadedState { + savedWorkspace: GraphWorkspaceSavedObject; + indexPatterns: IndexPatternSavedObject[]; + sharingSavedObjectProps?: SharingSavedObjectProps; +} export const useWorkspaceLoader = ({ + coreStart, + spaces, workspaceRef, store, savedObjectsClient, - toastNotifications, }: UseWorkspaceLoaderProps) => { - const [indexPatterns, setIndexPatterns] = useState(); - const [savedWorkspace, setSavedWorkspace] = useState(); - const history = useHistory(); - const location = useLocation(); + const [state, setState] = useState(); + const { replace: historyReplace } = useHistory(); + const { search } = useLocation(); const { id } = useParams(); /** @@ -41,7 +54,7 @@ export const useWorkspaceLoader = ({ * on changes in id parameter and URL query only. */ useEffect(() => { - const urlQuery = new URLSearchParams(location.search).get('query'); + const urlQuery = new URLSearchParams(search).get('query'); function loadWorkspace( fetchedSavedWorkspace: GraphWorkspaceSavedObject, @@ -71,24 +84,43 @@ export const useWorkspaceLoader = ({ .then((response) => response.savedObjects); } - async function fetchSavedWorkspace() { - return (id + async function fetchSavedWorkspace(): Promise<{ + savedObject: GraphWorkspaceSavedObject; + sharingSavedObjectProps?: SharingSavedObjectProps; + }> { + return id ? await getSavedWorkspace(savedObjectsClient, id).catch(function (e) { - toastNotifications.addError(e, { + coreStart.notifications.toasts.addError(e, { title: i18n.translate('xpack.graph.missingWorkspaceErrorMessage', { defaultMessage: "Couldn't load graph with ID", }), }); - history.replace('/home'); + historyReplace('/home'); // return promise that never returns to prevent the controller from loading return new Promise(() => {}); }) - : await getSavedWorkspace(savedObjectsClient)) as GraphWorkspaceSavedObject; + : getEmptyWorkspace(); } async function initializeWorkspace() { const fetchedIndexPatterns = await fetchIndexPatterns(); - const fetchedSavedWorkspace = await fetchSavedWorkspace(); + const { + savedObject: fetchedSavedWorkspace, + sharingSavedObjectProps: fetchedSharingSavedObjectProps, + } = await fetchSavedWorkspace(); + + if (spaces && fetchedSharingSavedObjectProps?.outcome === 'aliasMatch') { + // We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash + const newObjectId = fetchedSharingSavedObjectProps?.aliasTargetId!; // This is always defined if outcome === 'aliasMatch' + const newPath = getEditUrl(coreStart.http.basePath.prepend, { id: newObjectId }) + search; + spaces.ui.redirectLegacyUrl( + newPath, + i18n.translate('xpack.graph.legacyUrlConflict.objectNoun', { + defaultMessage: 'Graph', + }) + ); + return null; + } /** * Deal with situation of request to open saved workspace. Otherwise clean up store, @@ -99,22 +131,25 @@ export const useWorkspaceLoader = ({ } else if (workspaceRef.current) { clearStore(); } - - setIndexPatterns(fetchedIndexPatterns); - setSavedWorkspace(fetchedSavedWorkspace); + setState({ + savedWorkspace: fetchedSavedWorkspace, + indexPatterns: fetchedIndexPatterns, + sharingSavedObjectProps: fetchedSharingSavedObjectProps, + }); } initializeWorkspace(); }, [ id, - location, + search, store, - history, + historyReplace, savedObjectsClient, - setSavedWorkspace, - toastNotifications, + setState, + coreStart, workspaceRef, + spaces, ]); - return { savedWorkspace, indexPatterns }; + return state; }; diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 1ff9afe505a3b..1c44714a2c498 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { BehaviorSubject } from 'rxjs'; +import { SpacesApi } from '../../spaces/public'; import { AppNavLinkStatus, AppUpdater, @@ -44,6 +45,7 @@ export interface GraphPluginStartDependencies { savedObjects: SavedObjectsStart; kibanaLegacy: KibanaLegacyStart; home?: HomePublicPluginStart; + spaces?: SpacesApi; } export class GraphPlugin @@ -110,6 +112,7 @@ export class GraphPlugin overlays: coreStart.overlays, savedObjects: pluginsStart.savedObjects, uiSettings: core.uiSettings, + spaces: pluginsStart.spaces, }); }, }); diff --git a/x-pack/plugins/graph/public/services/url.ts b/x-pack/plugins/graph/public/services/url.ts index e45d1f0d662be..b33fdc82d8642 100644 --- a/x-pack/plugins/graph/public/services/url.ts +++ b/x-pack/plugins/graph/public/services/url.ts @@ -18,13 +18,13 @@ export function getNewPath() { return '/workspace'; } -export function getEditPath({ id }: GraphWorkspaceSavedObject) { +export function getEditPath({ id }: Pick) { return `/workspace/${id}`; } export function getEditUrl( addBasePath: (url: string) => string, - workspace: GraphWorkspaceSavedObject + workspace: Pick ) { return addBasePath(`#${getEditPath(workspace)}`); } diff --git a/x-pack/plugins/graph/tsconfig.json b/x-pack/plugins/graph/tsconfig.json index d655f28c4e46e..6a5623b311d5e 100644 --- a/x-pack/plugins/graph/tsconfig.json +++ b/x-pack/plugins/graph/tsconfig.json @@ -24,6 +24,7 @@ { "path": "../../../src/plugins/kibana_legacy/tsconfig.json"}, { "path": "../../../src/plugins/home/tsconfig.json"}, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, - { "path": "../../../src/plugins/kibana_react/tsconfig.json" } + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../spaces/tsconfig.json" } ] } diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts index 7d370c7106cb7..5e3c8a219ae47 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/index.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts @@ -31,7 +31,7 @@ export function createInventoryMetricAlertType(): ObservabilityRuleTypeModel import('./components/expression')), validate: validateMetricThreshold, diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts index b944b36fb9d1a..c6b2385f93c65 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts @@ -23,7 +23,7 @@ export function createLogThresholdAlertType(): ObservabilityRuleTypeModel import('./components/expression_editor/editor')), validate: validateExpression, diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts index 2dd35c20a5632..679019eb2e520 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts @@ -29,7 +29,7 @@ export function createMetricThresholdAlertType(): ObservabilityRuleTypeModel import('./components/expression')), validate: validateMetricThreshold, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx index fe0fbeecf8408..31bc09f9d4dd8 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx @@ -5,11 +5,12 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { useUiTracker } from '../../../../../../observability/public'; +import { useWaffleOptionsContext } from '../hooks/use_waffle_options'; import { InfraFormatter } from '../../../../lib/lib'; import { Timeline } from './timeline/timeline'; @@ -28,13 +29,20 @@ export const BottomDrawer: React.FC<{ formatter: InfraFormatter; width: number; }> = ({ measureRef, width, interval, formatter, children }) => { - const [isOpen, setIsOpen] = useState(false); + const { timelineOpen, changeTimelineOpen } = useWaffleOptionsContext(); + + const [isOpen, setIsOpen] = useState(Boolean(timelineOpen)); + + useEffect(() => { + if (isOpen !== timelineOpen) setIsOpen(Boolean(timelineOpen)); + }, [isOpen, timelineOpen]); const trackDrawerOpen = useUiTracker({ app: 'infra_metrics' }); const onClick = useCallback(() => { if (!isOpen) trackDrawerOpen({ metric: 'open_timeline_drawer__inventory' }); setIsOpen(!isOpen); - }, [isOpen, trackDrawerOpen]); + changeTimelineOpen(!isOpen); + }, [isOpen, trackDrawerOpen, changeTimelineOpen]); return ( diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx index ac4fac394dacc..d8b578769a1cb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx @@ -130,6 +130,7 @@ const mockedUseWaffleOptionsContexReturnValue: ReturnType {}), changeLegend: jest.fn(() => {}), changeSort: jest.fn(() => {}), + changeTimelineOpen: jest.fn(() => {}), setWaffleOptionsState: jest.fn(() => {}), boundsOverride: { max: 1, min: 0 }, autoBounds: true, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts index 0ba8398fa4c42..8767be4f8a27e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts @@ -44,6 +44,7 @@ export const DEFAULT_WAFFLE_OPTIONS_STATE: WaffleOptionsState = { legend: DEFAULT_LEGEND, source: 'default', sort: { by: 'name', direction: 'desc' }, + timelineOpen: false, }; export const useWaffleOptions = () => { @@ -134,6 +135,11 @@ export const useWaffleOptions = () => { setCustomMetrics(state.customMetrics); }, [state, inventoryPrefill]); + const changeTimelineOpen = useCallback( + (timelineOpen: boolean) => setState((previous) => ({ ...previous, timelineOpen })), + [setState] + ); + return { ...DEFAULT_WAFFLE_OPTIONS_STATE, ...state, @@ -149,6 +155,7 @@ export const useWaffleOptions = () => { changeCustomMetrics, changeLegend, changeSort, + changeTimelineOpen, setWaffleOptionsState: setState, }; }; @@ -188,7 +195,7 @@ export const WaffleOptionsStateRT = rt.intersection([ customMetrics: rt.array(SnapshotCustomMetricInputRT), sort: WaffleSortOptionRT, }), - rt.partial({ source: rt.string, legend: WaffleLegendOptionsRT }), + rt.partial({ source: rt.string, legend: WaffleLegendOptionsRT, timelineOpen: rt.boolean }), ]); export type WaffleSortOption = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts index 91f1859899177..02a2144f1282e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts @@ -39,6 +39,7 @@ export const useWaffleViewState = () => { region, legend, sort, + timelineOpen, setWaffleOptionsState, } = useWaffleOptionsContext(); const { currentTime, isAutoReloading, setWaffleTimeState } = useWaffleTimeContext(); @@ -60,6 +61,7 @@ export const useWaffleViewState = () => { autoReload: isAutoReloading, filterQuery, legend, + timelineOpen, }; const onViewChange = useCallback( @@ -77,6 +79,7 @@ export const useWaffleViewState = () => { accountId: newState.accountId, region: newState.region, legend: newState.legend, + timelineOpen: newState.timelineOpen, }); if (newState.time) { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_timerange.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_timerange.test.ts new file mode 100644 index 0000000000000..5640a1d928436 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_timerange.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTimerange } from './create_timerange'; +import { Aggregators } from '../types'; +import moment from 'moment'; + +describe('createTimerange(interval, aggType, timeframe)', () => { + describe('without timeframe', () => { + describe('Basic Metric Aggs', () => { + it('should return a second range for last 1 second', () => { + const subject = createTimerange(1000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(1000); + }); + it('should return a minute range for last 1 minute', () => { + const subject = createTimerange(60000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(60000); + }); + it('should return 5 minute range for last 5 minutes', () => { + const subject = createTimerange(300000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(300000); + }); + it('should return a hour range for last 1 hour', () => { + const subject = createTimerange(3600000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(3600000); + }); + it('should return a day range for last 1 day', () => { + const subject = createTimerange(86400000, Aggregators.COUNT); + expect(subject.end - subject.start).toEqual(86400000); + }); + }); + describe('Rate Aggs', () => { + it('should return a 20 second range for last 1 second', () => { + const subject = createTimerange(1000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(1000 * 5); + }); + it('should return a 5 minute range for last 1 minute', () => { + const subject = createTimerange(60000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(60000 * 5); + }); + it('should return 25 minute range for last 5 minutes', () => { + const subject = createTimerange(300000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(300000 * 5); + }); + it('should return 5 hour range for last hour', () => { + const subject = createTimerange(3600000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(3600000 * 5); + }); + it('should return a 5 day range for last day', () => { + const subject = createTimerange(86400000, Aggregators.RATE); + expect(subject.end - subject.start).toEqual(86400000 * 5); + }); + }); + }); + describe('with full timeframe', () => { + describe('Basic Metric Aggs', () => { + it('should return 5 minute range when given 4 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(4, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.COUNT, timeframe); + expect(subject.end - subject.start).toEqual(300000); + }); + it('should return 6 minute range when given 6 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(6, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.COUNT, timeframe); + expect(subject.end - subject.start).toEqual(360000); + }); + }); + describe('Rate Aggs', () => { + it('should return 25 minute range when given 4 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(4, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.RATE, timeframe); + expect(subject.end - subject.start).toEqual(300000 * 5); + }); + it('should return 25 minute range when given 6 minute timeframe', () => { + const end = moment(); + const timeframe = { + start: end.clone().subtract(6, 'minutes').valueOf(), + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.RATE, timeframe); + expect(subject.end - subject.start).toEqual(300000 * 5); + }); + }); + }); + describe('with partial timeframe', () => { + describe('Basic Metric Aggs', () => { + it('should return 5 minute range for last 5 minutes', () => { + const end = moment(); + const timeframe = { + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.AVERAGE, timeframe); + expect(subject).toEqual({ + start: end.clone().subtract(5, 'minutes').valueOf(), + end: end.valueOf(), + }); + }); + }); + describe('Rate Aggs', () => { + it('should return 25 minute range for last 5 minutes', () => { + const end = moment(); + const timeframe = { + end: end.valueOf(), + }; + const subject = createTimerange(300000, Aggregators.RATE, timeframe); + expect(subject).toEqual({ + start: end + .clone() + .subtract(300 * 5, 'seconds') + .valueOf(), + end: end.valueOf(), + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_timerange.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_timerange.ts new file mode 100644 index 0000000000000..cca63aca14d09 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_timerange.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { Aggregators } from '../types'; + +export const createTimerange = ( + interval: number, + aggType: Aggregators, + timeframe?: { end: number; start?: number } +) => { + const to = moment(timeframe ? timeframe.end : Date.now()).valueOf(); + + // Rate aggregations need 5 buckets worth of data + const minimumBuckets = aggType === Aggregators.RATE ? 5 : 1; + + const calculatedFrom = to - interval * minimumBuckets; + + // Use either the timeframe.start when the start is less then calculatedFrom + // OR use the calculatedFrom + const from = + timeframe && timeframe.start && timeframe.start <= calculatedFrom + ? timeframe.start + : calculatedFrom; + + return { start: from, end: to }; +}; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index 9a8f2267e7efe..a099b83fdb423 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -13,7 +13,6 @@ import { TOO_MANY_BUCKETS_PREVIEW_EXCEPTION, } from '../../../../../common/alerting/metrics'; import { getIntervalInSeconds } from '../../../../utils/get_interval_in_seconds'; -import { roundTimestamp } from '../../../../utils/round_timestamp'; import { InfraSource } from '../../../../../common/source_configuration/source_configuration'; import { InfraDatabaseSearchResponse } from '../../../adapters/framework/adapter_types'; import { createAfterKeyHandler } from '../../../../utils/create_afterkey_handler'; @@ -22,6 +21,7 @@ import { DOCUMENT_COUNT_I18N } from '../../common/messages'; import { UNGROUPED_FACTORY_KEY } from '../../common/utils'; import { MetricExpressionParams, Comparator, Aggregators } from '../types'; import { getElasticsearchMetricQuery } from './metric_query'; +import { createTimerange } from './create_timerange'; interface AggregationWithoutIntervals { aggregatedValue: { value: number; values?: Array<{ key: number; value: number }> }; @@ -135,23 +135,12 @@ const getMetric: ( const interval = `${timeSize}${timeUnit}`; const intervalAsSeconds = getIntervalInSeconds(interval); const intervalAsMS = intervalAsSeconds * 1000; - - const to = moment(timeframe ? timeframe.end : Date.now()).valueOf(); - - // Rate aggregations need 5 buckets worth of data - const minimumBuckets = aggType === Aggregators.RATE ? 5 : 1; - - const minimumFrom = to - intervalAsMS * minimumBuckets; - - const from = roundTimestamp( - timeframe && timeframe.start && timeframe.start <= minimumFrom ? timeframe.start : minimumFrom, - timeUnit - ); + const calculatedTimerange = createTimerange(intervalAsMS, aggType, timeframe); const searchBody = getElasticsearchMetricQuery( params, timefield, - { start: from, end: to }, + calculatedTimerange, hasGroupBy ? groupBy : undefined, filterQuery ); @@ -160,8 +149,8 @@ const getMetric: ( // Rate aggs always drop partial buckets; guard against this boolean being passed as false shouldDropPartialBuckets || aggType === Aggregators.RATE ? { - from, - to, + from: calculatedTimerange.start, + to: calculatedTimerange.end, bucketSizeInMillis: intervalAsMS, } : null; @@ -191,10 +180,7 @@ const getMetric: ( bucket, aggType, dropPartialBucketsOptions, - { - start: from, - end: to, - }, + calculatedTimerange, bucket.doc_count ), }), @@ -212,7 +198,7 @@ const getMetric: ( (result.aggregations! as unknown) as Aggregation, aggType, dropPartialBucketsOptions, - { start: from, end: to }, + calculatedTimerange, isNumber(result.hits.total) ? result.hits.total : result.hits.total.value ), }; diff --git a/x-pack/plugins/lens/public/state_management/index.ts b/x-pack/plugins/lens/public/state_management/index.ts index a23a040de2361..1d8f4fdffa730 100644 --- a/x-pack/plugins/lens/public/state_management/index.ts +++ b/x-pack/plugins/lens/public/state_management/index.ts @@ -6,7 +6,7 @@ */ import { configureStore, getDefaultMiddleware, DeepPartial } from '@reduxjs/toolkit'; -import logger from 'redux-logger'; +import { createLogger } from 'redux-logger'; import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; import { lensSlice } from './lens_slice'; import { timeRangeMiddleware } from './time_range_middleware'; @@ -50,7 +50,14 @@ export const makeConfigureStore = ( optimizingMiddleware(), timeRangeMiddleware(storeDeps.lensServices.data), ]; - if (process.env.NODE_ENV === 'development') middleware.push(logger); + if (process.env.NODE_ENV === 'development') { + middleware.push( + createLogger({ + // @ts-ignore + predicate: () => window.ELASTIC_LENS_LOGGER, + }) + ); + } return configureStore({ reducer, diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 5cfed7d6a58b5..b6b3e636fffeb 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import { FeatureCollection } from 'geojson'; export const EMS_APP_NAME = 'kibana'; -export const EMS_CATALOGUE_PATH = 'ems/catalogue'; export const EMS_FILES_CATALOGUE_PATH = 'ems/files'; export const EMS_FILES_API_PATH = 'ems/files'; diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index 8e996934ef69e..f2c13a81045ee 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -7,10 +7,10 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { Query } from 'src/plugins/data/public'; +import type { Query } from 'src/plugins/data/common'; import { SortDirection } from 'src/plugins/data/common/search'; import { RENDER_AS, SCALING_TYPES } from '../constants'; -import { MapExtent, MapQuery } from './map_descriptor'; +import { MapExtent } from './map_descriptor'; import { Filter, TimeRange } from '../../../../../src/plugins/data/common'; import { ESTermSourceDescriptor } from './source_descriptor_types'; @@ -20,12 +20,11 @@ export type Timeslice = { }; // Global map state passed to every layer. -export type MapFilters = { +export type DataFilters = { buffer?: MapExtent; // extent with additional buffer extent?: MapExtent; // map viewport filters: Filter[]; - query?: MapQuery; - refreshTimerLastTriggeredAt?: string; + query?: Query; searchSessionId?: string; timeFilters: TimeRange; timeslice?: Timeslice; @@ -60,24 +59,24 @@ export type VectorSourceSyncMeta = | ESTermSourceSyncMeta | null; -export type VectorSourceRequestMeta = MapFilters & { +export type VectorSourceRequestMeta = DataFilters & { applyGlobalQuery: boolean; applyGlobalTime: boolean; + applyForceRefresh: boolean; fieldNames: string[]; geogridPrecision?: number; - timesiceMaskField?: string; - sourceQuery?: MapQuery; + timesliceMaskField?: string; + sourceQuery?: Query; sourceMeta: VectorSourceSyncMeta; + isForceRefresh: boolean; }; -export type VectorJoinSourceRequestMeta = Omit & { - sourceQuery?: Query; -}; +export type VectorJoinSourceRequestMeta = Omit; -export type VectorStyleRequestMeta = MapFilters & { +export type VectorStyleRequestMeta = DataFilters & { dynamicStyleFields: string[]; isTimeAware: boolean; - sourceQuery: MapQuery; + sourceQuery: Query; timeFilters: TimeRange; }; @@ -107,7 +106,7 @@ export type VectorTileLayerMeta = { }; // Partial because objects are justified downstream in constructors -export type DataMeta = Partial< +export type DataRequestMeta = Partial< VectorSourceRequestMeta & VectorJoinSourceRequestMeta & VectorStyleRequestMeta & @@ -134,8 +133,8 @@ export type StyleMetaData = { export type DataRequestDescriptor = { dataId: string; - dataMetaAtStart?: DataMeta | null; + dataRequestMetaAtStart?: DataRequestMeta | null; dataRequestToken?: symbol; data?: object; - dataMeta?: DataMeta; + dataRequestMeta?: DataRequestMeta; }; 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 20d811fab62f7..8cb43713face4 100644 --- a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts +++ b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts @@ -10,7 +10,6 @@ import { ReactNode } from 'react'; import { GeoJsonProperties } from 'geojson'; import { Geometry } from 'geojson'; -import { Query } from '../../../../../src/plugins/data/common'; import { DRAW_SHAPE, ES_SPATIAL_RELATIONS } from '../constants'; export type MapExtent = { @@ -20,10 +19,6 @@ export type MapExtent = { maxLat: number; }; -export type MapQuery = Query & { - queryLastTriggeredAt?: string; -}; - export type MapCenter = { lat: number; lon: number; diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index 9a2af711ea2c7..285c4043e46c7 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -42,6 +42,7 @@ export type AbstractESSourceDescriptor = AbstractSourceDescriptor & { geoField?: string; applyGlobalQuery: boolean; applyGlobalTime: boolean; + applyForceRefresh: boolean; }; type AbstractAggDescriptor = { diff --git a/x-pack/plugins/maps/common/index.ts b/x-pack/plugins/maps/common/index.ts index 7c551b3ed9eb4..c1b5d26fca292 100644 --- a/x-pack/plugins/maps/common/index.ts +++ b/x-pack/plugins/maps/common/index.ts @@ -5,8 +5,24 @@ * 2.0. */ -// TODO: https://github.com/elastic/kibana/issues/109853 -/* eslint-disable @kbn/eslint/no_export_all */ +export { + AGG_TYPE, + COLOR_MAP_TYPE, + ES_GEO_FIELD_TYPE, + FIELD_ORIGIN, + INITIAL_LOCATION, + LABEL_BORDER_SIZES, + MAP_SAVED_OBJECT_TYPE, + SOURCE_TYPES, + STYLE_TYPE, + SYMBOLIZE_AS_TYPES, +} from './constants'; -export * from './constants'; -export * from './types'; +export { + EMSFileSourceDescriptor, + ESTermSourceDescriptor, + LayerDescriptor, + TooltipFeature, + VectorLayerDescriptor, + VectorStyleDescriptor, +} from './descriptor_types'; diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index e2cc415820db5..bfd501dbcb295 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -34,7 +34,7 @@ "ui": true, "server": true, "extraPublicDirs": [ - "common/constants" + "common" ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 50df95a52c4d4..48b0a416b5f0f 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -45,22 +45,28 @@ import { } from './map_action_constants'; import { ILayer } from '../classes/layers/layer'; import { IVectorLayer } from '../classes/layers/vector_layer'; -import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types'; +import { DataRequestMeta, MapExtent, DataFilters } from '../../common/descriptor_types'; import { DataRequestAbortError } from '../classes/util/data_request'; import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_util'; const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1; export type DataRequestContext = { - startLoading(dataId: string, requestToken: symbol, requestMeta?: DataMeta): void; - stopLoading(dataId: string, requestToken: symbol, data: object, resultsMeta?: DataMeta): void; + startLoading(dataId: string, requestToken: symbol, requestMeta?: DataRequestMeta): void; + stopLoading( + dataId: string, + requestToken: symbol, + data: object, + resultsMeta?: DataRequestMeta + ): void; onLoadError(dataId: string, requestToken: symbol, errorMessage: string): void; onJoinError(errorMessage: string): void; updateSourceData(newData: unknown): void; isRequestStillActive(dataId: string, requestToken: symbol): boolean; registerCancelCallback(requestToken: symbol, callback: () => void): void; - dataFilters: MapFilters; - forceRefresh: boolean; + dataFilters: DataFilters; + forceRefreshDueToDrawing: boolean; // Boolean signaling data request triggered by a user updating layer features via drawing tools. When true, layer will re-load regardless of "source.applyForceRefresh" flag. + isForceRefresh: boolean; // Boolean signaling data request triggered by auto-refresh timer or user clicking refresh button. When true, layer will re-load only when "source.applyForceRefresh" flag is set to true. }; export function clearDataRequests(layer: ILayer) { @@ -112,13 +118,14 @@ function getDataRequestContext( dispatch: ThunkDispatch, getState: () => MapStoreState, layerId: string, - forceRefresh: boolean = false + forceRefreshDueToDrawing: boolean, + isForceRefresh: boolean ): DataRequestContext { return { dataFilters: getDataFilters(getState()), - startLoading: (dataId: string, requestToken: symbol, meta: DataMeta) => + startLoading: (dataId: string, requestToken: symbol, meta: DataRequestMeta) => dispatch(startDataLoad(layerId, dataId, requestToken, meta)), - stopLoading: (dataId: string, requestToken: symbol, data: object, meta: DataMeta) => + stopLoading: (dataId: string, requestToken: symbol, data: object, meta: DataRequestMeta) => dispatch(endDataLoad(layerId, dataId, requestToken, data, meta)), onLoadError: (dataId: string, requestToken: symbol, errorMessage: string) => dispatch(onDataLoadError(layerId, dataId, requestToken, errorMessage)), @@ -136,17 +143,18 @@ function getDataRequestContext( }, registerCancelCallback: (requestToken: symbol, callback: () => void) => dispatch(registerCancelCallback(requestToken, callback)), - forceRefresh, + forceRefreshDueToDrawing, + isForceRefresh, }; } -export function syncDataForAllLayers() { +export function syncDataForAllLayers(isForceRefresh: boolean) { return async ( dispatch: ThunkDispatch, getState: () => MapStoreState ) => { const syncPromises = getLayerList(getState()).map((layer) => { - return dispatch(syncDataForLayer(layer)); + return dispatch(syncDataForLayer(layer, isForceRefresh)); }); await Promise.all(syncPromises); }; @@ -162,19 +170,20 @@ function syncDataForAllJoinLayers() { return 'hasJoins' in layer ? (layer as IVectorLayer).hasJoins() : false; }) .map((layer) => { - return dispatch(syncDataForLayer(layer)); + return dispatch(syncDataForLayer(layer, false)); }); await Promise.all(syncPromises); }; } -export function syncDataForLayer(layer: ILayer, forceRefresh: boolean = false) { +export function syncDataForLayerDueToDrawing(layer: ILayer) { return async (dispatch: Dispatch, getState: () => MapStoreState) => { const dataRequestContext = getDataRequestContext( dispatch, getState, layer.getId(), - forceRefresh + true, + false ); if (!layer.isVisible() || !layer.showAtZoomLevel(dataRequestContext.dataFilters.zoom)) { return; @@ -183,14 +192,30 @@ export function syncDataForLayer(layer: ILayer, forceRefresh: boolean = false) { }; } -export function syncDataForLayerId(layerId: string | null) { +export function syncDataForLayer(layer: ILayer, isForceRefresh: boolean) { + return async (dispatch: Dispatch, getState: () => MapStoreState) => { + const dataRequestContext = getDataRequestContext( + dispatch, + getState, + layer.getId(), + false, + isForceRefresh + ); + if (!layer.isVisible() || !layer.showAtZoomLevel(dataRequestContext.dataFilters.zoom)) { + return; + } + await layer.syncData(dataRequestContext); + }; +} + +export function syncDataForLayerId(layerId: string | null, isForceRefresh: boolean) { return async ( dispatch: ThunkDispatch, getState: () => MapStoreState ) => { const layer = getLayerById(layerId, getState()); if (layer) { - dispatch(syncDataForLayer(layer)); + dispatch(syncDataForLayer(layer, isForceRefresh)); } }; } @@ -204,7 +229,12 @@ function setLayerDataLoadErrorStatus(layerId: string, errorMessage: string | nul }; } -function startDataLoad(layerId: string, dataId: string, requestToken: symbol, meta: DataMeta) { +function startDataLoad( + layerId: string, + dataId: string, + requestToken: symbol, + meta: DataRequestMeta +) { return ( dispatch: ThunkDispatch, getState: () => MapStoreState @@ -237,7 +267,7 @@ function endDataLoad( dataId: string, requestToken: symbol, data: object, - meta: DataMeta + meta: DataRequestMeta ) { return ( dispatch: ThunkDispatch, @@ -342,7 +372,7 @@ export function fitToLayerExtent(layerId: string) { if (targetLayer) { try { const bounds = await targetLayer.getBounds( - getDataRequestContext(dispatch, getState, layerId) + getDataRequestContext(dispatch, getState, layerId, false, false) ); if (bounds) { await dispatch(setGotoWithBounds(scaleBounds(bounds, FIT_TO_BOUNDS_SCALE_FACTOR))); @@ -374,7 +404,9 @@ export function fitToDataBounds(onNoBounds?: () => void) { if (!(await layer.isFittable())) { return null; } - return layer.getBounds(getDataRequestContext(dispatch, getState, layer.getId())); + return layer.getBounds( + getDataRequestContext(dispatch, getState, layer.getId(), false, false) + ); }); let bounds; @@ -442,7 +474,7 @@ export function autoFitToBounds() { // Ensure layer syncing occurs when setGotoWithBounds is not triggered. function onNoBounds() { if (localSetQueryCallId === lastSetQueryCallId) { - dispatch(syncDataForAllLayers()); + dispatch(syncDataForAllLayers(false)); } } dispatch(fitToDataBounds(onNoBounds)); diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index edd21090143bf..d5bb061ccf430 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -80,7 +80,7 @@ export function rollbackToTrackedLayerStateForSelectedLayer() { // syncDataForLayer may not trigger endDataLoad if no re-fetch is required dispatch(updateStyleMeta(layerId)); - dispatch(syncDataForLayerId(layerId)); + dispatch(syncDataForLayerId(layerId, false)); }; } @@ -149,7 +149,7 @@ export function addLayer(layerDescriptor: LayerDescriptor) { type: ADD_LAYER, layer: layerDescriptor, }); - dispatch(syncDataForLayerId(layerDescriptor.id)); + dispatch(syncDataForLayerId(layerDescriptor.id, false)); const layer = createLayerInstance(layerDescriptor); const features = await layer.getLicensedFeatures(); @@ -226,7 +226,7 @@ export function setLayerVisibility(layerId: string, makeVisible: boolean) { visibility: makeVisible, }); if (makeVisible) { - dispatch(syncDataForLayerId(layerId)); + dispatch(syncDataForLayerId(layerId, false)); } }; } @@ -330,7 +330,7 @@ function updateMetricsProp(layerId: string, value: unknown) { value, }); await dispatch(updateStyleProperties(layerId, previousFields as IESAggField[])); - dispatch(syncDataForLayerId(layerId)); + dispatch(syncDataForLayerId(layerId, false)); }; } @@ -356,7 +356,7 @@ export function updateSourceProp( if (newLayerType) { dispatch(updateLayerType(layerId, newLayerType)); } - dispatch(syncDataForLayerId(layerId)); + dispatch(syncDataForLayerId(layerId, false)); }; } @@ -459,7 +459,7 @@ export function setLayerQuery(id: string, query: Query) { newValue: query, }); - dispatch(syncDataForLayerId(id)); + dispatch(syncDataForLayerId(id, false)); }; } @@ -563,7 +563,7 @@ export function updateLayerStyle(layerId: string, styleDescriptor: StyleDescript dispatch(updateStyleMeta(layerId)); // Style update may require re-fetch, for example ES search may need to retrieve field used for dynamic styling - dispatch(syncDataForLayerId(layerId)); + dispatch(syncDataForLayerId(layerId, false)); }; } @@ -589,7 +589,7 @@ export function setJoinsForLayer(layer: ILayer, joins: JoinDescriptor[]) { joins, }); await dispatch(updateStyleProperties(layer.getId(), previousFields)); - dispatch(syncDataForLayerId(layer.getId())); + dispatch(syncDataForLayerId(layer.getId(), false)); }; } diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.ts b/x-pack/plugins/maps/public/actions/map_actions.test.ts index d222d8e5b0466..935ca332baa22 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.test.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.test.ts @@ -277,7 +277,6 @@ describe('map_actions', () => { const query = { language: 'kuery', query: '', - queryLastTriggeredAt: '2020-08-14T15:07:12.276Z', }; const timeFilters = { from: 'now-1y', to: 'now' }; const filters = [ @@ -327,7 +326,6 @@ describe('map_actions', () => { const newQuery = { language: 'kuery', query: 'foobar', - queryLastTriggeredAt: '2020-08-14T15:07:12.276Z', }; const setQueryAction = await setQuery({ query: newQuery, @@ -384,7 +382,6 @@ describe('map_actions', () => { }); await setQueryAction(dispatchMock, getStoreMock); - // Only checking calls length instead of calls because queryLastTriggeredAt changes on this run expect(dispatchMock.mock.calls.length).toEqual(2); }); }); diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index c1db14347460f..ba52203ce486b 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -51,7 +51,11 @@ import { UPDATE_MAP_SETTING, UPDATE_EDIT_STATE, } from './map_action_constants'; -import { autoFitToBounds, syncDataForAllLayers, syncDataForLayer } from './data_request_actions'; +import { + autoFitToBounds, + syncDataForAllLayers, + syncDataForLayerDueToDrawing, +} from './data_request_actions'; import { addLayer, addLayerWithoutDataSync } from './layer_actions'; import { MapSettings } from '../reducers/map'; import { DrawState, MapCenterAndZoom, MapExtent, Timeslice } from '../../common/descriptor_types'; @@ -172,7 +176,7 @@ export function mapExtentChanged(mapExtentState: MapExtentState) { }); } - dispatch(syncDataForAllLayers()); + dispatch(syncDataForAllLayers(false)); }; } @@ -212,10 +216,6 @@ export function clearGoto() { return { type: CLEAR_GOTO }; } -function generateQueryTimestamp() { - return new Date().toISOString(); -} - export function setQuery({ query, timeFilters, @@ -240,11 +240,6 @@ export function setQuery({ getState: () => MapStoreState ) => { const prevQuery = getQuery(getState()); - const prevTriggeredAt = - prevQuery && prevQuery.queryLastTriggeredAt - ? prevQuery.queryLastTriggeredAt - : generateQueryTimestamp(); - const prevTimeFilters = getTimeFilters(getState()); function getNextTimeslice() { @@ -261,11 +256,7 @@ export function setQuery({ const nextQueryContext = { timeFilters: timeFilters ? timeFilters : prevTimeFilters, timeslice: getNextTimeslice(), - query: { - ...(query ? query : prevQuery), - // ensure query changes to trigger re-fetch when "Refresh" clicked - queryLastTriggeredAt: forceRefresh ? generateQueryTimestamp() : prevTriggeredAt, - }, + query: query ? query : prevQuery, filters: filters ? filters : getFilters(getState()), searchSessionId: searchSessionId ? searchSessionId : getSearchSessionId(getState()), searchSessionMapBuffer, @@ -280,7 +271,7 @@ export function setQuery({ searchSessionMapBuffer: getSearchSessionMapBuffer(getState()), }; - if (_.isEqual(nextQueryContext, prevQueryContext)) { + if (!forceRefresh && _.isEqual(nextQueryContext, prevQueryContext)) { // do nothing if query context has not changed return; } @@ -293,7 +284,7 @@ export function setQuery({ if (getMapSettings(getState()).autoFitToDataBounds) { dispatch(autoFitToBounds()); } else { - await dispatch(syncDataForAllLayers()); + await dispatch(syncDataForAllLayers(forceRefresh)); } }; } @@ -372,7 +363,7 @@ export function addNewFeatureToIndex(geometry: Geometry | Position[]) { try { await layer.addFeature(geometry); - await dispatch(syncDataForLayer(layer, true)); + await dispatch(syncDataForLayerDueToDrawing(layer)); } catch (e) { getToasts().addError(e, { title: i18n.translate('xpack.maps.mapActions.addFeatureError', { @@ -399,7 +390,7 @@ export function deleteFeatureFromIndex(featureId: string) { } try { await layer.deleteFeature(featureId); - await dispatch(syncDataForLayer(layer, true)); + await dispatch(syncDataForLayerDueToDrawing(layer)); } catch (e) { getToasts().addError(e, { title: i18n.translate('xpack.maps.mapActions.removeFeatureError', { diff --git a/x-pack/plugins/maps/public/actions/ui_actions.ts b/x-pack/plugins/maps/public/actions/ui_actions.ts index 27e11a938e22b..70e24283ef48f 100644 --- a/x-pack/plugins/maps/public/actions/ui_actions.ts +++ b/x-pack/plugins/maps/public/actions/ui_actions.ts @@ -12,7 +12,7 @@ import { getFlyoutDisplay } from '../selectors/ui_selectors'; import { FLYOUT_STATE } from '../reducers/ui'; import { setQuery, trackMapSettings } from './map_actions'; import { setSelectedLayer } from './layer_actions'; -import { DRAW_MODE } from '../../common'; +import { DRAW_MODE } from '../../common/constants'; import { UPDATE_EDIT_STATE } from './map_action_constants'; export const UPDATE_FLYOUT = 'UPDATE_FLYOUT'; diff --git a/x-pack/plugins/maps/public/classes/layers/__fixtures__/mock_sync_context.ts b/x-pack/plugins/maps/public/classes/layers/__fixtures__/mock_sync_context.ts index 16aca6760c4d5..b81ba6c854629 100644 --- a/x-pack/plugins/maps/public/classes/layers/__fixtures__/mock_sync_context.ts +++ b/x-pack/plugins/maps/public/classes/layers/__fixtures__/mock_sync_context.ts @@ -7,21 +7,22 @@ import sinon from 'sinon'; import { DataRequestContext } from '../../../actions'; -import { DataMeta, MapFilters } from '../../../../common/descriptor_types'; +import { DataRequestMeta, DataFilters } from '../../../../common/descriptor_types'; export class MockSyncContext implements DataRequestContext { - dataFilters: MapFilters; + dataFilters: DataFilters; isRequestStillActive: (dataId: string, requestToken: symbol) => boolean; onLoadError: (dataId: string, requestToken: symbol, errorMessage: string) => void; registerCancelCallback: (requestToken: symbol, callback: () => void) => void; - startLoading: (dataId: string, requestToken: symbol, meta: DataMeta) => void; - stopLoading: (dataId: string, requestToken: symbol, data: object, meta: DataMeta) => void; + startLoading: (dataId: string, requestToken: symbol, meta: DataRequestMeta) => void; + stopLoading: (dataId: string, requestToken: symbol, data: object, meta: DataRequestMeta) => void; onJoinError: (errorMessage: string) => void; updateSourceData: (newData: unknown) => void; - forceRefresh: boolean; + forceRefreshDueToDrawing: boolean; + isForceRefresh: boolean; - constructor({ dataFilters }: { dataFilters: Partial }) { - const mapFilters: MapFilters = { + constructor({ dataFilters }: { dataFilters: Partial }) { + const mapFilters: DataFilters = { filters: [], timeFilters: { from: 'now', @@ -41,6 +42,7 @@ export class MockSyncContext implements DataRequestContext { this.stopLoading = sinon.spy(); this.onJoinError = sinon.spy(); this.updateSourceData = sinon.spy(); - this.forceRefresh = false; + this.forceRefreshDueToDrawing = false; + this.isForceRefresh = false; } } diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx index 4c81ee67e1daf..d4138ccfaf319 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx @@ -63,6 +63,7 @@ describe('getSource', () => { ...documentSourceDescriptor, applyGlobalQuery: false, applyGlobalTime: false, + applyForceRefresh: false, }), layerDescriptor: BlendedVectorLayer.createDescriptor( { @@ -86,6 +87,7 @@ describe('getSource', () => { geoField: sourceDescriptor.geoField, applyGlobalQuery: sourceDescriptor.applyGlobalQuery, applyGlobalTime: sourceDescriptor.applyGlobalTime, + applyForceRefresh: sourceDescriptor.applyForceRefresh, }; expect(abstractEsSourceDescriptor).toEqual({ type: sourceDescriptor.type, @@ -94,6 +96,7 @@ describe('getSource', () => { indexPatternId: 'myIndexPattern', applyGlobalQuery: false, applyGlobalTime: false, + applyForceRefresh: false, } as AbstractESSourceDescriptor); }); }); diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index 5db22ff5354a8..d2734265f3bc3 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -60,6 +60,7 @@ function getClusterSource(documentSource: IESSource, documentStyle: IVectorStyle }); clusterSourceDescriptor.applyGlobalQuery = documentSource.getApplyGlobalQuery(); clusterSourceDescriptor.applyGlobalTime = documentSource.getApplyGlobalTime(); + clusterSourceDescriptor.applyForceRefresh = documentSource.getApplyForceRefresh(); clusterSourceDescriptor.metrics = [ { type: AGG_TYPE.COUNT, @@ -290,16 +291,18 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { async syncData(syncContext: DataRequestContext) { const dataRequestId = ACTIVE_COUNT_DATA_ID; const requestToken = Symbol(`layer-active-count:${this.getId()}`); - const searchFilters: VectorSourceRequestMeta = await this._getSearchFilters( + const requestMeta: VectorSourceRequestMeta = await this._getVectorSourceRequestMeta( + syncContext.isForceRefresh, syncContext.dataFilters, this.getSource(), this.getCurrentStyle() ); const source = this.getSource(); - const canSkipFetch = await canSkipSourceUpdate({ + + const canSkipSourceFetch = await canSkipSourceUpdate({ source, prevDataRequest: this.getDataRequest(dataRequestId), - nextMeta: searchFilters, + nextRequestMeta: requestMeta, extentAware: source.isFilterByMapBounds(), getUpdateDueToTimeslice: (timeslice?: Timeslice) => { return this._getUpdateDueToTimesliceFromSourceRequestMeta(source, timeslice); @@ -308,7 +311,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { let activeSource; let activeStyle; - if (canSkipFetch) { + if (canSkipSourceFetch) { // Even when source fetch is skipped, need to call super._syncData to sync StyleMeta and formatters if (this._isClustered) { activeSource = this._clusterSource; @@ -320,12 +323,12 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { } else { let isSyncClustered; try { - syncContext.startLoading(dataRequestId, requestToken, searchFilters); + syncContext.startLoading(dataRequestId, requestToken, requestMeta); isSyncClustered = !(await this._documentSource.canLoadAllDocuments( - searchFilters, + requestMeta, syncContext.registerCancelCallback.bind(null, requestToken) )); - syncContext.stopLoading(dataRequestId, requestToken, { isSyncClustered }, searchFilters); + syncContext.stopLoading(dataRequestId, requestToken, { isSyncClustered }, requestMeta); } catch (error) { if (!(error instanceof DataRequestAbortError) || !isSearchSourceAbortError(error)) { syncContext.onLoadError(dataRequestId, requestToken, error.message); diff --git a/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts b/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts new file mode 100644 index 0000000000000..4d52dacc63782 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; +import type { Query } from 'src/plugins/data/common'; +import { DataFilters, VectorSourceRequestMeta } from '../../../common/descriptor_types'; +import { IVectorSource } from '../sources/vector_source'; +import { ITermJoinSource } from '../sources/term_join_source'; + +export function buildVectorRequestMeta( + source: IVectorSource | ITermJoinSource, + fieldNames: string[], + dataFilters: DataFilters, + sourceQuery: Query | null | undefined, + isForceRefresh: boolean +): VectorSourceRequestMeta { + return { + ...dataFilters, + fieldNames: _.uniq(fieldNames).sort(), + geogridPrecision: source.getGeoGridPrecision(dataFilters.zoom), + sourceQuery: sourceQuery ? sourceQuery : undefined, + applyGlobalQuery: source.getApplyGlobalQuery(), + applyGlobalTime: source.getApplyGlobalTime(), + sourceMeta: source.getSyncMeta(), + applyForceRefresh: source.isESSource() ? source.getApplyForceRefresh() : false, + isForceRefresh, + }; +} diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts index a4955a965d77c..6b91e4812a1d6 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts @@ -64,6 +64,7 @@ function createChoroplethLayerDescriptor({ metrics: [metricsDescriptor], applyGlobalQuery: true, applyGlobalTime: true, + applyForceRefresh: true, }, }, ], @@ -148,6 +149,8 @@ export function createEsChoroplethLayerDescriptor({ scalingType: SCALING_TYPES.LIMIT, tooltipProperties: [leftJoinField], applyGlobalQuery: false, + applyGlobalTime: false, + applyForceRefresh: false, }), leftField: leftJoinField, rightIndexPatternId, diff --git a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts index 229532c09f955..408460de28aeb 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts @@ -92,6 +92,7 @@ export function createRegionMapLayerDescriptor({ metrics: [metricsDescriptor], applyGlobalQuery: true, applyGlobalTime: true, + applyForceRefresh: true, }; if (termsSize !== undefined) { termSourceDescriptor.size = termsSize; diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts index d12c8432a4191..d65d114205163 100644 --- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts @@ -10,11 +10,12 @@ import { FeatureCollection } from 'geojson'; import { AbstractLayer } from '../layer'; import { HeatmapStyle } from '../../styles/heatmap/heatmap_style'; import { EMPTY_FEATURE_COLLECTION, LAYER_TYPE } from '../../../../common/constants'; -import { HeatmapLayerDescriptor, MapQuery } from '../../../../common/descriptor_types'; +import { HeatmapLayerDescriptor } from '../../../../common/descriptor_types'; import { ESGeoGridSource } from '../../sources/es_geo_grid_source'; import { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from '../vector_layer'; import { DataRequestContext } from '../../../actions'; import { DataRequestAbortError } from '../../util/data_request'; +import { buildVectorRequestMeta } from '../build_vector_request_meta'; const SCALED_PROPERTY_NAME = '__kbn_heatmap_weight__'; // unique name to store scaled value for weighting @@ -94,21 +95,18 @@ export class HeatmapLayer extends AbstractLayer { return; } - const sourceQuery = this.getQuery() as MapQuery; try { await syncVectorSource({ layerId: this.getId(), layerName: await this.getDisplayName(this.getSource()), prevDataRequest: this.getSourceDataRequest(), - requestMeta: { - ...syncContext.dataFilters, - fieldNames: this.getSource().getFieldNames(), - geogridPrecision: this.getSource().getGeoGridPrecision(syncContext.dataFilters.zoom), - sourceQuery: sourceQuery ? sourceQuery : undefined, - applyGlobalQuery: this.getSource().getApplyGlobalQuery(), - applyGlobalTime: this.getSource().getApplyGlobalTime(), - sourceMeta: this.getSource().getSyncMeta(), - }, + requestMeta: buildVectorRequestMeta( + this.getSource(), + this.getSource().getFieldNames(), + syncContext.dataFilters, + this.getQuery(), + syncContext.isForceRefresh + ), syncContext, source: this.getSource(), getUpdateDueToTimeslice: () => { @@ -194,7 +192,7 @@ export class HeatmapLayer extends AbstractLayer { layerId: this.getId(), syncContext, source: this.getSource(), - sourceQuery: this.getQuery() as MapQuery, + sourceQuery: this.getQuery(), }); } diff --git a/x-pack/plugins/maps/public/classes/layers/layer.test.ts b/x-pack/plugins/maps/public/classes/layers/layer.test.ts index d3d8a94e175eb..dcc183c5c1741 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer.test.ts @@ -84,6 +84,7 @@ describe('cloneDescriptor', () => { type: SOURCE_TYPES.ES_TERM_SOURCE, applyGlobalQuery: true, applyGlobalTime: true, + applyForceRefresh: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx index 5a82cf881e34d..0c3d1dc41d640 100644 --- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx +++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx @@ -11,7 +11,7 @@ import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_re import { NewVectorLayerEditor } from './wizard'; import { DrawLayerIcon } from '../../layers/icons/draw_layer_icon'; import { getFileUpload } from '../../../kibana_services'; -import { LAYER_WIZARD_CATEGORY } from '../../../../common'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; const ADD_VECTOR_DRAWING_LAYER = 'ADD_VECTOR_DRAWING_LAYER'; diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts index 2e57014824a3c..596d2ce86bbe2 100644 --- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts +++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts @@ -6,11 +6,8 @@ */ import { getHttp } from '../../../kibana_services'; -import { - CreateDocSourceResp, - INDEX_SOURCE_API_PATH, - IndexSourceMappings, -} from '../../../../common'; +import { CreateDocSourceResp, IndexSourceMappings } from '../../../../common/types'; +import { INDEX_SOURCE_API_PATH } from '../../../../common/constants'; export const createNewIndexAndPattern = async ({ indexName, diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts index 74ab35e6cb360..8955342824a77 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { emsWorldLayerId } from '../../../../../common'; +import { emsWorldLayerId } from '../../../../../common/constants'; jest.mock('../../../../kibana_services', () => { return { @@ -61,6 +61,7 @@ describe('createLayerDescriptor', () => { type: 'avg', }, ], + applyForceRefresh: true, term: 'client.geo.country_iso_code', type: 'ES_TERM_SOURCE', whereQuery: { @@ -201,6 +202,7 @@ describe('createLayerDescriptor', () => { ], requestType: 'heatmap', resolution: 'MOST_FINE', + applyForceRefresh: true, type: 'ES_GEO_GRID', }, style: { @@ -245,6 +247,7 @@ describe('createLayerDescriptor', () => { ], requestType: 'point', resolution: 'MOST_FINE', + applyForceRefresh: true, type: 'ES_GEO_GRID', }, style: { diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts index 0b57afb38d585..d55040172f830 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts @@ -179,6 +179,7 @@ export function createLayerDescriptor({ whereQuery: apmSourceQuery, applyGlobalQuery: true, applyGlobalTime: true, + applyForceRefresh: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts index a3a3e8b20f678..cc6a7dfd9e796 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts @@ -47,6 +47,7 @@ describe('createLayerDescriptor', () => { geoField: 'client.geo.location', id: '12345', indexPatternId: 'id', + applyForceRefresh: true, scalingType: 'TOP_HITS', sortField: '', sortOrder: 'desc', @@ -156,6 +157,7 @@ describe('createLayerDescriptor', () => { geoField: 'server.geo.location', id: '12345', indexPatternId: 'id', + applyForceRefresh: true, scalingType: 'TOP_HITS', sortField: '', sortOrder: 'desc', @@ -274,6 +276,7 @@ describe('createLayerDescriptor', () => { type: 'sum', }, ], + applyForceRefresh: true, sourceGeoField: 'client.geo.location', type: 'ES_PEW_PEW', }, @@ -386,6 +389,7 @@ describe('createLayerDescriptor', () => { geoField: 'source.geo.location', id: '12345', indexPatternId: 'id', + applyForceRefresh: true, scalingType: 'TOP_HITS', sortField: '', sortOrder: 'desc', @@ -495,6 +499,7 @@ describe('createLayerDescriptor', () => { geoField: 'destination.geo.location', id: '12345', indexPatternId: 'id', + applyForceRefresh: true, scalingType: 'TOP_HITS', sortField: '', sortOrder: 'desc', @@ -613,6 +618,7 @@ describe('createLayerDescriptor', () => { type: 'sum', }, ], + applyForceRefresh: true, sourceGeoField: 'source.geo.location', type: 'ES_PEW_PEW', }, @@ -724,6 +730,7 @@ describe('createLayerDescriptor', () => { filterByMapBounds: true, geoField: 'client.geo.location', id: '12345', + applyForceRefresh: true, indexPatternId: 'id', scalingType: 'TOP_HITS', sortField: '', @@ -835,6 +842,7 @@ describe('createLayerDescriptor', () => { id: '12345', indexPatternId: 'id', scalingType: 'TOP_HITS', + applyForceRefresh: true, sortField: '', sortOrder: 'desc', tooltipProperties: [ @@ -952,6 +960,7 @@ describe('createLayerDescriptor', () => { type: 'sum', }, ], + applyForceRefresh: true, sourceGeoField: 'client.geo.location', type: 'ES_PEW_PEW', }, diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index 6277411fa053a..0d365cc5fc8c4 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -38,7 +38,6 @@ import { } from '../../../../common/descriptor_types'; import { MVTSingleLayerVectorSourceConfig } from '../../sources/mvt_single_layer_vector_source/types'; import { canSkipSourceUpdate } from '../../util/can_skip_fetch'; -import { isRefreshOnlyQuery } from '../../util/is_refresh_only_query'; import { CustomIconAndTooltipContent } from '../layer'; export class TiledVectorLayer extends VectorLayer { @@ -113,9 +112,11 @@ export class TiledVectorLayer extends VectorLayer { stopLoading, onLoadError, dataFilters, + isForceRefresh, }: DataRequestContext) { const requestToken: symbol = Symbol(`layer-${this.getId()}-${SOURCE_DATA_REQUEST_ID}`); - const searchFilters: VectorSourceRequestMeta = await this._getSearchFilters( + const requestMeta: VectorSourceRequestMeta = await this._getVectorSourceRequestMeta( + isForceRefresh, dataFilters, this.getSource(), this._style as IVectorStyle @@ -132,7 +133,7 @@ export class TiledVectorLayer extends VectorLayer { extentAware: false, // spatial extent knowledge is already fully automated by tile-loading based on pan-zooming source: this.getSource(), prevDataRequest, - nextMeta: searchFilters, + nextRequestMeta: requestMeta, getUpdateDueToTimeslice: (timeslice?: Timeslice) => { // TODO use meta features to determine if tiles already contain features for timeslice. return true; @@ -145,18 +146,17 @@ export class TiledVectorLayer extends VectorLayer { } } - startLoading(SOURCE_DATA_REQUEST_ID, requestToken, searchFilters); + startLoading(SOURCE_DATA_REQUEST_ID, requestToken, requestMeta); try { - const prevMeta = prevDataRequest ? prevDataRequest.getMeta() : undefined; const prevData = prevDataRequest ? (prevDataRequest.getData() as MVTSingleLayerVectorSourceConfig) : undefined; const urlToken = - !prevData || isRefreshOnlyQuery(prevMeta ? prevMeta.query : undefined, searchFilters.query) + !prevData || (requestMeta.isForceRefresh && requestMeta.applyForceRefresh) ? uuid() : prevData.urlToken; - const newUrlTemplateAndMeta = await this._source.getUrlTemplateWithMeta(searchFilters); + const newUrlTemplateAndMeta = await this._source.getUrlTemplateWithMeta(requestMeta); let urlTemplate; if (newUrlTemplateAndMeta.refreshTokenParamName) { diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx index 346e59f60af32..6bc72f09e9387 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx @@ -7,6 +7,7 @@ import { FeatureCollection } from 'geojson'; import type { Map as MbMap } from '@kbn/mapbox-gl'; +import type { Query } from 'src/plugins/data/common'; import { EMPTY_FEATURE_COLLECTION, SOURCE_BOUNDS_DATA_REQUEST_ID, @@ -14,9 +15,8 @@ import { VECTOR_SHAPE_TYPE, } from '../../../../common/constants'; import { - DataMeta, + DataRequestMeta, MapExtent, - MapQuery, Timeslice, VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; @@ -77,15 +77,17 @@ export async function syncVectorSource({ } = syncContext; const dataRequestId = SOURCE_DATA_REQUEST_ID; const requestToken = Symbol(`${layerId}-${dataRequestId}`); - const canSkipFetch = syncContext.forceRefresh + + const canSkipFetch = syncContext.forceRefreshDueToDrawing ? false : await canSkipSourceUpdate({ source, prevDataRequest, - nextMeta: requestMeta, + nextRequestMeta: requestMeta, extentAware: source.isFilterByMapBounds(), getUpdateDueToTimeslice, }); + if (canSkipFetch) { return { refreshed: false, @@ -113,11 +115,11 @@ export async function syncVectorSource({ ) { layerFeatureCollection.features.push(...getCentroidFeatures(layerFeatureCollection)); } - const responseMeta: DataMeta = meta ? { ...meta } : {}; + const responseMeta: DataRequestMeta = meta ? { ...meta } : {}; if (requestMeta.applyGlobalTime && (await source.isTimeAware())) { - const timesiceMaskField = await source.getTimesliceMaskFieldName(); - if (timesiceMaskField) { - responseMeta.timesiceMaskField = timesiceMaskField; + const timesliceMaskField = await source.getTimesliceMaskFieldName(); + if (timesliceMaskField) { + responseMeta.timesliceMaskField = timesliceMaskField; } } stopLoading(dataRequestId, requestToken, layerFeatureCollection, responseMeta); @@ -142,7 +144,7 @@ export async function getVectorSourceBounds({ layerId: string; syncContext: DataRequestContext; source: IVectorSource; - sourceQuery: MapQuery | null; + sourceQuery: Query | null; }): Promise { const { startLoading, stopLoading, registerCancelCallback, dataFilters } = syncContext; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 54e0c00141cd3..c4903ddb325b2 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -11,6 +11,7 @@ import type { AnyLayer as MbLayer, GeoJSONSource as MbGeoJSONSource, } from '@kbn/mapbox-gl'; +import type { Query } from 'src/plugins/data/common'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry, Position } from 'geojson'; import _ from 'lodash'; import { EuiIcon } from '@elastic/eui'; @@ -48,14 +49,13 @@ import { } from '../../util/mb_filter_expressions'; import { DynamicStylePropertyOptions, - MapFilters, - MapQuery, + DataFilters, StyleMetaDescriptor, Timeslice, - VectorJoinSourceRequestMeta, VectorLayerDescriptor, VectorSourceRequestMeta, VectorStyleRequestMeta, + VectorJoinSourceRequestMeta, } from '../../../../common/descriptor_types'; import { ISource } from '../../sources/source'; import { IVectorSource } from '../../sources/vector_source'; @@ -70,6 +70,7 @@ import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { ITermJoinSource } from '../../sources/term_join_source'; import { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; import { JoinState, performInnerJoins } from './perform_inner_joins'; +import { buildVectorRequestMeta } from '../build_vector_request_meta'; export interface VectorLayerArguments { source: IVectorSource; @@ -266,7 +267,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { layerId: this.getId(), syncContext, source: this.getSource(), - sourceQuery: this.getQuery() as MapQuery, + sourceQuery: this.getQuery(), }); } @@ -332,29 +333,31 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { onLoadError, registerCancelCallback, dataFilters, + isForceRefresh, }: { join: InnerJoin } & DataRequestContext): Promise { const joinSource = join.getRightJoinSource(); const sourceDataId = join.getSourceDataRequestId(); const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`); - const searchFilters: VectorJoinSourceRequestMeta = { - ...dataFilters, - fieldNames: joinSource.getFieldNames(), - sourceQuery: joinSource.getWhereQuery(), - applyGlobalQuery: joinSource.getApplyGlobalQuery(), - applyGlobalTime: joinSource.getApplyGlobalTime(), - sourceMeta: joinSource.getSyncMeta(), - }; - const prevDataRequest = this.getDataRequest(sourceDataId); + const joinRequestMeta: VectorJoinSourceRequestMeta = buildVectorRequestMeta( + joinSource, + joinSource.getFieldNames(), + dataFilters, + joinSource.getWhereQuery(), + isForceRefresh + ) as VectorJoinSourceRequestMeta; + + const prevDataRequest = this.getDataRequest(sourceDataId); const canSkipFetch = await canSkipSourceUpdate({ source: joinSource, prevDataRequest, - nextMeta: searchFilters, + nextRequestMeta: joinRequestMeta, extentAware: false, // join-sources are term-aggs that are spatially unaware (e.g. ESTermSource/TableSource). getUpdateDueToTimeslice: () => { return true; }, }); + if (canSkipFetch) { return { dataHasChanged: false, @@ -364,10 +367,10 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } try { - startLoading(sourceDataId, requestToken, searchFilters); + startLoading(sourceDataId, requestToken, joinRequestMeta); const leftSourceName = await this._source.getDisplayName(); const propertiesMap = await joinSource.getPropertiesMap( - searchFilters, + joinRequestMeta, leftSourceName, join.getLeftField().getName(), registerCancelCallback.bind(null, requestToken) @@ -396,8 +399,9 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return await Promise.all(joinSyncs); } - async _getSearchFilters( - dataFilters: MapFilters, + async _getVectorSourceRequestMeta( + isForceRefresh: boolean, + dataFilters: DataFilters, source: IVectorSource, style: IVectorStyle ): Promise { @@ -411,17 +415,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { if (timesliceMaskFieldName) { fieldNames.push(timesliceMaskFieldName); } - - const sourceQuery = this.getQuery() as MapQuery; - return { - ...dataFilters, - fieldNames: _.uniq(fieldNames).sort(), - geogridPrecision: source.getGeoGridPrecision(dataFilters.zoom), - sourceQuery: sourceQuery ? sourceQuery : undefined, - applyGlobalQuery: source.getApplyGlobalQuery(), - applyGlobalTime: source.getApplyGlobalTime(), - sourceMeta: source.getSyncMeta(), - }; + return buildVectorRequestMeta(source, fieldNames, dataFilters, this.getQuery(), isForceRefresh); } async _syncSourceStyleMeta( @@ -429,7 +423,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { source: IVectorSource, style: IVectorStyle ) { - const sourceQuery = this.getQuery() as MapQuery; + const sourceQuery = this.getQuery(); return this._syncStyleMeta({ source, style, @@ -481,7 +475,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { dataRequestId: string; dynamicStyleProps: Array>; source: IVectorSource | ITermJoinSource; - sourceQuery?: MapQuery; + sourceQuery?: Query; style: IVectorStyle; } & DataRequestContext) { if (!source.isESSource() || dynamicStyleProps.length === 0) { @@ -641,7 +635,12 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { layerId: this.getId(), layerName: await this.getDisplayName(source), prevDataRequest: this.getSourceDataRequest(), - requestMeta: await this._getSearchFilters(syncContext.dataFilters, source, style), + requestMeta: await this._getVectorSourceRequestMeta( + syncContext.isForceRefresh, + syncContext.dataFilters, + source, + style + ), syncContext, source, getUpdateDueToTimeslice: (timeslice?: Timeslice) => { @@ -995,9 +994,9 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } const prevMeta = this.getSourceDataRequest()?.getMeta(); - return prevMeta !== undefined && prevMeta.timesiceMaskField !== undefined + return prevMeta !== undefined && prevMeta.timesliceMaskField !== undefined ? { - timesiceMaskField: prevMeta.timesiceMaskField, + timesliceMaskField: prevMeta.timesliceMaskField, timeslice, } : undefined; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts index 20627f42b3d2d..fe3c6d27ef588 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts @@ -7,7 +7,7 @@ import { ITileLayerArguments } from '../tile_layer/tile_layer'; import { SOURCE_TYPES } from '../../../../common/constants'; -import { MapFilters, XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; +import { DataFilters, XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; import { ITMSSource, AbstractTMSSource } from '../../sources/tms_source'; import { ILayer } from '../layer'; import { VectorTileLayer } from './vector_tile_layer'; @@ -63,7 +63,7 @@ describe('VectorTileLayer', () => { onLoadError: (requestId: string, token: string, message: string) => { actualErrorMessage = message; }, - dataFilters: ({ foo: 'bar' } as unknown) as MapFilters, + dataFilters: ({ foo: 'bar' } as unknown) as DataFilters, } as unknown) as DataRequestContext; await layer.syncData(mockContext); diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts index 360f00b486a38..7db3652011e9a 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts @@ -40,6 +40,7 @@ class TestESAggSource extends AbstractESAggSource { metrics, applyGlobalQuery: true, applyGlobalTime: true, + applyForceRefresh: true, }, [] ); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index efbb755f2a1f7..41d5715e47b8e 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -155,15 +155,16 @@ describe('ESGeoGridSource', () => { extent, applyGlobalQuery: true, applyGlobalTime: true, + applyForceRefresh: true, fieldNames: [], buffer: extent, sourceQuery: { query: '', language: 'KQL', - queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', }, sourceMeta: null, zoom: 0, + isForceRefresh: false, }; describe('getGeoJsonWithMeta', () => { @@ -315,7 +316,7 @@ describe('ESGeoGridSource', () => { expect(urlTemplateWithMeta.minSourceZoom).toBe(0); expect(urlTemplateWithMeta.maxSourceZoom).toBe(24); expect(urlTemplateWithMeta.urlTemplate).toEqual( - "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point" + "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point" ); }); @@ -327,7 +328,7 @@ describe('ESGeoGridSource', () => { expect( urlTemplateWithMeta.urlTemplate.startsWith( - "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1" + "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1" ) ).toBe(true); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts index 0c15afff6b051..4a818d898a190 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts @@ -20,7 +20,7 @@ describe('getSourceTooltipContent', () => { const sourceDataRequest = new DataRequest({ data: {}, dataId: 'source', - dataMeta: { + dataRequestMeta: { areResultsTrimmed: false, areEntitiesTrimmed: false, entityCount: 70, @@ -39,7 +39,7 @@ describe('getSourceTooltipContent', () => { const sourceDataRequest = new DataRequest({ data: {}, dataId: 'source', - dataMeta: { + dataRequestMeta: { areResultsTrimmed: true, areEntitiesTrimmed: true, entityCount: 1000, @@ -58,7 +58,7 @@ describe('getSourceTooltipContent', () => { const sourceDataRequest = new DataRequest({ data: {}, dataId: 'source', - dataMeta: { + dataRequestMeta: { areResultsTrimmed: false, areEntitiesTrimmed: false, entityCount: 70, @@ -77,7 +77,7 @@ describe('getSourceTooltipContent', () => { const sourceDataRequest = new DataRequest({ data: {}, dataId: 'source', - dataMeta: { + dataRequestMeta: { areResultsTrimmed: true, areEntitiesTrimmed: true, entityCount: 1000, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.test.ts index e7711a6e28e01..0c68bf6d832ad 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/create_layer_descriptor.test.ts @@ -41,6 +41,7 @@ test('Should create layer descriptor', () => { geoField: 'myGeoField', id: '12345', indexPatternId: 'myIndexPattern', + applyForceRefresh: true, scalingType: 'CLUSTERS', sortField: '', sortOrder: 'desc', diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index 1a5ea8bb14e0e..5bff5d69aeab7 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -102,11 +102,12 @@ describe('ESSearchSource', () => { sourceQuery: { query: 'tooltipField: foobar', language: 'KQL', - queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', }, sourceMeta: null, applyGlobalQuery: true, applyGlobalTime: true, + applyForceRefresh: true, + isForceRefresh: false, }; it('Should only include required props', async () => { @@ -116,7 +117,7 @@ describe('ESSearchSource', () => { }); const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters); expect(urlTemplateWithMeta.urlTemplate).toBe( - `rootdir/api/maps/mvt/getTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fieldsFromSource,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape` + `rootdir/api/maps/mvt/getTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar')),'6':('0':fieldsFromSource,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape` ); }); @@ -130,7 +131,7 @@ describe('ESSearchSource', () => { searchSessionId: '1', }); expect(urlTemplateWithMeta.urlTemplate).toBe( - `rootdir/api/maps/mvt/getTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fieldsFromSource,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape&searchSessionId=1` + `rootdir/api/maps/mvt/getTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar')),'6':('0':fieldsFromSource,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape&searchSessionId=1` ); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 1ca7ddb586293..2b847d218434d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -48,7 +48,7 @@ import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { ESDocField } from '../../fields/es_doc_field'; import { registerSource } from '../source_registry'; import { - DataMeta, + DataRequestMeta, ESSearchSourceDescriptor, Timeslice, VectorSourceRequestMeta, @@ -853,7 +853,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return indexPattern.timeFieldName ? indexPattern.timeFieldName : null; } - getUpdateDueToTimeslice(prevMeta: DataMeta, timeslice?: Timeslice): boolean { + getUpdateDueToTimeslice(prevMeta: DataRequestMeta, timeslice?: Timeslice): boolean { if (this._isTopHits() || this._descriptor.scalingType === SCALING_TYPES.MVT) { return true; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap index 99ce13ce326d6..749d55aeb5da7 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap @@ -34,6 +34,7 @@ exports[`scaling form should disable clusters option when clustering is not supp } delay="regular" + display="inlineBlock" position="left" > } delay="regular" + display="inlineBlock" position="left" > >; registerCancelCallback: (callback: () => void) => void; - sourceQuery?: MapQuery; + sourceQuery?: Query; timeFilters: TimeRange; searchSessionId?: string; }): Promise; @@ -88,6 +88,8 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource typeof descriptor.applyGlobalQuery !== 'undefined' ? descriptor.applyGlobalQuery : true, applyGlobalTime: typeof descriptor.applyGlobalTime !== 'undefined' ? descriptor.applyGlobalTime : true, + applyForceRefresh: + typeof descriptor.applyForceRefresh !== 'undefined' ? descriptor.applyForceRefresh : true, }; } @@ -108,11 +110,11 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource return this._descriptor.applyGlobalTime; } - isFieldAware(): boolean { - return true; + getApplyForceRefresh(): boolean { + return this._descriptor.applyForceRefresh; } - isRefreshTimerAware(): boolean { + isFieldAware(): boolean { return true; } @@ -197,7 +199,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource } async makeSearchSource( - searchFilters: VectorSourceRequestMeta | VectorJoinSourceRequestMeta | BoundsFilters, + searchFilters: VectorSourceRequestMeta | VectorJoinSourceRequestMeta | BoundsRequestMeta, limit: number, initialSearchContext?: object ): Promise { @@ -253,7 +255,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource } async getBoundsForFilters( - boundsFilters: BoundsFilters, + boundsFilters: BoundsRequestMeta, registerCancelCallback: (callback: () => void) => void ): Promise { const searchSource = await this.makeSearchSource(boundsFilters, 0); @@ -421,7 +423,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource style: IVectorStyle; dynamicStyleProps: Array>; registerCancelCallback: (callback: () => void) => void; - sourceQuery?: MapQuery; + sourceQuery?: Query; timeFilters: TimeRange; searchSessionId?: string; }): Promise { diff --git a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts index 6f7d5aa91e2b8..9e21f16d7f30a 100644 --- a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts @@ -6,7 +6,7 @@ */ import { GeoJsonFileSource } from './geojson_file_source'; -import { BoundsFilters } from '../vector_source'; +import { BoundsRequestMeta } from '../vector_source'; import { FIELD_ORIGIN } from '../../../../common/constants'; describe('GeoJsonFileSource', () => { @@ -20,7 +20,7 @@ describe('GeoJsonFileSource', () => { it('should get null bounds', async () => { const geojsonFileSource = new GeoJsonFileSource({}); expect( - await geojsonFileSource.getBoundsForFilters(({} as unknown) as BoundsFilters, () => {}) + await geojsonFileSource.getBoundsForFilters(({} as unknown) as BoundsRequestMeta, () => {}) ).toEqual(null); }); @@ -51,7 +51,7 @@ describe('GeoJsonFileSource', () => { expect(geojsonFileSource.isBoundsAware()).toBe(true); expect( - await geojsonFileSource.getBoundsForFilters(({} as unknown) as BoundsFilters, () => {}) + await geojsonFileSource.getBoundsForFilters(({} as unknown) as BoundsRequestMeta, () => {}) ).toEqual({ maxLat: 3, maxLon: 2, diff --git a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts index 592c2f852f0e7..4de29fde1253c 100644 --- a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts @@ -6,7 +6,7 @@ */ import { Feature, FeatureCollection } from 'geojson'; -import { AbstractVectorSource, BoundsFilters, GeoJsonWithMeta } from '../vector_source'; +import { AbstractVectorSource, BoundsRequestMeta, GeoJsonWithMeta } from '../vector_source'; import { EMPTY_FEATURE_COLLECTION, FIELD_ORIGIN, SOURCE_TYPES } from '../../../../common/constants'; import { InlineFieldDescriptor, @@ -103,7 +103,7 @@ export class GeoJsonFileSource extends AbstractVectorSource { } async getBoundsForFilters( - boundsFilters: BoundsFilters, + boundsFilters: BoundsRequestMeta, registerCancelCallback: (callback: () => void) => void ): Promise { const featureCollection = (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection; diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_source_settings.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_source_settings.test.tsx.snap index c82618a500a33..7926011be4ecc 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_source_settings.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_source_settings.test.tsx.snap @@ -28,6 +28,7 @@ exports[`should not render fields-editor when there is no layername 1`] = ` anchorClassName="eui-alignMiddle" content="Zoom levels where the layer is present in the tiles. This does not correspond directly to visibility. Layer data from lower levels can always be displayed at higher zoom levels (but not vice versa)." delay="regular" + display="inlineBlock" position="top" > @@ -84,6 +85,7 @@ exports[`should render with fields 1`] = ` anchorClassName="eui-alignMiddle" content="Zoom levels where the layer is present in the tiles. This does not correspond directly to visibility. Layer data from lower levels can always be displayed at higher zoom levels (but not vice versa)." delay="regular" + display="inlineBlock" position="top" > @@ -132,6 +134,7 @@ exports[`should render with fields 1`] = ` } delay="regular" + display="inlineBlock" position="top" > @@ -182,6 +185,7 @@ exports[`should render without fields 1`] = ` anchorClassName="eui-alignMiddle" content="Zoom levels where the layer is present in the tiles. This does not correspond directly to visibility. Layer data from lower levels can always be displayed at higher zoom levels (but not vice versa)." delay="regular" + display="inlineBlock" position="top" > diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index 6911cbabdf971..d041e0d3ad5de 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -10,7 +10,7 @@ import uuid from 'uuid/v4'; import React from 'react'; import { GeoJsonProperties, Geometry, Position } from 'geojson'; import { AbstractSource, ImmutableSourceProperty, SourceEditorArgs } from '../source'; -import { BoundsFilters, GeoJsonWithMeta } from '../vector_source'; +import { BoundsRequestMeta, GeoJsonWithMeta } from '../vector_source'; import { ITiledSingleLayerVectorSource } from '../tiled_single_layer_vector_source'; import { FIELD_ORIGIN, @@ -190,7 +190,7 @@ export class MVTSingleLayerVectorSource } async getBoundsForFilters( - boundsFilters: BoundsFilters, + boundsFilters: BoundsRequestMeta, registerCancelCallback: (callback: () => void) => void ): Promise { return null; diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 0ecbde06cf3e2..5b2fc16d18b41 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -16,7 +16,7 @@ import { FieldFormatter, LAYER_TYPE, MAX_ZOOM, MIN_ZOOM } from '../../../common/ import { AbstractSourceDescriptor, Attribution, - DataMeta, + DataRequestMeta, Timeslice, } from '../../../common/descriptor_types'; import { LICENSED_FEATURES } from '../../licensed_features'; @@ -47,7 +47,6 @@ export interface ISource { isFilterByMapBounds(): boolean; isGeoGridPrecisionAware(): boolean; isQueryAware(): boolean; - isRefreshTimerAware(): boolean; isTimeAware(): Promise; getImmutableProperties(): Promise; getAttributionProvider(): (() => Promise) | null; @@ -60,6 +59,7 @@ export interface ISource { getFieldNames(): string[]; getApplyGlobalQuery(): boolean; getApplyGlobalTime(): boolean; + getApplyForceRefresh(): boolean; getIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[]; getGeoGridPrecision(zoom: number): number; @@ -69,7 +69,7 @@ export interface ISource { getMinZoom(): number; getMaxZoom(): number; getLicensedFeatures(): Promise; - getUpdateDueToTimeslice(prevMeta: DataMeta, timeslice?: Timeslice): boolean; + getUpdateDueToTimeslice(prevMeta: DataRequestMeta, timeslice?: Timeslice): boolean; } export class AbstractSource implements ISource { @@ -115,10 +115,6 @@ export class AbstractSource implements ISource { return false; } - isRefreshTimerAware(): boolean { - return false; - } - isGeoGridPrecisionAware(): boolean { return false; } @@ -143,6 +139,10 @@ export class AbstractSource implements ISource { return false; } + getApplyForceRefresh(): boolean { + return false; + } + getIndexPatternIds(): string[] { return []; } @@ -201,7 +201,7 @@ export class AbstractSource implements ISource { return []; } - getUpdateDueToTimeslice(prevMeta: DataMeta, timeslice?: Timeslice): boolean { + getUpdateDueToTimeslice(prevMeta: DataRequestMeta, timeslice?: Timeslice): boolean { return true; } } diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts index 337cc2d601abd..62404cbe942e3 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts @@ -5,11 +5,11 @@ * 2.0. */ +import type { Query } from 'src/plugins/data/common'; import { TableSource } from './table_source'; import { FIELD_ORIGIN } from '../../../../common/constants'; import { - MapFilters, - MapQuery, + DataFilters, VectorJoinSourceRequestMeta, VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; @@ -178,12 +178,12 @@ describe('TableSource', () => { try { await tableSource.getGeoJsonWithMeta( 'foobar', - ({} as unknown) as MapFilters & { + ({} as unknown) as DataFilters & { applyGlobalQuery: boolean; applyGlobalTime: boolean; fieldNames: string[]; geogridPrecision?: number; - sourceQuery?: MapQuery; + sourceQuery?: Query; sourceMeta: VectorSourceSyncMeta; }, () => {}, diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts index 372fb4983d7cc..8730ea7e3d02b 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts @@ -6,11 +6,11 @@ */ import uuid from 'uuid'; +import type { Query } from 'src/plugins/data/common'; import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; import { MapExtent, - MapFilters, - MapQuery, + DataFilters, TableSourceDescriptor, VectorJoinSourceRequestMeta, VectorSourceSyncMeta, @@ -19,10 +19,9 @@ import { Adapters } from '../../../../../../../src/plugins/inspector/common/adap import { ITermJoinSource } from '../term_join_source'; import { BucketProperties, PropertiesMap } from '../../../../common/elasticsearch_util'; import { IField } from '../../fields/field'; -import { Query } from '../../../../../../../src/plugins/data/common/query'; import { AbstractVectorSource, - BoundsFilters, + BoundsRequestMeta, GeoJsonWithMeta, IVectorSource, SourceTooltipConfig, @@ -156,7 +155,7 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource } async getBoundsForFilters( - boundsFilters: BoundsFilters, + boundsFilters: BoundsRequestMeta, registerCancelCallback: (callback: () => void) => void ): Promise { return null; @@ -187,12 +186,12 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource // Could be useful to implement, e.g. to preview raw csv data async getGeoJsonWithMeta( layerName: string, - searchFilters: MapFilters & { + searchFilters: DataFilters & { applyGlobalQuery: boolean; applyGlobalTime: boolean; fieldNames: string[]; geogridPrecision?: number; - sourceQuery?: MapQuery; + sourceQuery?: Query; sourceMeta: VectorSourceSyncMeta; }, registerCancelCallback: (callback: () => void) => void, diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 05f0124310bd8..bf0752d54c426 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { Query } from 'src/plugins/data/common'; import { FeatureCollection, GeoJsonProperties, Geometry, Position } from 'geojson'; import { Filter, TimeRange } from 'src/plugins/data/public'; import { VECTOR_SHAPE_TYPE } from '../../../../common/constants'; @@ -14,7 +15,6 @@ import { IField } from '../../fields/field'; import { ESSearchSourceResponseMeta, MapExtent, - MapQuery, Timeslice, VectorSourceRequestMeta, VectorSourceSyncMeta, @@ -34,12 +34,12 @@ export interface GeoJsonWithMeta { meta?: GeoJsonFetchMeta; } -export interface BoundsFilters { +export interface BoundsRequestMeta { applyGlobalQuery: boolean; applyGlobalTime: boolean; filters: Filter[]; - query?: MapQuery; - sourceQuery?: MapQuery; + query?: Query; + sourceQuery?: Query; timeFilters: TimeRange; timeslice?: Timeslice; } @@ -47,7 +47,7 @@ export interface BoundsFilters { export interface IVectorSource extends ISource { getTooltipProperties(properties: GeoJsonProperties): Promise; getBoundsForFilters( - boundsFilters: BoundsFilters, + layerDataFilters: BoundsRequestMeta, registerCancelCallback: (callback: () => void) => void ): Promise; getGeoJsonWithMeta( @@ -103,7 +103,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc } async getBoundsForFilters( - boundsFilters: BoundsFilters, + boundsFilters: BoundsRequestMeta, registerCancelCallback: (callback: () => void) => void ): Promise { return null; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap index 3f1cddf944374..5b43c5fb95560 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap @@ -33,6 +33,7 @@ exports[`renderLegendDetailRow categorical Should render categorical legend with @@ -113,6 +114,7 @@ exports[`renderLegendDetailRow ordinal Should render custom ordinal legend with @@ -176,6 +178,7 @@ exports[`renderLegendDetailRow ordinal Should render interpolate bands 1`] = ` @@ -305,6 +308,7 @@ exports[`renderLegendDetailRow ordinal Should render percentile bands 1`] = ` @@ -412,6 +416,7 @@ exports[`renderLegendDetailRow ordinal Should render single band when interpolat diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap index 631a6117a111d..11a4fafda29e1 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap @@ -12,6 +12,7 @@ exports[`renderLegendDetailRow Should render categorical legend with breaks 1`] diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.ts similarity index 81% rename from x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js rename to x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.ts index da3cbb9055d43..16d25469025f4 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.ts @@ -7,6 +7,8 @@ import { canSkipSourceUpdate, updateDueToExtent } from './can_skip_fetch'; import { DataRequest } from './data_request'; +import { Filter } from 'src/plugins/data/common'; +import { ISource } from '../sources/source'; describe('updateDueToExtent', () => { it('should be false when buffers are the same', async () => { @@ -91,9 +93,6 @@ describe('canSkipSourceUpdate', () => { isTimeAware: () => { return false; }, - isRefreshTimerAware: () => { - return false; - }, isFilterByMapBounds: () => { return false; }, @@ -107,11 +106,10 @@ describe('canSkipSourceUpdate', () => { return false; }, }; - const prevFilters = []; + const prevFilters: Filter[] = []; const prevQuery = { language: 'kuery', query: 'machine.os.keyword : "win 7"', - queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', }; describe('applyGlobalQuery is false', () => { @@ -119,7 +117,7 @@ describe('canSkipSourceUpdate', () => { const prevDataRequest = new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, query: prevQuery, @@ -128,16 +126,16 @@ describe('canSkipSourceUpdate', () => { }); it('can skip update when filter changes', async () => { - const nextMeta = { + const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, - filters: [prevQuery], + filters: [({} as unknown) as Filter], query: prevQuery, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -146,7 +144,7 @@ describe('canSkipSourceUpdate', () => { }); it('can skip update when query changes', async () => { - const nextMeta = { + const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, query: { @@ -156,9 +154,9 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -166,20 +164,19 @@ describe('canSkipSourceUpdate', () => { expect(canSkipUpdate).toBe(true); }); - it('can not skip update when query is refreshed', async () => { - const nextMeta = { + it('Should not skip refresh update when applyForceRefresh is true', async () => { + const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, - query: { - ...prevQuery, - queryLastTriggeredAt: 'sometime layer when Refresh button is clicked', - }, + query: prevQuery, + isForceRefresh: true, + applyForceRefresh: true, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -187,17 +184,37 @@ describe('canSkipSourceUpdate', () => { expect(canSkipUpdate).toBe(false); }); + it('Should skip refresh update when applyForceRefresh is false', async () => { + const nextRequestMeta = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery, + isForceRefresh: true, + applyForceRefresh: false, + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: (queryAwareSourceMock as unknown) as ISource, + prevDataRequest, + nextRequestMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, + }); + + expect(canSkipUpdate).toBe(true); + }); + it('can not skip update when applyGlobalQuery changes', async () => { - const nextMeta = { + const nextRequestMeta = { applyGlobalQuery: !prevApplyGlobalQuery, filters: prevFilters, query: prevQuery, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -211,7 +228,7 @@ describe('canSkipSourceUpdate', () => { const prevDataRequest = new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, query: prevQuery, @@ -220,16 +237,16 @@ describe('canSkipSourceUpdate', () => { }); it('can not skip update when filter changes', async () => { - const nextMeta = { + const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, - filters: [prevQuery], + filters: [({} as unknown) as Filter], query: prevQuery, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -238,7 +255,7 @@ describe('canSkipSourceUpdate', () => { }); it('can not skip update when query changes', async () => { - const nextMeta = { + const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, query: { @@ -248,9 +265,9 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -259,19 +276,18 @@ describe('canSkipSourceUpdate', () => { }); it('can not skip update when query is refreshed', async () => { - const nextMeta = { + const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, - query: { - ...prevQuery, - queryLastTriggeredAt: 'sometime layer when Refresh button is clicked', - }, + query: prevQuery, + isForceRefresh: true, + applyForceRefresh: true, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -280,16 +296,16 @@ describe('canSkipSourceUpdate', () => { }); it('can not skip update when applyGlobalQuery changes', async () => { - const nextMeta = { + const nextRequestMeta = { applyGlobalQuery: !prevApplyGlobalQuery, filters: prevFilters, query: prevQuery, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: queryAwareSourceMock, + source: (queryAwareSourceMock as unknown) as ISource, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), getUpdateDueToTimeslice, }); @@ -305,9 +321,6 @@ describe('canSkipSourceUpdate', () => { isTimeAware: () => { return true; }, - isRefreshTimerAware: () => { - return false; - }, isFilterByMapBounds: () => { return false; }, @@ -326,15 +339,15 @@ describe('canSkipSourceUpdate', () => { describe('applyGlobalTime', () => { it('can not skip update when applyGlobalTime changes', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: createSourceMock(), + source: (createSourceMock() as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: false, }, extentAware: false, @@ -346,15 +359,15 @@ describe('canSkipSourceUpdate', () => { it('can skip update when applyGlobalTime does not change', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: createSourceMock(), + source: (createSourceMock() as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: true, }, extentAware: false, @@ -368,10 +381,10 @@ describe('canSkipSourceUpdate', () => { describe('timeFilters', () => { it('can not skip update when time range changes', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: createSourceMock(), + source: (createSourceMock() as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-15m', @@ -380,7 +393,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -396,10 +409,10 @@ describe('canSkipSourceUpdate', () => { it('can skip update when time range does not change', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: createSourceMock(), + source: (createSourceMock() as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-15m', @@ -408,7 +421,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-15m', @@ -424,10 +437,10 @@ describe('canSkipSourceUpdate', () => { it('can skip update when time range changes but applyGlobalTime is false', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: createSourceMock(), + source: (createSourceMock() as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: false, timeFilters: { from: 'now-15m', @@ -436,7 +449,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: false, timeFilters: { from: 'now-7d', @@ -455,10 +468,10 @@ describe('canSkipSourceUpdate', () => { const mockSource = createSourceMock(); it('can not skip update when timeslice changes (undefined => provided)', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: mockSource, + source: (mockSource as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -467,7 +480,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -487,10 +500,10 @@ describe('canSkipSourceUpdate', () => { it('can not skip update when timeslice changes', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: mockSource, + source: (mockSource as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -503,7 +516,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -523,10 +536,10 @@ describe('canSkipSourceUpdate', () => { it('can not skip update when timeslice changes (provided => undefined)', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: mockSource, + source: (mockSource as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -539,7 +552,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -555,10 +568,10 @@ describe('canSkipSourceUpdate', () => { it('can skip update when timeslice does not change', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: mockSource, + source: (mockSource as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -571,7 +584,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: true, timeFilters: { from: 'now-7d', @@ -591,10 +604,10 @@ describe('canSkipSourceUpdate', () => { it('can skip update when timeslice changes but applyGlobalTime is false', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: mockSource, + source: (mockSource as unknown) as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { + dataRequestMeta: { applyGlobalTime: false, timeFilters: { from: 'now-7d', @@ -607,7 +620,7 @@ describe('canSkipSourceUpdate', () => { }, data: {}, }), - nextMeta: { + nextRequestMeta: { applyGlobalTime: false, timeFilters: { from: 'now-7d', diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts index b6f03ef3d1c63..69a5c73ba2933 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts @@ -8,15 +8,14 @@ import _ from 'lodash'; import turfBboxPolygon from '@turf/bbox-polygon'; import turfBooleanContains from '@turf/boolean-contains'; -import { isRefreshOnlyQuery } from './is_refresh_only_query'; import { ISource } from '../sources/source'; -import { DataMeta, Timeslice } from '../../../common/descriptor_types'; +import { DataRequestMeta, Timeslice } from '../../../common/descriptor_types'; import { DataRequest } from './data_request'; const SOURCE_UPDATE_REQUIRED = true; const NO_SOURCE_UPDATE_REQUIRED = false; -export function updateDueToExtent(prevMeta: DataMeta = {}, nextMeta: DataMeta = {}) { +export function updateDueToExtent(prevMeta: DataRequestMeta = {}, nextMeta: DataRequestMeta = {}) { const { buffer: previousBuffer } = prevMeta; const { buffer: newBuffer } = nextMeta; @@ -54,30 +53,28 @@ export function updateDueToExtent(prevMeta: DataMeta = {}, nextMeta: DataMeta = export async function canSkipSourceUpdate({ source, prevDataRequest, - nextMeta, + nextRequestMeta, extentAware, getUpdateDueToTimeslice, }: { source: ISource; prevDataRequest: DataRequest | undefined; - nextMeta: DataMeta; + nextRequestMeta: DataRequestMeta; extentAware: boolean; getUpdateDueToTimeslice: (timeslice?: Timeslice) => boolean; }): Promise { + const mustForceRefresh = nextRequestMeta.isForceRefresh && nextRequestMeta.applyForceRefresh; + if (mustForceRefresh) { + // Cannot skip + return false; + } + const timeAware = await source.isTimeAware(); - const refreshTimerAware = await source.isRefreshTimerAware(); const isFieldAware = source.isFieldAware(); const isQueryAware = source.isQueryAware(); const isGeoGridPrecisionAware = source.isGeoGridPrecisionAware(); - if ( - !timeAware && - !refreshTimerAware && - !extentAware && - !isFieldAware && - !isQueryAware && - !isGeoGridPrecisionAware - ) { + if (!timeAware && !extentAware && !isFieldAware && !isQueryAware && !isGeoGridPrecisionAware) { return !!prevDataRequest && prevDataRequest.hasDataOrRequestInProgress(); } @@ -93,26 +90,18 @@ export async function canSkipSourceUpdate({ let updateDueToTime = false; let updateDueToTimeslice = false; if (timeAware) { - updateDueToApplyGlobalTime = prevMeta.applyGlobalTime !== nextMeta.applyGlobalTime; - if (nextMeta.applyGlobalTime) { - updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); - if (!_.isEqual(prevMeta.timeslice, nextMeta.timeslice)) { - updateDueToTimeslice = getUpdateDueToTimeslice(nextMeta.timeslice); + updateDueToApplyGlobalTime = prevMeta.applyGlobalTime !== nextRequestMeta.applyGlobalTime; + if (nextRequestMeta.applyGlobalTime) { + updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextRequestMeta.timeFilters); + if (!_.isEqual(prevMeta.timeslice, nextRequestMeta.timeslice)) { + updateDueToTimeslice = getUpdateDueToTimeslice(nextRequestMeta.timeslice); } } } - let updateDueToRefreshTimer = false; - if (refreshTimerAware && nextMeta.refreshTimerLastTriggeredAt) { - updateDueToRefreshTimer = !_.isEqual( - prevMeta.refreshTimerLastTriggeredAt, - nextMeta.refreshTimerLastTriggeredAt - ); - } - let updateDueToFields = false; if (isFieldAware) { - updateDueToFields = !_.isEqual(prevMeta.fieldNames, nextMeta.fieldNames); + updateDueToFields = !_.isEqual(prevMeta.fieldNames, nextRequestMeta.fieldNames); } let updateDueToQuery = false; @@ -120,42 +109,41 @@ export async function canSkipSourceUpdate({ let updateDueToSourceQuery = false; let updateDueToApplyGlobalQuery = false; if (isQueryAware) { - updateDueToApplyGlobalQuery = prevMeta.applyGlobalQuery !== nextMeta.applyGlobalQuery; - updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextMeta.sourceQuery); - - if (nextMeta.applyGlobalQuery) { - updateDueToQuery = !_.isEqual(prevMeta.query, nextMeta.query); - updateDueToFilters = !_.isEqual(prevMeta.filters, nextMeta.filters); - } else { - // Global filters and query are not applied to layer search request so no re-fetch required. - // Exception is "Refresh" query. - updateDueToQuery = isRefreshOnlyQuery(prevMeta.query, nextMeta.query); + updateDueToApplyGlobalQuery = prevMeta.applyGlobalQuery !== nextRequestMeta.applyGlobalQuery; + updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextRequestMeta.sourceQuery); + + if (nextRequestMeta.applyGlobalQuery) { + updateDueToQuery = !_.isEqual(prevMeta.query, nextRequestMeta.query); + updateDueToFilters = !_.isEqual(prevMeta.filters, nextRequestMeta.filters); } } let updateDueToSearchSessionId = false; - if (timeAware || isQueryAware) { - updateDueToSearchSessionId = prevMeta.searchSessionId !== nextMeta.searchSessionId; + if ((timeAware || isQueryAware) && nextRequestMeta.applyForceRefresh) { + // If the force-refresh flag is turned off, we should ignore refreshes on the dashboard-context + updateDueToSearchSessionId = prevMeta.searchSessionId !== nextRequestMeta.searchSessionId; } let updateDueToPrecisionChange = false; let updateDueToExtentChange = false; if (isGeoGridPrecisionAware) { - updateDueToPrecisionChange = !_.isEqual(prevMeta.geogridPrecision, nextMeta.geogridPrecision); + updateDueToPrecisionChange = !_.isEqual( + prevMeta.geogridPrecision, + nextRequestMeta.geogridPrecision + ); } if (extentAware) { - updateDueToExtentChange = updateDueToExtent(prevMeta, nextMeta); + updateDueToExtentChange = updateDueToExtent(prevMeta, nextRequestMeta); } - const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextMeta.sourceMeta); + const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextRequestMeta.sourceMeta); return ( !updateDueToApplyGlobalTime && !updateDueToTime && !updateDueToTimeslice && - !updateDueToRefreshTimer && !updateDueToExtentChange && !updateDueToFields && !updateDueToQuery && @@ -173,7 +161,7 @@ export function canSkipStyleMetaUpdate({ nextMeta, }: { prevDataRequest: DataRequest | undefined; - nextMeta: DataMeta; + nextMeta: DataRequestMeta; }): boolean { if (!prevDataRequest) { return false; @@ -208,7 +196,7 @@ export function canSkipFormattersUpdate({ nextMeta, }: { prevDataRequest: DataRequest | undefined; - nextMeta: DataMeta; + nextMeta: DataRequestMeta; }): boolean { if (!prevDataRequest) { return false; diff --git a/x-pack/plugins/maps/public/classes/util/data_request.ts b/x-pack/plugins/maps/public/classes/util/data_request.ts index 0eb50af6107e0..3977fd3c9e0a9 100644 --- a/x-pack/plugins/maps/public/classes/util/data_request.ts +++ b/x-pack/plugins/maps/public/classes/util/data_request.ts @@ -7,7 +7,7 @@ /* eslint-disable max-classes-per-file */ -import { DataRequestDescriptor, DataMeta } from '../../../common/descriptor_types'; +import { DataRequestDescriptor, DataRequestMeta } from '../../../common/descriptor_types'; export class DataRequest { private readonly _descriptor: DataRequestDescriptor; @@ -26,11 +26,11 @@ export class DataRequest { return !!this._descriptor.dataRequestToken; } - getMeta(): DataMeta { - if (this._descriptor.dataMetaAtStart) { - return this._descriptor.dataMetaAtStart; - } else if (this._descriptor.dataMeta) { - return this._descriptor.dataMeta; + getMeta(): DataRequestMeta { + if (this._descriptor.dataRequestMetaAtStart) { + return this._descriptor.dataRequestMetaAtStart; + } else if (this._descriptor.dataRequestMeta) { + return this._descriptor.dataRequestMeta; } else { return {}; } diff --git a/x-pack/plugins/maps/public/classes/util/is_refresh_only_query.ts b/x-pack/plugins/maps/public/classes/util/is_refresh_only_query.ts deleted file mode 100644 index 57a11c1d161dc..0000000000000 --- a/x-pack/plugins/maps/public/classes/util/is_refresh_only_query.ts +++ /dev/null @@ -1,24 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { MapQuery } from '../../../common/descriptor_types'; - -// Refresh only query is query where timestamps are different but query is the same. -// Triggered by clicking "Refresh" button in QueryBar -export function isRefreshOnlyQuery( - prevQuery: MapQuery | undefined, - newQuery: MapQuery | undefined -): boolean { - if (!prevQuery || !newQuery) { - return false; - } - return ( - prevQuery.queryLastTriggeredAt !== newQuery.queryLastTriggeredAt && - prevQuery.language === newQuery.language && - prevQuery.query === newQuery.query - ); -} diff --git a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts index eb58963929716..68efd416718fd 100644 --- a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts +++ b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts @@ -15,7 +15,7 @@ import { import { Timeslice } from '../../../common/descriptor_types'; export interface TimesliceMaskConfig { - timesiceMaskField: string; + timesliceMaskField: string; timeslice: Timeslice; } @@ -34,15 +34,15 @@ function getFilterExpression( } if (timesliceMaskConfig) { - allFilters.push(['has', timesliceMaskConfig.timesiceMaskField]); + allFilters.push(['has', timesliceMaskConfig.timesliceMaskField]); allFilters.push([ '>=', - ['get', timesliceMaskConfig.timesiceMaskField], + ['get', timesliceMaskConfig.timesliceMaskField], timesliceMaskConfig.timeslice.from, ]); allFilters.push([ '<', - ['get', timesliceMaskConfig.timesiceMaskField], + ['get', timesliceMaskConfig.timesliceMaskField], timesliceMaskConfig.timeslice.to, ]); } diff --git a/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx b/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx new file mode 100644 index 0000000000000..1b0d021b2efdb --- /dev/null +++ b/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFormRow, EuiSwitch, EuiSwitchEvent, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + applyForceRefresh: boolean; + setApplyForceRefresh: (applyGlobalTime: boolean) => void; +} + +export function ForceRefreshCheckbox({ applyForceRefresh, setApplyForceRefresh }: Props) { + const onRespondToForceRefreshChange = (event: EuiSwitchEvent) => { + setApplyForceRefresh(event.target.checked); + }; + + return ( + + + + + + ); +} diff --git a/x-pack/plugins/maps/public/components/global_filter_checkbox.tsx b/x-pack/plugins/maps/public/components/global_filter_checkbox.tsx index bddb1cfd9cfcd..96805e0c6b5ec 100644 --- a/x-pack/plugins/maps/public/components/global_filter_checkbox.tsx +++ b/x-pack/plugins/maps/public/components/global_filter_checkbox.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { EuiFormRow, EuiSwitch, EuiSwitchEvent, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; interface Props { applyGlobalQuery: boolean; @@ -21,13 +22,20 @@ export function GlobalFilterCheckbox({ applyGlobalQuery, label, setApplyGlobalQu return ( - + + + ); } diff --git a/x-pack/plugins/maps/public/components/global_time_checkbox.tsx b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx index 675426dbb3f76..ae0c50c063b68 100644 --- a/x-pack/plugins/maps/public/components/global_time_checkbox.tsx +++ b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx @@ -6,8 +6,8 @@ */ import React from 'react'; -import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; - +import { EuiFormRow, EuiSwitch, EuiSwitchEvent, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; interface Props { applyGlobalTime: boolean; label: string; @@ -21,13 +21,20 @@ export function GlobalTimeCheckbox({ applyGlobalTime, label, setApplyGlobalTime return ( - + + + ); } diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/index.ts b/x-pack/plugins/maps/public/connected_components/add_layer_panel/index.ts index 67f0c3664acda..ed10b135899d5 100644 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/index.ts +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/index.ts @@ -22,7 +22,7 @@ import { import { MapStoreState } from '../../reducers/store'; import { LayerDescriptor } from '../../../common/descriptor_types'; import { hasPreviewLayers, isLoadingPreviewLayers } from '../../selectors/map_selectors'; -import { DRAW_MODE } from '../../../common'; +import { DRAW_MODE } from '../../../common/constants'; function mapStateToProps(state: MapStoreState) { return { diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx index 6e258e679b96f..c92bd43af14cf 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx @@ -15,8 +15,10 @@ import { EuiSpacer, EuiText, EuiTextColor, - EuiTextAlign, EuiButtonEmpty, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -27,6 +29,7 @@ import { getIndexPatternService, getData } from '../../../kibana_services'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; import { GlobalTimeCheckbox } from '../../../components/global_time_checkbox'; import { ILayer } from '../../../classes/layers/layer'; +import { ForceRefreshCheckbox } from '../../../components/force_refresh_checkbox'; export interface Props { layer: ILayer; @@ -113,6 +116,10 @@ export class FilterEditor extends Component { this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalTime', applyGlobalTime); }; + _onRespondToForceRefreshChange = (applyForceRefresh: boolean) => { + this.props.updateSourceProp(this.props.layer.getId(), 'applyForceRefresh', applyForceRefresh); + }; + _renderQueryPopover() { const layerQuery = this.props.layer.getQuery(); const { SearchBar } = getData().ui; @@ -153,7 +160,7 @@ export class FilterEditor extends Component { const query = this.props.layer.getQuery(); if (!query || !query.query) { return ( - +

{ return ( {query.query} - ); @@ -183,7 +189,7 @@ export class FilterEditor extends Component { defaultMessage: 'Edit filter', }) : i18n.translate('xpack.maps.layerPanel.filterEditor.addFilterButtonLabel', { - defaultMessage: 'Add filter', + defaultMessage: 'Set filter', }); const openButtonIcon = query && query.query ? 'pencil' : 'plusInCircleFilled'; @@ -209,6 +215,7 @@ export class FilterEditor extends Component { setApplyGlobalTime={this._onApplyGlobalTimeChange} /> ) : null; + return ( @@ -222,12 +229,15 @@ export class FilterEditor extends Component { - {this._renderQuery()} - - {this._renderQueryPopover()} + + {this._renderQueryPopover()} + {this._renderQuery()} + + + { /> {globalTimeCheckbox} + ); } diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap index 92330c1d1ddce..0a79b21175d1e 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap @@ -9,6 +9,7 @@ exports[`Should render callout when joins are disabled 1`] = ` ) { return { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/index.ts index 9936d412de9e6..d46d4f53de47f 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/index.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/index.ts @@ -32,7 +32,7 @@ import { import { getDrawMode, getIsFullScreen } from '../../selectors/ui_selectors'; import { getInspectorAdapters } from '../../reducers/non_serializable_instances'; import { MapStoreState } from '../../reducers/store'; -import { DRAW_MODE } from '../../../common'; +import { DRAW_MODE } from '../../../common/constants'; import { TileMetaFeature } from '../../../common/descriptor_types'; import type { MapExtentState } from '../../reducers/map/types'; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts index 009a512023309..a9281898902e4 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts @@ -23,7 +23,7 @@ import { getGeoFieldNames, } from '../../../selectors/map_selectors'; import { getDrawMode } from '../../../selectors/ui_selectors'; -import { DRAW_MODE } from '../../../../common'; +import { DRAW_MODE } from '../../../../common/constants'; import { MapStoreState } from '../../../reducers/store'; function mapStateToProps(state: MapStoreState) { diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap index 05c2ad69af771..047f0087c559f 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap @@ -37,6 +37,7 @@ exports[`LayerControl is rendered 1`] = ` + + + + +

+
-
- + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
-
- + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
+
+ +
+
+
+ Applied globally +
+
+
+
+
+
+
+

+ Trusted App 0 +

- -
+
-
-
- Name -
-
- - - trusted app 1 - - -
-
- OS -
-
- - - Mac - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
+ - - someone + + + + OS -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - someone + + IS - -
-
- Description -
-
- - - Trusted App 1 + + + Windows -
-
+ +
+
+ +
+
-
+
+

+ trusted app 1 +

+
+
-
+
+ +
+
+ class="euiText euiText--small" + > + Last updated +
+
+
+ class="euiText euiText--small" + > + + 1 minute ago + +
- - - - - - - - - - - - - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
-
-
-
-
-
-
-
- -
-
-
-
- +
+
+ Created +
+
+
+
+ + 1 minute ago + +
+
+
+
+
-
-
- -
-
-
-
-
- Name -
-
- - - trusted app 2 - - -
-
- OS -
-
- - - Linux - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
- - - someone - - -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - - someone - - -
-
- Description -
-
- - - Trusted App 2 - - -
-
+ +
+
+
-
+
+ + + + Created by + + + +
+
-
+
- +
+
+ someone
- - - - - - - - - - - - - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
-
-
-
- +
-
-
-
- + +
+
+
+
+ someone +
+
+
+
+
+
+ +
+
+
+ Applied globally +
+
+
+
+
+
+
+

+ Trusted App 1 +

-
-
+
-
-
- Name -
-
- - - trusted app 3 - - -
-
- OS -
-
- - - Windows - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
+ - - someone + + + + OS -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - someone + + IS - -
-
- Description -
-
- - - Trusted App 3 + + + macos -
-
+ +
+
+ +
+
-
+
+

+ trusted app 2 +

+
+
-
+
+ +
+
+ class="euiText euiText--small" + > + Last updated +
+
+
+ class="euiText euiText--small" + > + + 1 minute ago + +
- - - - - - - - - - + + +
+
+
+ +
+
+
+
+
+ Created +
+
+
+
-
- - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
+ + 1 minute ago + +
+
+
+
+
+
+
+ +
+
+
+
+
+
-
- +
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
- Name -
-
- - - trusted app 4 - - -
-
- OS -
-
- - - Mac - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
- - - someone - - -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - - someone - - -
-
- Description -
-
- - - Trusted App 4 - - -
-
-
-
-
-
-
-
-
-
-
+ +
+
+
-
-
-
-
+ someone
- - - - - - - - - - - - - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
-
-
-
- +
-
-
-
- + +
+
+
+
+ someone +
+
+
+
+
+
+ +
+
+
+ Applied globally +
+
+
+
+
+
+
+

+ Trusted App 2 +

- -
+
-
-
- Name -
-
- - - trusted app 5 - - -
-
- OS -
-
- - - Linux - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
+ - - someone + + + + OS -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - someone + + IS - -
-
- Description -
-
- - - Trusted App 5 + + + Linux -
-
+ +
+
+ +
+
-
+
+

+ trusted app 3 +

+
+
-
+
+ +
+
+ class="euiText euiText--small" + > + Last updated +
+
+
+ class="euiText euiText--small" + > + + 1 minute ago + +
- - - - - - - - - - + + +
+
+
+ +
+
+
+
+
+ Created +
+
+
+
-
- - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
+ + 1 minute ago + +
+
+
+
+
+
+
+ +
+
+
+
+
+
-
- + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
-
- +
-
-
-
-
-
-
- -
-
-
-
-
- Name -
-
- - - trusted app 6 - - -
-
- OS -
-
- - - Windows - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
- - - someone - - -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - - someone - - -
-
- Description -
-
- - - Trusted App 6 - - -
-
-
-
-
-
-
-
-
+
- +
+
+ someone
- - - - - - - - - - - - - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
+
+
+ +
+
-
- -
-
-
-
- -
+ Applied globally
+
+
+

+ Trusted App 3 +

+
-
-
+
-
-
- Name -
-
- - - trusted app 7 - - -
-
- OS -
-
- - - Mac - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
+ - - someone + + + + OS -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - someone + + IS - -
-
- Description -
-
- - - Trusted App 7 + + + Windows -
-
+ +
+
+
+
+
-
+
+

+ trusted app 4 +

+
+
-
+
+ +
+
+ class="euiText euiText--small" + > + Last updated +
+
+
+ class="euiText euiText--small" + > + + 1 minute ago + +
- - - - - - - - - - + + +
+
+
+ +
+
+
+
+
-
- - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
+ Created +
+
+
+
+ + 1 minute ago + +
+
+
+
+
+
+
+ +
+
+
+
+
+
-
- + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
-
- + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
-
-
-
-
-
-
-
-
- Name -
-
- - - trusted app 8 - - -
-
- OS -
-
- +
+
- - Linux - - - -
- Date created -
-
- 1 minute ago -
-
- Created by -
-
+
+ Applied globally +
+
+
+
+
+
+
+

+ Trusted App 4 +

+
+
+
+
+
+
+ - - someone + + + + OS - -
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - someone + + IS - -
-
- Description -
-
- - - Trusted App 8 + + + macos -
- +
+
+
+
+
+
-
+
+

+ trusted app 5 +

+
+
-
+
+ +
+
+ class="euiText euiText--small" + > + Last updated +
+
+
+ class="euiText euiText--small" + > + + 1 minute ago + +
- - - - - - - - - - + + +
+
+
+ +
+
+
+
+
+ Created +
+
+
+
-
- - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
+ + 1 minute ago + +
+
+
+
+
+
+
+ +
+
+
+
+
+
-
- + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
-
- + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
+
+ +
+
+
+ Applied globally +
+
+
+
+
+
+
+

+ Trusted App 5 +

-
-
+
-
-
- Name -
-
- - - trusted app 9 - - -
-
- OS -
-
- - - Windows - - -
-
- Date created -
-
- 1 minute ago -
-
- Created by -
-
+ - - someone + + + + OS -
-
- Date modified -
-
- 1 minute ago -
-
- Modified by -
-
- - someone + + IS - -
-
- Description -
-
- - - Trusted App 9 + + + Linux -
-
+ +
+
+ +
+
-
+
+

+ trusted app 6 +

+
+
-
+
+ +
+
+ class="euiText euiText--small" + > + Last updated +
+
+
+ class="euiText euiText--small" + > + + 1 minute ago + +
- - - - - - - - - - - - - -
-
- - - Field - - - - - - Operator - - - - - - Value - - -
-
- - No items found - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
+
- - Remove - - - +
+
+ Created +
+
+
+
+ + 1 minute ago + +
+
+
+
+
+
+
+
+ +
+
+
-
- - - -
-
-
-
-
- +
+
+ + + + Created by + + + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
+
+
+
+
+ + + + Updated by + + + +
+
+
+
+
+ +
+
+
+
+ someone +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ Applied globally +
+
+
+
+
+
+
+

+ Trusted App 6 +

-
-
-
+
+ +
+
+
+
- - -
  • +
  • +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    - +
    +
    + + +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 7 +

    +
    +
    +
    +
    +
    +
    + + + - 5 + OS - - -
  • - … -
  • -
  • - -
  • - - - +
    +
    +
    +
    - - - - - -`; - -exports[`TrustedAppsGrid renders correctly when loading data for the first time 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
    -
    +
    +
    +
    +
    +
    +
    +

    + trusted app 8 +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 8 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + Linux + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + trusted app 9 +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 9 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + Windows + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; + +exports[`TrustedAppsGrid renders correctly when loading data for the first time 1`] = ` +.c1 { + position: relative; + padding-top: 4px; +} + +.c1 .body { + min-height: 40px; +} + +.c1 .body-content { + position: relative; +} + +.c0 .trusted-app + .trusted-app { + margin-top: 24px; +} + +
    +
    @@ -3639,35 +4494,6 @@ exports[`TrustedAppsGrid renders correctly when loading data for the first time `; exports[`TrustedAppsGrid renders correctly when loading data for the second time 1`] = ` -.c2 { - background-color: #f5f7fa; - padding: 16px; -} - -.c5 { - padding: 12px 24px 24px 0; -} - -.c5.c5.c5 { - margin-left: 0; -} - -.c5 .trustedAppsConditionsTable { - margin-left: 16px; -} - -.c3.c3.c3 { - width: 40%; - margin-top: 0; - margin-bottom: 8px; -} - -.c4.c4.c4 { - width: 60%; - margin-top: 0; - margin-bottom: 8px; -} - .c1 { position: relative; padding-top: 4px; @@ -3681,6 +4507,10 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time position: relative; } +.c3.artifactEntryCard + .c2.artifactEntryCard { + margin-top: 24px; +} + .c0 .trusted-app + .trusted-app { margin-top: 24px; } @@ -3702,6179 +4532,7914 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="body-content undefined" >
    +
    +
    +
    +
    +
    +

    + trusted app 0 +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 0 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + Windows + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + trusted app 1 +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 1 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + macos + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + trusted app 2 +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 2 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + Linux + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + trusted app 3 +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 3 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + Windows + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + trusted app 4 +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Created by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + Updated by + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 4 +

    +
    +
    +
    +
    -
    -
    - Name -
    -
    - - - trusted app 0 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 0 + + + macos -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 5 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - + + +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    -
    - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 5 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 1 - - -
    -
    - OS -
    -
    - - - Mac - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone - - -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    + - - someone + + + + OS -
    -
    - Description -
    -
    - - Trusted App 1 + + IS + + + + Linux -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 6 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    - - Remove - - - +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    -
    -
    -
    -
    -
    -
    -
    -
    - Name -
    -
    - - - trusted app 2 - - -
    -
    - OS -
    -
    - - - Linux - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone - - -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - - someone - - -
    -
    - Description -
    -
    - - - Trusted App 2 - - -
    -
    + +
    +
    +
    -
    +
    + + + + Created by + + + +
    +
    -
    +
    - +
    +
    + someone
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    - +
    -
    -
    -
    - + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 6 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 3 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 3 + + + Windows -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 7 +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Created +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally
    +
    +
    +

    + Trusted App 7 +

    +
    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 4 - - -
    -
    - OS -
    -
    - - - Mac - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 4 + + + macos -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 8 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    - - Remove - - - +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    -
    -
    -
    -
    -
    -
    -
    -
    - Name -
    -
    - - - trusted app 5 - - -
    -
    - OS -
    -
    - - - Linux - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone - - -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - - someone - - -
    -
    - Description -
    -
    - - - Trusted App 5 - - -
    -
    + +
    +
    +
    -
    +
    + + + + Created by + + + +
    +
    -
    +
    - +
    +
    + someone
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    - +
    -
    -
    -
    - + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 8 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 6 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 6 + + + Linux -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 9 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - + + +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    -
    - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 9 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + Windows + + + +
    +
    +
    +
    +
    +
    -
    -
    - Name -
    -
    + + Rows per page + : + 10 + + + +
    +
    +
    +
    +
    + + + + + +
    +
    +
    + + + +`; + +exports[`TrustedAppsGrid renders correctly when new page and page size set (not loading yet) 1`] = ` +.c1 { + position: relative; + padding-top: 4px; +} + +.c1 .body { + min-height: 40px; +} + +.c1 .body-content { + position: relative; +} + +.c3.artifactEntryCard + .c2.artifactEntryCard { + margin-top: 24px; +} + +.c0 .trusted-app + .trusted-app { + margin-top: 24px; +} + +
    +
    +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 0 +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Created +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 0 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 8 - - -
    -
    - OS -
    -
    - - - Linux - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 8 + + + Windows -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 1 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - + + +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    -
    - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 1 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 9 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 9 + + + macos -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 2 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - + + +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    -
    - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - +
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    -
    +
    +
    - 3 - - - - -
  • +
  • +
    +
    +
    +
    +
    +

    -

    +
    +
    +
    +
    +
    + + + - 4 + OS - - -
  • - -
  • -
  • - … -
  • -
  • - -
  • - - - +
    +
    +
    +
    - - - - - -`; - -exports[`TrustedAppsGrid renders correctly when new page and page size set (not loading yet) 1`] = ` -.c2 { - background-color: #f5f7fa; - padding: 16px; -} - -.c5 { - padding: 12px 24px 24px 0; -} - -.c5.c5.c5 { - margin-left: 0; -} - -.c5 .trustedAppsConditionsTable { - margin-left: 16px; -} - -.c3.c3.c3 { - width: 40%; - margin-top: 0; - margin-bottom: 8px; -} - -.c4.c4.c4 { - width: 60%; - margin-top: 0; - margin-bottom: 8px; -} - -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
    -
    -
    -
    -
    -
    -
    - Name -
    -
    - - - trusted app 0 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone - - -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - + trusted app 3 + +
    +
    - - someone - - - -
    - Description -
    -
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    - - - Trusted App 0 - - - - + +
    +
    +
    -
    +
    + + + + Created by + + + +
    +
    -
    +
    - +
    +
    + someone
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    - +
    -
    -
    -
    - + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 3 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 1 - - -
    -
    - OS -
    -
    - - - Mac - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 1 + + + Windows -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 4 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    - - Remove - - - +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    -
    -
    -
    -
    -
    -
    -
    -
    - Name -
    -
    - - - trusted app 2 - - -
    -
    - OS -
    -
    - - - Linux - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone - - -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - - someone - - -
    -
    - Description -
    -
    - - - Trusted App 2 - - -
    -
    + +
    +
    +
    -
    +
    + + + + Created by + + + +
    +
    -
    +
    +
    + +
    +
    +
    -
    -
    -
    -
    + someone
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    - +
    -
    -
    -
    - + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 4 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 3 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 3 + + + macos -
    -
    + +
    +
    + +
    +
    -
    +
    +

    + trusted app 5 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - + + +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    -
    - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 5 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 4 - - -
    -
    - OS -
    -
    - - - Mac - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 4 + + + Linux -
    -
    + +
    +
    + +
    +
    -
    +
    +

    + trusted app 6 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    - - Remove - - - +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    -
    -
    - -
    -
    -
    -
    -
    - Name -
    -
    - - - trusted app 5 - - -
    -
    - OS -
    -
    - - - Linux - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone - - -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - - someone - - -
    -
    - Description -
    -
    - - - Trusted App 5 - - -
    -
    + +
    +
    +
    -
    +
    + + + + Created by + + + +
    +
    -
    +
    - +
    +
    + someone
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    - +
    -
    -
    -
    - + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 6 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 6 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 6 + + + Windows -
    -
    + +
    +
    + +
    +
    -
    +
    +

    + trusted app 7 +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Last updated +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Created +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 7 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 7 - - -
    -
    - OS -
    -
    + - - Mac - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 7 + + + macos -
    -
    + +
    +
    +
    +
    +
    -
    +
    +

    + trusted app 8 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    - - Remove - - - +
    +
    + Created +
    +
    +
    +
    + + 1 minute ago + +
    +
    +
    +
    +
    -
    -
    -
    -
    -
    -
    -
    -
    - Name -
    -
    - - - trusted app 8 - - -
    -
    - OS -
    -
    - - - Linux - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    - - - someone - - -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - - someone - - -
    -
    - Description -
    -
    - - - Trusted App 8 - - -
    -
    + +
    +
    +
    -
    +
    + + + + Created by + + + +
    +
    -
    +
    - +
    +
    + someone
    - - - - - - - - - - - - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    -
    -
    -
    - +
    -
    -
    -
    - + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 8 +

    -
    -
    +
    -
    -
    - Name -
    -
    - - - trusted app 9 - - -
    -
    - OS -
    -
    - - - Windows - - -
    -
    - Date created -
    -
    - 1 minute ago -
    -
    - Created by -
    -
    + - - someone + + + + OS -
    -
    - Date modified -
    -
    - 1 minute ago -
    -
    - Modified by -
    -
    - - someone + + IS - -
    -
    - Description -
    -
    - - - Trusted App 9 + + + Linux -
    -
    + +
    +
    + +
    +
    -
    +
    +

    + trusted app 9 +

    +
    +
    -
    +
    + +
    +
    + class="euiText euiText--small" + > + Last updated +
    +
    +
    + class="euiText euiText--small" + > + + 1 minute ago + +
    - - - - - - - - - - + + +
    +
    +
    + +
    +
    +
    +
    +
    + Created +
    +
    +
    +
    -
    - - -
    -
    - - - Field - - - - - - Operator - - - - - - Value - - -
    -
    - - No items found - -
    -
    + + 1 minute ago + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    -
    - + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + someone +
    +
    +
    +
    +
    +
    + +
    +
    +
    + Applied globally +
    +
    +
    +
    +
    +
    +
    +

    + Trusted App 9 +

    +
    +
    +
    +
    +
    +
    + + + + + + OS + + + + + IS + + + + Windows + + + +
    diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx index 74f3f0524b304..d5eca75f9c2b5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; +import { render, act } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; @@ -18,7 +18,6 @@ import { createUserChangedUrlAction, createGlobalNoMiddlewareStore, } from '../../../test_utils'; - import { TrustedAppsGrid } from '.'; import { EuiThemeProvider } from '../../../../../../../../../../src/plugins/kibana_react/common'; @@ -26,6 +25,8 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', })); +jest.mock('../../../../../../common/lib/kibana'); + const now = 111111; const renderList = (store: ReturnType) => { @@ -129,7 +130,15 @@ describe('TrustedAppsGrid', () => { ); store.dispatch = jest.fn(); - (await renderList(store).findAllByTestId('trustedAppDeleteButton'))[0].click(); + const renderResult = renderList(store); + + await act(async () => { + (await renderResult.findAllByTestId('trustedAppCard-header-actions-button'))[0].click(); + }); + + await act(async () => { + (await renderResult.findByTestId('deleteTrustedAppAction')).click(); + }); expect(store.dispatch).toBeCalledWith({ type: 'trustedAppDeletionDialogStarted', diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx index 8d8b52ac62358..ba09d0c7ee0cc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx @@ -5,10 +5,13 @@ * 2.0. */ -import React, { memo, useCallback } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; import { Pagination } from '../../../state'; import { @@ -17,28 +20,31 @@ import { getListItems, getListPagination, isListLoading, + getMapOfPoliciesById, } from '../../../store/selectors'; -import { - useTrustedAppsNavigateCallback, - useTrustedAppsSelector, - useTrustedAppsStoreActionCallback, -} from '../../hooks'; +import { useTrustedAppsNavigateCallback, useTrustedAppsSelector } from '../../hooks'; -import { TrustedAppCard, TrustedAppCardProps } from '../trusted_app_card'; -import { getTrustedAppsListPath } from '../../../../../common/routing'; +import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../../common/routing'; import { PaginatedContent, PaginatedContentProps, } from '../../../../../components/paginated_content'; -import { TrustedApp } from '../../../../../../../common/endpoint/types'; +import { PolicyDetailsRouteState, TrustedApp } from '../../../../../../../common/endpoint/types'; +import { + ArtifactEntryCard, + ArtifactEntryCardProps, +} from '../../../../../components/artifact_entry_card'; +import { AppAction } from '../../../../../../common/store/actions'; +import { APP_ID } from '../../../../../../../common/constants'; +import { useAppUrl } from '../../../../../../common/lib/kibana'; export interface PaginationBarProps { pagination: Pagination; onChange: (pagination: { size: number; index: number }) => void; } -type TrustedAppCardType = typeof TrustedAppCard; +type ArtifactEntryCardType = typeof ArtifactEntryCard; const RootWrapper = styled.div` .trusted-app + .trusted-app { @@ -46,52 +52,140 @@ const RootWrapper = styled.div` } `; +const BACK_TO_TRUSTED_APPS_LABEL = i18n.translate( + 'xpack.securitySolution.trustedapps.grid.policyDetailsLinkBackLabel', + { defaultMessage: 'Back to trusted Applications' } +); + +const EDIT_TRUSTED_APP_ACTION_LABEL = i18n.translate( + 'xpack.securitySolution.trustedapps.grid.cardAction.edit', + { + defaultMessage: 'Edit trusted application', + } +); + +const DELETE_TRUSTED_APP_ACTION_LABEL = i18n.translate( + 'xpack.securitySolution.trustedapps.grid.cardAction.delete', + { + defaultMessage: 'Delete trusted application', + } +); + export const TrustedAppsGrid = memo(() => { const history = useHistory(); + const dispatch = useDispatch>(); + const { getAppUrl } = useAppUrl(); + const pagination = useTrustedAppsSelector(getListPagination); const listItems = useTrustedAppsSelector(getListItems); const isLoading = useTrustedAppsSelector(isListLoading); const error = useTrustedAppsSelector(getListErrorMessage); const location = useTrustedAppsSelector(getCurrentLocation); - - const handleTrustedAppDelete = useTrustedAppsStoreActionCallback((trustedApp) => ({ - type: 'trustedAppDeletionDialogStarted', - payload: { entry: trustedApp }, - })); - - const handleTrustedAppEdit: TrustedAppCardProps['onEdit'] = useCallback( - (trustedApp) => { - history.push( - getTrustedAppsListPath({ - ...location, - show: 'edit', - id: trustedApp.id, - }) - ); - }, - [history, location] - ); + const policyListById = useTrustedAppsSelector(getMapOfPoliciesById); const handlePaginationChange: PaginatedContentProps< TrustedApp, - TrustedAppCardType + ArtifactEntryCardType >['onChange'] = useTrustedAppsNavigateCallback(({ pageIndex, pageSize }) => ({ page_index: pageIndex, page_size: pageSize, })); + const artifactCardPropsPerItem = useMemo(() => { + const cachedCardProps: Record = {}; + + // Casting `listItems` below to remove the `Immutable<>` from it in order to prevent errors + // with common component's props + for (const trustedApp of listItems as TrustedApp[]) { + let policies: ArtifactEntryCardProps['policies']; + + if (trustedApp.effectScope.type === 'policy' && trustedApp.effectScope.policies.length) { + policies = trustedApp.effectScope.policies.reduce< + Required['policies'] + >((policyToNavOptionsMap, policyId) => { + const currentPagePath = getTrustedAppsListPath({ + ...location, + }); + + const policyDetailsPath = getPolicyDetailPath(policyId); + + const routeState: PolicyDetailsRouteState = { + backLink: { + label: BACK_TO_TRUSTED_APPS_LABEL, + navigateTo: [ + APP_ID, + { + path: currentPagePath, + }, + ], + href: getAppUrl({ path: currentPagePath }), + }, + }; + + policyToNavOptionsMap[policyId] = { + navigateAppId: APP_ID, + navigateOptions: { + path: policyDetailsPath, + state: routeState, + }, + href: getAppUrl({ path: policyDetailsPath }), + children: policyListById[policyId]?.name ?? policyId, + }; + return policyToNavOptionsMap; + }, {}); + } + + cachedCardProps[trustedApp.id] = { + item: trustedApp, + policies, + 'data-test-subj': 'trustedAppCard', + actions: [ + { + icon: 'controlsHorizontal', + onClick: () => { + history.push( + getTrustedAppsListPath({ + ...location, + show: 'edit', + id: trustedApp.id, + }) + ); + }, + 'data-test-subj': 'editTrustedAppAction', + children: EDIT_TRUSTED_APP_ACTION_LABEL, + }, + { + icon: 'trash', + onClick: () => { + dispatch({ + type: 'trustedAppDeletionDialogStarted', + payload: { entry: trustedApp }, + }); + }, + 'data-test-subj': 'deleteTrustedAppAction', + children: DELETE_TRUSTED_APP_ACTION_LABEL, + }, + ], + }; + } + + return cachedCardProps; + }, [dispatch, getAppUrl, history, listItems, location, policyListById]); + + const handleArtifactCardProps = useCallback( + (trustedApp: TrustedApp) => { + return artifactCardPropsPerItem[trustedApp.id]; + }, + [artifactCardPropsPerItem] + ); + return ( - + items={listItems as TrustedApp[]} onChange={handlePaginationChange} - ItemComponent={TrustedAppCard} - itemComponentProps={(ta) => ({ - trustedApp: ta, - onDelete: handleTrustedAppDelete, - onEdit: handleTrustedAppEdit, - className: 'trusted-app', - })} + ItemComponent={ArtifactEntryCard} + itemComponentProps={handleArtifactCardProps} loading={isLoading} itemId="id" error={error} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index 9e2cad93fc51f..6ffcf5614a697 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { - TrustedApp, MacosLinuxConditionEntry, WindowsConditionEntry, ConditionEntryField, @@ -61,35 +60,6 @@ export const OPERATOR_TITLES: { [K in OperatorFieldIds]: string } = { }), }; -export const PROPERTY_TITLES: Readonly< - { [K in keyof Omit]: string } -> = { - name: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.name', { - defaultMessage: 'Name', - }), - os: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.os', { - defaultMessage: 'OS', - }), - created_at: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.createdAt', { - defaultMessage: 'Date created', - }), - created_by: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.createdBy', { - defaultMessage: 'Created by', - }), - updated_at: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.updatedAt', { - defaultMessage: 'Date modified', - }), - updated_by: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.updatedBy', { - defaultMessage: 'Modified by', - }), - description: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.description', { - defaultMessage: 'Description', - }), - effectScope: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.effectScope', { - defaultMessage: 'Effect scope', - }), -}; - export const ENTRY_PROPERTY_TITLES: Readonly< { [K in keyof Omit]: string } > = { @@ -104,41 +74,6 @@ export const ENTRY_PROPERTY_TITLES: Readonly< }), }; -export const ACTIONS_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.trustedapps.list.columns.actions', - { - defaultMessage: 'Actions', - } -); - -export const LIST_ACTIONS = { - delete: { - name: i18n.translate('xpack.securitySolution.trustedapps.list.actions.delete', { - defaultMessage: 'Remove', - }), - description: i18n.translate( - 'xpack.securitySolution.trustedapps.list.actions.delete.description', - { - defaultMessage: 'Remove this entry', - } - ), - }, -}; - -export const CARD_DELETE_BUTTON_LABEL = i18n.translate( - 'xpack.securitySolution.trustedapps.card.removeButtonLabel', - { - defaultMessage: 'Remove', - } -); - -export const CARD_EDIT_BUTTON_LABEL = i18n.translate( - 'xpack.securitySolution.trustedapps.card.editButtonLabel', - { - defaultMessage: 'Edit', - } -); - export const GRID_VIEW_TOGGLE_LABEL = i18n.translate( 'xpack.securitySolution.trustedapps.view.toggle.grid', { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index ff7ba8068b4ff..30e170575e2f4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -183,8 +183,13 @@ describe('When on the Trusted Apps Page', () => { beforeEach(async () => { renderResult = await renderWithListData(); + + await act(async () => { + (await renderResult.findAllByTestId('trustedAppCard-header-actions-button'))[0].click(); + }); + act(() => { - fireEvent.click(renderResult.getByTestId('trustedAppEditButton')); + fireEvent.click(renderResult.getByTestId('editTrustedAppAction')); }); }); diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts index ecbb80123e07e..042777491d9c3 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts @@ -14,7 +14,7 @@ import { LayerMappingDetails, } from './types'; import * as i18n from './translations'; -import { SOURCE_TYPES } from '../../../../../maps/common/constants'; +import { SOURCE_TYPES } from '../../../../../maps/common'; const euiVisColorPalette = euiPaletteColorBlind(); // Update field mappings to modify what fields will be returned to map tooltip diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.test.tsx index bc869dead4556..7d5e34df8c8fc 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/map_tool_tip.test.tsx @@ -9,7 +9,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import '../../../../common/mock/match_media'; import { MapToolTipComponent } from './map_tool_tip'; -import { TooltipFeature } from '../../../../../../maps/common/descriptor_types'; +import { TooltipFeature } from '../../../../../../maps/common'; describe('MapToolTip', () => { test('placeholder component renders correctly against snapshot', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap index 596a05c4c8ab4..8a7b179da059f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap @@ -5,6 +5,7 @@ exports[`SortIndicator rendering renders correctly against snapshot 1`] = ` content="Sorted descending" data-test-subj="sort-indicator-tooltip" delay="regular" + display="inlineBlock" position="top" > { const cliDefaults = { @@ -67,106 +71,62 @@ export const run: (options?: RunOptions) => Promise = async ({ }); // touch the Trusted Apps List so it can be created - await kbnClient.request({ - method: 'GET', - path: TRUSTED_APPS_LIST_API, - }); + // and + // setup fleet with endpoint integrations + logger.info('setting up Fleet with endpoint and creating trusted apps list'); + const [installedEndpointPackage] = await Promise.all([ + setupFleetForEndpoint(kbnClient).then((response) => response.endpointPackage), + + kbnClient.request({ + method: 'GET', + path: TRUSTED_APPS_LIST_API, + }), + ]); + + // Setup a list of read endpoint policies and return a method to randomly select one + const randomPolicyId: () => string = await (async () => { + const randomN = (max: number): number => Math.floor(Math.random() * max); + const policyIds: string[] = []; + + for (let i = 0, t = 5; i < t; i++) { + policyIds.push( + ( + await indexFleetEndpointPolicy( + kbnClient, + `Policy for Trusted App assignment ${i + 1}`, + installedEndpointPackage.version + ) + ).integrationPolicies[0].id + ); + } + + return () => policyIds[randomN(policyIds.length)]; + })(); return bluebird.map( Array.from({ length: count }), - () => - kbnClient + async () => { + const body = trustedAppGenerator.generateTrustedAppForCreate(); + + if (body.effectScope.type === 'policy') { + body.effectScope.policies = [randomPolicyId(), randomPolicyId()]; + } + + return kbnClient .request({ method: 'POST', path: TRUSTED_APPS_CREATE_API, - body: generateTrustedAppEntry(), + body, }) .then(({ data }) => { logger.write(data.id); return data; - }), + }); + }, { concurrency: 10 } ); }; -interface GenerateTrustedAppEntryOptions { - os?: OperatingSystem; - name?: string; -} -const generateTrustedAppEntry: (options?: GenerateTrustedAppEntryOptions) => object = ({ - os = randomOperatingSystem(), - name = randomName(), -} = {}): NewTrustedApp => { - const newTrustedApp: NewTrustedApp = { - description: `Generator says we trust ${name}`, - name, - os, - effectScope: { - type: 'global', - }, - entries: [ - { - // @ts-ignore - field: 'process.hash.*', - operator: 'included', - type: 'match', - value: '1234234659af249ddf3e40864e9fb241', - }, - { - // @ts-ignore - field: 'process.executable.caseless', - operator: 'included', - type: 'match', - value: '/one/two/three', - }, - ], - }; - - return newTrustedApp; -}; - -const randomN = (max: number): number => Math.floor(Math.random() * max); - -const randomName = (() => { - const names = [ - 'Symantec Endpoint Security', - 'Bitdefender GravityZone', - 'Malwarebytes', - 'Sophos Intercept X', - 'Webroot Business Endpoint Protection', - 'ESET Endpoint Security', - 'FortiClient', - 'Kaspersky Endpoint Security', - 'Trend Micro Apex One', - 'CylancePROTECT', - 'VIPRE', - 'Norton', - 'McAfee Endpoint Security', - 'AVG AntiVirus', - 'CrowdStrike Falcon', - 'Avast Business Antivirus', - 'Avira Antivirus', - 'Cisco AMP for Endpoints', - 'Eset Endpoint Antivirus', - 'VMware Carbon Black', - 'Palo Alto Networks Traps', - 'Trend Micro', - 'SentinelOne', - 'Panda Security for Desktops', - 'Microsoft Defender ATP', - ]; - const count = names.length; - - return () => names[randomN(count)]; -})(); - -const randomOperatingSystem = (() => { - const osKeys = Object.keys(OperatingSystem) as Array; - const count = osKeys.length; - - return () => OperatingSystem[osKeys[randomN(count)]]; -})(); - const createRunLogger = () => { let groupCount = 1; let itemCount = 0; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts index de62c6b211400..2e5e331b71b00 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts @@ -53,6 +53,7 @@ describe('schedule_throttle_notification_actions', () => { to: 'now', type: 'query', references: ['http://www.example.com'], + namespace: 'a namespace', note: '# sample markdown', version: 1, exceptionsList: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index a7eff049d0d9e..97d976d337564 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -39,6 +39,7 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; import { getPerformBulkActionSchemaMock } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema.mock'; import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; import { FindBulkExecutionLogResponse } from '../../rule_execution_log/types'; +import { ruleTypeMappings } from '../../signals/utils'; export const typicalSetStatusSignalByIdsPayload = (): SetSignalsStatusSchemaDecoded => ({ signal_ids: ['somefakeid1', 'somefakeid2'], @@ -179,18 +180,18 @@ export const getEmptyFindResult = (): FindHit => ({ data: [], }); -export const getFindResultWithSingleHit = (): FindHit => ({ +export const getFindResultWithSingleHit = (isRuleRegistryEnabled: boolean): FindHit => ({ page: 1, perPage: 1, total: 1, - data: [getAlertMock(getQueryRuleParams())], + data: [getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())], }); -export const nonRuleFindResult = (): FindHit => ({ +export const nonRuleFindResult = (isRuleRegistryEnabled: boolean): FindHit => ({ page: 1, perPage: 1, total: 1, - data: [nonRuleAlert()], + data: [nonRuleAlert(isRuleRegistryEnabled)], }); export const getFindResultWithMultiHits = ({ @@ -348,19 +349,22 @@ export const createActionResult = (): ActionResult => ({ isPreconfigured: false, }); -export const nonRuleAlert = () => ({ +export const nonRuleAlert = (isRuleRegistryEnabled: boolean) => ({ // Defaulting to QueryRuleParams because ts doesn't like empty objects - ...getAlertMock(getQueryRuleParams()), + ...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), id: '04128c15-0d1b-4716-a4c5-46997ac7f3bc', name: 'Non-Rule Alert', alertTypeId: 'something', }); -export const getAlertMock = (params: T): Alert => ({ +export const getAlertMock = ( + isRuleRegistryEnabled: boolean, + params: T +): Alert => ({ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', name: 'Detect Root/Admin Users', tags: [`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`], - alertTypeId: 'siem.signals', + alertTypeId: isRuleRegistryEnabled ? ruleTypeMappings[params.type] : 'siem.signals', consumer: 'siem', params, createdAt: new Date('2019-12-13T16:40:33.400Z'), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap index 3c065ab0ac109..1d4e84ea5dccf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap @@ -1,5 +1,609 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`get_signals_template backwards compatibility mappings for version 45 should match snapshot 1`] = ` +Object { + "_meta": Object { + "aliases_version": 1, + "version": 45, + }, + "properties": Object { + "kibana.alert.ancestors.depth": Object { + "path": "signal.ancestors.depth", + "type": "alias", + }, + "kibana.alert.ancestors.id": Object { + "path": "signal.ancestors.id", + "type": "alias", + }, + "kibana.alert.ancestors.index": Object { + "path": "signal.ancestors.index", + "type": "alias", + }, + "kibana.alert.ancestors.type": Object { + "path": "signal.ancestors.type", + "type": "alias", + }, + "kibana.alert.depth": Object { + "path": "signal.depth", + "type": "alias", + }, + "kibana.alert.original_event.action": Object { + "path": "signal.original_event.action", + "type": "alias", + }, + "kibana.alert.original_event.category": Object { + "path": "signal.original_event.category", + "type": "alias", + }, + "kibana.alert.original_event.code": Object { + "path": "signal.original_event.code", + "type": "alias", + }, + "kibana.alert.original_event.created": Object { + "path": "signal.original_event.created", + "type": "alias", + }, + "kibana.alert.original_event.dataset": Object { + "path": "signal.original_event.dataset", + "type": "alias", + }, + "kibana.alert.original_event.duration": Object { + "path": "signal.original_event.duration", + "type": "alias", + }, + "kibana.alert.original_event.end": Object { + "path": "signal.original_event.end", + "type": "alias", + }, + "kibana.alert.original_event.hash": Object { + "path": "signal.original_event.hash", + "type": "alias", + }, + "kibana.alert.original_event.id": Object { + "path": "signal.original_event.id", + "type": "alias", + }, + "kibana.alert.original_event.kind": Object { + "path": "signal.original_event.kind", + "type": "alias", + }, + "kibana.alert.original_event.module": Object { + "path": "signal.original_event.module", + "type": "alias", + }, + "kibana.alert.original_event.outcome": Object { + "path": "signal.original_event.outcome", + "type": "alias", + }, + "kibana.alert.original_event.provider": Object { + "path": "signal.original_event.provider", + "type": "alias", + }, + "kibana.alert.original_event.reason": Object { + "path": "signal.original_event.reason", + "type": "alias", + }, + "kibana.alert.original_event.risk_score": Object { + "path": "signal.original_event.risk_score", + "type": "alias", + }, + "kibana.alert.original_event.risk_score_norm": Object { + "path": "signal.original_event.risk_score_norm", + "type": "alias", + }, + "kibana.alert.original_event.sequence": Object { + "path": "signal.original_event.sequence", + "type": "alias", + }, + "kibana.alert.original_event.severity": Object { + "path": "signal.original_event.severity", + "type": "alias", + }, + "kibana.alert.original_event.start": Object { + "path": "signal.original_event.start", + "type": "alias", + }, + "kibana.alert.original_event.timezone": Object { + "path": "signal.original_event.timezone", + "type": "alias", + }, + "kibana.alert.original_event.type": Object { + "path": "signal.original_event.type", + "type": "alias", + }, + "kibana.alert.original_time": Object { + "path": "signal.original_time", + "type": "alias", + }, + "kibana.alert.reason": Object { + "path": "signal.reason", + "type": "alias", + }, + "kibana.alert.risk_score": Object { + "path": "signal.rule.risk_score", + "type": "alias", + }, + "kibana.alert.rule.author": Object { + "path": "signal.rule.author", + "type": "alias", + }, + "kibana.alert.rule.building_block_type": Object { + "path": "signal.rule.building_block_type", + "type": "alias", + }, + "kibana.alert.rule.created_at": Object { + "path": "signal.rule.created_at", + "type": "alias", + }, + "kibana.alert.rule.created_by": Object { + "path": "signal.rule.created_by", + "type": "alias", + }, + "kibana.alert.rule.description": Object { + "path": "signal.rule.description", + "type": "alias", + }, + "kibana.alert.rule.enabled": Object { + "path": "signal.rule.enabled", + "type": "alias", + }, + "kibana.alert.rule.false_positives": Object { + "path": "signal.rule.false_positives", + "type": "alias", + }, + "kibana.alert.rule.from": Object { + "path": "signal.rule.from", + "type": "alias", + }, + "kibana.alert.rule.immutable": Object { + "path": "signal.rule.immutable", + "type": "alias", + }, + "kibana.alert.rule.index": Object { + "path": "signal.rule.index", + "type": "alias", + }, + "kibana.alert.rule.interval": Object { + "path": "signal.rule.interval", + "type": "alias", + }, + "kibana.alert.rule.language": Object { + "path": "signal.rule.language", + "type": "alias", + }, + "kibana.alert.rule.license": Object { + "path": "signal.rule.license", + "type": "alias", + }, + "kibana.alert.rule.max_signals": Object { + "path": "signal.rule.max_signals", + "type": "alias", + }, + "kibana.alert.rule.name": Object { + "path": "signal.rule.name", + "type": "alias", + }, + "kibana.alert.rule.note": Object { + "path": "signal.rule.note", + "type": "alias", + }, + "kibana.alert.rule.query": Object { + "path": "signal.rule.query", + "type": "alias", + }, + "kibana.alert.rule.references": Object { + "path": "signal.rule.references", + "type": "alias", + }, + "kibana.alert.rule.risk_score_mapping.field": Object { + "path": "signal.rule.risk_score_mapping.field", + "type": "alias", + }, + "kibana.alert.rule.risk_score_mapping.operator": Object { + "path": "signal.rule.risk_score_mapping.operator", + "type": "alias", + }, + "kibana.alert.rule.risk_score_mapping.value": Object { + "path": "signal.rule.risk_score_mapping.value", + "type": "alias", + }, + "kibana.alert.rule.rule_id": Object { + "path": "signal.rule.rule_id", + "type": "alias", + }, + "kibana.alert.rule.rule_name_override": Object { + "path": "signal.rule.rule_name_override", + "type": "alias", + }, + "kibana.alert.rule.saved_id": Object { + "path": "signal.rule.saved_id", + "type": "alias", + }, + "kibana.alert.rule.severity_mapping.field": Object { + "path": "signal.rule.severity_mapping.field", + "type": "alias", + }, + "kibana.alert.rule.severity_mapping.operator": Object { + "path": "signal.rule.severity_mapping.operator", + "type": "alias", + }, + "kibana.alert.rule.severity_mapping.severity": Object { + "path": "signal.rule.severity_mapping.severity", + "type": "alias", + }, + "kibana.alert.rule.severity_mapping.value": Object { + "path": "signal.rule.severity_mapping.value", + "type": "alias", + }, + "kibana.alert.rule.tags": Object { + "path": "signal.rule.tags", + "type": "alias", + }, + "kibana.alert.rule.threat.framework": Object { + "path": "signal.rule.threat.framework", + "type": "alias", + }, + "kibana.alert.rule.threat.tactic.id": Object { + "path": "signal.rule.threat.tactic.id", + "type": "alias", + }, + "kibana.alert.rule.threat.tactic.name": Object { + "path": "signal.rule.threat.tactic.name", + "type": "alias", + }, + "kibana.alert.rule.threat.tactic.reference": Object { + "path": "signal.rule.threat.tactic.reference", + "type": "alias", + }, + "kibana.alert.rule.threat.technique.id": Object { + "path": "signal.rule.threat.technique.id", + "type": "alias", + }, + "kibana.alert.rule.threat.technique.name": Object { + "path": "signal.rule.threat.technique.name", + "type": "alias", + }, + "kibana.alert.rule.threat.technique.reference": Object { + "path": "signal.rule.threat.technique.reference", + "type": "alias", + }, + "kibana.alert.rule.threat.technique.subtechnique.id": Object { + "path": "signal.rule.threat.technique.subtechnique.id", + "type": "alias", + }, + "kibana.alert.rule.threat.technique.subtechnique.name": Object { + "path": "signal.rule.threat.technique.subtechnique.name", + "type": "alias", + }, + "kibana.alert.rule.threat.technique.subtechnique.reference": Object { + "path": "signal.rule.threat.technique.subtechnique.reference", + "type": "alias", + }, + "kibana.alert.rule.threat_index": Object { + "path": "signal.rule.threat_index", + "type": "alias", + }, + "kibana.alert.rule.threat_indicator_path": Object { + "path": "signal.rule.threat_indicator_path", + "type": "alias", + }, + "kibana.alert.rule.threat_language": Object { + "path": "signal.rule.threat_language", + "type": "alias", + }, + "kibana.alert.rule.threat_mapping.entries.field": Object { + "path": "signal.rule.threat_mapping.entries.field", + "type": "alias", + }, + "kibana.alert.rule.threat_mapping.entries.type": Object { + "path": "signal.rule.threat_mapping.entries.type", + "type": "alias", + }, + "kibana.alert.rule.threat_mapping.entries.value": Object { + "path": "signal.rule.threat_mapping.entries.value", + "type": "alias", + }, + "kibana.alert.rule.threat_query": Object { + "path": "signal.rule.threat_query", + "type": "alias", + }, + "kibana.alert.rule.threshold.field": Object { + "path": "signal.rule.threshold.field", + "type": "alias", + }, + "kibana.alert.rule.threshold.value": Object { + "path": "signal.rule.threshold.value", + "type": "alias", + }, + "kibana.alert.rule.timeline_id": Object { + "path": "signal.rule.timeline_id", + "type": "alias", + }, + "kibana.alert.rule.timeline_title": Object { + "path": "signal.rule.timeline_title", + "type": "alias", + }, + "kibana.alert.rule.to": Object { + "path": "signal.rule.to", + "type": "alias", + }, + "kibana.alert.rule.type": Object { + "path": "signal.rule.type", + "type": "alias", + }, + "kibana.alert.rule.updated_at": Object { + "path": "signal.rule.updated_at", + "type": "alias", + }, + "kibana.alert.rule.updated_by": Object { + "path": "signal.rule.updated_by", + "type": "alias", + }, + "kibana.alert.rule.uuid": Object { + "path": "signal.rule.id", + "type": "alias", + }, + "kibana.alert.rule.version": Object { + "path": "signal.rule.version", + "type": "alias", + }, + "kibana.alert.severity": Object { + "path": "signal.rule.severity", + "type": "alias", + }, + "kibana.alert.threshold_result.cardinality.field": Object { + "path": "signal.threshold_result.cardinality.field", + "type": "alias", + }, + "kibana.alert.threshold_result.cardinality.value": Object { + "path": "signal.threshold_result.cardinality.value", + "type": "alias", + }, + "kibana.alert.threshold_result.count": Object { + "path": "signal.threshold_result.count", + "type": "alias", + }, + "kibana.alert.threshold_result.from": Object { + "path": "signal.threshold_result.from", + "type": "alias", + }, + "kibana.alert.threshold_result.terms.field": Object { + "path": "signal.threshold_result.terms.field", + "type": "alias", + }, + "kibana.alert.threshold_result.terms.value": Object { + "path": "signal.threshold_result.terms.value", + "type": "alias", + }, + "kibana.alert.workflow_status": Object { + "path": "signal.status", + "type": "alias", + }, + "signal": Object { + "properties": Object { + "_meta": Object { + "properties": Object { + "version": Object { + "type": "long", + }, + }, + "type": "object", + }, + "ancestors": Object { + "properties": Object { + "depth": Object { + "type": "long", + }, + "id": Object { + "type": "keyword", + }, + "index": Object { + "type": "keyword", + }, + "rule": Object { + "type": "keyword", + }, + "type": Object { + "type": "keyword", + }, + }, + }, + "depth": Object { + "type": "integer", + }, + "group": Object { + "properties": Object { + "id": Object { + "type": "keyword", + }, + "index": Object { + "type": "integer", + }, + }, + "type": "object", + }, + "original_event": Object { + "properties": Object { + "reason": Object { + "type": "keyword", + }, + }, + "type": "object", + }, + "reason": Object { + "type": "keyword", + }, + "rule": Object { + "properties": Object { + "author": Object { + "type": "keyword", + }, + "building_block_type": Object { + "type": "keyword", + }, + "license": Object { + "type": "keyword", + }, + "note": Object { + "type": "text", + }, + "risk_score_mapping": Object { + "properties": Object { + "field": Object { + "type": "keyword", + }, + "operator": Object { + "type": "keyword", + }, + "value": Object { + "type": "keyword", + }, + }, + "type": "object", + }, + "rule_name_override": Object { + "type": "keyword", + }, + "severity_mapping": Object { + "properties": Object { + "field": Object { + "type": "keyword", + }, + "operator": Object { + "type": "keyword", + }, + "severity": Object { + "type": "keyword", + }, + "value": Object { + "type": "keyword", + }, + }, + "type": "object", + }, + "threat": Object { + "properties": Object { + "technique": Object { + "properties": Object { + "subtechnique": Object { + "properties": Object { + "id": Object { + "type": "keyword", + }, + "name": Object { + "type": "keyword", + }, + "reference": Object { + "type": "keyword", + }, + }, + "type": "object", + }, + }, + "type": "object", + }, + }, + "type": "object", + }, + "threat_index": Object { + "type": "keyword", + }, + "threat_indicator_path": Object { + "type": "keyword", + }, + "threat_language": Object { + "type": "keyword", + }, + "threat_mapping": Object { + "properties": Object { + "entries": Object { + "properties": Object { + "field": Object { + "type": "keyword", + }, + "type": Object { + "type": "keyword", + }, + "value": Object { + "type": "keyword", + }, + }, + "type": "object", + }, + }, + "type": "object", + }, + "threat_query": Object { + "type": "keyword", + }, + "threshold": Object { + "properties": Object { + "field": Object { + "type": "keyword", + }, + "value": Object { + "type": "float", + }, + }, + "type": "object", + }, + }, + "type": "object", + }, + "threshold_result": Object { + "properties": Object { + "cardinality": Object { + "properties": Object { + "field": Object { + "type": "keyword", + }, + "value": Object { + "type": "long", + }, + }, + }, + "count": Object { + "type": "long", + }, + "from": Object { + "type": "date", + }, + "terms": Object { + "properties": Object { + "field": Object { + "type": "keyword", + }, + "value": Object { + "type": "keyword", + }, + }, + }, + }, + }, + }, + "type": "object", + }, + }, + "runtime": Object { + "host.os.name.caseless": Object { + "script": Object { + "source": "if(doc['host.os.name'].size()!=0) emit(doc['host.os.name'].value.toLowerCase());", + }, + "type": "keyword", + }, + }, +} +`; + +exports[`get_signals_template backwards compatibility mappings for version 57 should match snapshot 1`] = ` +Object { + "_meta": Object { + "aliases_version": 1, + "version": 57, + }, +} +`; + exports[`get_signals_template it should match snapshot 1`] = ` Object { "index_patterns": Array [ @@ -1495,6 +2099,11 @@ Object { }, "name": Object { "fields": Object { + "caseless": Object { + "ignore_above": 1024, + "normalizer": "lowercase", + "type": "keyword", + }, "text": Object { "norms": false, "type": "text", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts index d65a1ad87b41a..61635fdcef9f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts @@ -23,11 +23,9 @@ import type { import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; import { - createSignalsFieldAliases, getSignalsTemplate, SIGNALS_TEMPLATE_VERSION, - SIGNALS_FIELD_ALIASES_VERSION, - ALIAS_VERSION_FIELD, + createBackwardsCompatibilityMapping, } from './get_signals_template'; import { ensureMigrationCleanupPolicy } from '../../migrations/migration_cleanup'; import signalsPolicy from './signals_policy.json'; @@ -35,7 +33,6 @@ import { templateNeedsUpdate } from './check_template_version'; import { getIndexVersion } from './get_index_version'; import { isOutdated } from '../../migrations/helpers'; import { RuleDataPluginService } from '../../../../../../rule_registry/server'; -import signalExtraFields from './signal_extra_fields.json'; import { ConfigType } from '../../../../config'; import { parseExperimentalConfigValue } from '../../../../../common/experimental_features'; @@ -126,7 +123,7 @@ export const createDetectionIndex = async ( } if (indexExists) { - await addFieldAliasesToIndices({ esClient, index, spaceId }); + await addFieldAliasesToIndices({ esClient, index }); // The internal user is used here because Elasticsearch requires the PUT alias requestor to have 'manage' permissions // for BOTH the index AND alias name. However, through 7.14 admins only needed permissions for .siem-signals (the index) // and not .alerts-security.alerts (the alias). From the security solution perspective, all .siem-signals--* @@ -148,33 +145,17 @@ export const createDetectionIndex = async ( const addFieldAliasesToIndices = async ({ esClient, index, - spaceId, }: { esClient: ElasticsearchClient; index: string; - spaceId: string; }) => { const { body: indexMappings } = await esClient.indices.get({ index }); - // Make sure that all signal fields we add aliases for are guaranteed to exist in the mapping for ALL historical - // signals indices (either by adding them to signalExtraFields or ensuring they exist in the original signals - // mapping) or else this call will fail and not update ANY signals indices - const fieldAliases = createSignalsFieldAliases(); for (const [indexName, mapping] of Object.entries(indexMappings)) { const currentVersion: number | undefined = get(mapping.mappings?._meta, 'version'); - const newMapping = { - properties: { - ...signalExtraFields, - ...fieldAliases, - // ...getRbacRequiredFields(spaceId), - }, - _meta: { - version: currentVersion, - [ALIAS_VERSION_FIELD]: SIGNALS_FIELD_ALIASES_VERSION, - }, - }; + const body = createBackwardsCompatibilityMapping(currentVersion ?? 0); await esClient.indices.putMapping({ index: indexName, - body: newMapping, + body, allow_no_indices: true, } as estypes.IndicesPutMappingRequest); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts index bb67dd1fca6df..70363cba34fce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getSignalsTemplate } from './get_signals_template'; +import { createBackwardsCompatibilityMapping, getSignalsTemplate } from './get_signals_template'; describe('get_signals_template', () => { test('it should set the lifecycle "name" and "rollover_alias" to be the name of the index passed in', () => { @@ -124,4 +124,14 @@ describe('get_signals_template', () => { ); expect(template).toMatchSnapshot(); }); + + test('backwards compatibility mappings for version 45 should match snapshot', () => { + const mapping = createBackwardsCompatibilityMapping(45); + expect(mapping).toMatchSnapshot(); + }); + + test('backwards compatibility mappings for version 57 should match snapshot', () => { + const mapping = createBackwardsCompatibilityMapping(57); + expect(mapping).toMatchSnapshot(); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts index 3470f955dbdba..b7a0521e5c3ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts @@ -11,10 +11,12 @@ import { ALERT_RULE_PRODUCER, ALERT_RULE_TYPE_ID, } from '@kbn/rule-data-utils'; +import { merge } from 'lodash'; import signalsMapping from './signals_mapping.json'; import ecsMapping from './ecs_mapping.json'; import otherMapping from './other_mappings.json'; import aadFieldConversion from './signal_aad_mapping.json'; +import signalExtraFields from './signal_extra_fields.json'; /** @constant @@ -22,7 +24,9 @@ import aadFieldConversion from './signal_aad_mapping.json'; @description This value represents the template version assumed by app code. If this number is greater than the user's signals index version, the detections UI will attempt to update the signals template and roll over to - a new signals index. + a new signals index. + + Since we create a new index for new versions, this version on an existing index should never change. If making mappings changes in a patch release, this number should be incremented by 1. If making mappings changes in a minor release, this number should be @@ -34,12 +38,24 @@ export const SIGNALS_TEMPLATE_VERSION = 57; @constant @type {number} @description This value represents the version of the field aliases that map the new field names - used for alerts-as-data to the old signal.* field names. If any .siem-signals- indices - have an aliases_version less than this value, the detections UI will call create_index_route and - and go through the index update process. Increment this number if making changes to the field - aliases we use to make signals forwards-compatible. + used for alerts-as-data to the old signal.* field names and any other runtime fields that are added + to .siem-signals indices for compatibility reasons (e.g. host.os.name.caseless). + + This version number can change over time on existing indices as we add backwards compatibility fields. + + If any .siem-signals- indices have an aliases_version less than this value, the detections + UI will call create_index_route and and go through the index update process. Increment this number if + making changes to the field aliases we use to make signals forwards-compatible. */ export const SIGNALS_FIELD_ALIASES_VERSION = 1; + +/** + @constant + @type {number} + @description This value represents the minimum required index version (SIGNALS_TEMPLATE_VERSION) for EQL + rules to write signals correctly. If the write index has a `version` less than this value, the EQL rule + will throw an error on execution. +*/ export const MIN_EQL_RULE_INDEX_VERSION = 2; export const ALIAS_VERSION_FIELD = 'aliases_version'; @@ -68,13 +84,12 @@ export const getSignalsTemplate = (index: string, spaceId: string, aadIndexAlias }, mappings: { dynamic: false, - properties: { - ...ecsMapping.mappings.properties, - ...otherMapping.mappings.properties, - ...fieldAliases, - // ...getRbacRequiredFields(spaceId), - signal: signalsMapping.mappings.properties.signal, - }, + properties: merge( + ecsMapping.mappings.properties, + otherMapping.mappings.properties, + fieldAliases, + signalsMapping.mappings.properties + ), _meta: { version: SIGNALS_TEMPLATE_VERSION, [ALIAS_VERSION_FIELD]: SIGNALS_FIELD_ALIASES_VERSION, @@ -97,6 +112,47 @@ export const createSignalsFieldAliases = () => { return fieldAliases; }; +export const backwardsCompatibilityMappings = [ + { + minVersion: 0, + // Version 45 shipped with 7.14 + maxVersion: 45, + mapping: { + runtime: { + 'host.os.name.caseless': { + type: 'keyword', + script: { + source: + "if(doc['host.os.name'].size()!=0) emit(doc['host.os.name'].value.toLowerCase());", + }, + }, + }, + properties: { + // signalExtraFields contains the field mappings that have been added to the signals indices over time. + // We need to include these here because we can't add an alias for a field that isn't in the mapping, + // and we want to apply the aliases to all old signals indices at the same time. + ...signalExtraFields, + ...createSignalsFieldAliases(), + }, + }, + }, +]; + +export const createBackwardsCompatibilityMapping = (version: number) => { + const mappings = backwardsCompatibilityMappings + .filter((mapping) => version <= mapping.maxVersion && version >= mapping.minVersion) + .map((mapping) => mapping.mapping); + + const meta = { + _meta: { + version, + [ALIAS_VERSION_FIELD]: SIGNALS_FIELD_ALIASES_VERSION, + }, + }; + + return merge({}, ...mappings, meta); +}; + export const getRbacRequiredFields = (spaceId: string) => { return { [SPACE_IDS]: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/other_mappings.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/other_mappings.json index b61ad2e43ac03..5ad8f5238a97d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/other_mappings.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/other_mappings.json @@ -98,6 +98,29 @@ } } }, + "host": { + "properties": { + "os": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + }, + "caseless": { + "ignore_above": 1024, + "normalizer": "lowercase", + "type": "keyword" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, "interface": { "properties": { "alias": { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index 189173f44a295..866c70626d2bc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -72,12 +72,16 @@ jest.mock('../../../timeline/routes/prepackaged_timelines/install_prepackaged_ti }; }); -describe('add_prepackaged_rules_route', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('add_prepackaged_rules_route - %s', (_, isRuleRegistryEnabled) => { const siemMockClient = siemMock.createClient(); let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let securitySetup: SecurityPluginSetup; let mockExceptionsClient: ExceptionListClient; + const testif = isRuleRegistryEnabled ? test.skip : test; beforeEach(() => { server = serverMock.create(); @@ -91,8 +95,10 @@ describe('add_prepackaged_rules_route', () => { mockExceptionsClient = listMock.getExceptionListClient(); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + clients.rulesClient.update.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); (installPrepackagedTimelines as jest.Mock).mockReset(); (installPrepackagedTimelines as jest.Mock).mockResolvedValue({ @@ -106,7 +112,7 @@ describe('add_prepackaged_rules_route', () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) ); - addPrepackedRulesRoute(server.router, createMockConfig(), securitySetup); + addPrepackedRulesRoute(server.router, createMockConfig(), securitySetup, isRuleRegistryEnabled); }); describe('status codes', () => { @@ -129,23 +135,25 @@ describe('add_prepackaged_rules_route', () => { }); }); - test('it returns a 400 if the index does not exist', async () => { + test('it returns a 400 if the index does not exist when rule registry not enabled', async () => { const request = addPrepackagedRulesRequest(); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) ); const response = await server.inject(request, context); - expect(response.status).toEqual(400); - expect(response.body).toEqual({ - status_code: 400, - message: expect.stringContaining( - 'Pre-packaged rules cannot be installed until the signals index is created' - ), - }); + expect(response.status).toEqual(isRuleRegistryEnabled ? 200 : 400); + if (!isRuleRegistryEnabled) { + expect(response.body).toEqual({ + status_code: 400, + message: expect.stringContaining( + 'Pre-packaged rules cannot be installed until the signals index is created' + ), + }); + } }); - it('returns 404 if siem client is unavailable', async () => { + test('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; const response = await server.inject( addPrepackagedRulesRequest(), @@ -185,16 +193,19 @@ describe('add_prepackaged_rules_route', () => { }); }); - test('catches errors if payloads cause errors to be thrown', async () => { - context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( - elasticsearchClientMock.createErrorTransportRequestPromise(new Error('Test error')) - ); - const request = addPrepackagedRulesRequest(); - const response = await server.inject(request, context); - - expect(response.status).toEqual(500); - expect(response.body).toEqual({ message: 'Test error', status_code: 500 }); - }); + testif( + 'catches errors if signals index does not exist when rule registry not enabled', + async () => { + context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( + elasticsearchClientMock.createErrorTransportRequestPromise(new Error('Test error')) + ); + const request = addPrepackagedRulesRequest(); + const response = await server.inject(request, context); + + expect(response.status).toEqual(500); + expect(response.body).toEqual({ message: 'Test error', status_code: 500 }); + } + ); }); test('should install prepackaged timelines', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 21933b2918722..0048c735b0a7c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -43,7 +43,8 @@ import { installPrepackagedTimelines } from '../../../timeline/routes/prepackage export const addPrepackedRulesRoute = ( router: SecuritySolutionPluginRouter, config: ConfigType, - security: SetupPlugins['security'] + security: SetupPlugins['security'], + isRuleRegistryEnabled: boolean ) => { router.put( { @@ -79,7 +80,9 @@ export const addPrepackedRulesRoute = ( frameworkRequest, config.maxTimelineImportExportSize, config.prebuiltRulesFromFileSystem, - config.prebuiltRulesFromSavedObjects + config.prebuiltRulesFromSavedObjects, + undefined, + isRuleRegistryEnabled ); return response.ok({ body: validated ?? {} }); } catch (err) { @@ -109,7 +112,8 @@ export const createPrepackagedRules = async ( maxTimelineImportExportSize: ConfigType['maxTimelineImportExportSize'], prebuiltRulesFromFileSystem: ConfigType['prebuiltRulesFromFileSystem'], prebuiltRulesFromSavedObjects: ConfigType['prebuiltRulesFromSavedObjects'], - exceptionsClient?: ExceptionListClient + exceptionsClient?: ExceptionListClient, + isRuleRegistryEnabled?: boolean | undefined ): Promise => { const esClient = context.core.elasticsearch.client; const savedObjectsClient = context.core.savedObjects.client; @@ -131,11 +135,14 @@ export const createPrepackagedRules = async ( prebuiltRulesFromFileSystem, prebuiltRulesFromSavedObjects ); - const prepackagedRules = await getExistingPrepackagedRules({ rulesClient }); + const prepackagedRules = await getExistingPrepackagedRules({ + rulesClient, + isRuleRegistryEnabled: isRuleRegistryEnabled ?? false, + }); const rulesToInstall = getRulesToInstall(latestPrepackagedRules, prepackagedRules); const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, prepackagedRules); const signalsIndex = siemClient.getSignalsIndex(); - if (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0) { + if (!isRuleRegistryEnabled && (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0)) { const signalsIndexExists = await getIndexExists(esClient.asCurrentUser, signalsIndex); if (!signalsIndexExists) { throw new PrepackagedRulesError( @@ -145,7 +152,14 @@ export const createPrepackagedRules = async ( } } - await Promise.all(installPrepackagedRules(rulesClient, rulesToInstall, signalsIndex)); + await Promise.all( + installPrepackagedRules( + rulesClient, + rulesToInstall, + signalsIndex, + isRuleRegistryEnabled ?? false + ) + ); const timeline = await installPrepackagedTimelines( maxTimelineImportExportSize, frameworkRequest, @@ -160,7 +174,8 @@ export const createPrepackagedRules = async ( context.securitySolution.getSpaceId(), ruleStatusClient, rulesToUpdate, - signalsIndex + signalsIndex, + isRuleRegistryEnabled ?? false ); const prepackagedRulesOutput: PrePackagedRulesAndTimelinesSchema = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 3de2770972c82..2c8696dbd4554 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -24,7 +24,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('create_rules_bulk', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('create_rules_bulk - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -35,12 +38,14 @@ describe('create_rules_bulk', () => { ml = mlServicesMock.createSetupContract(); clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no existing rules - clients.rulesClient.create.mockResolvedValue(getAlertMock(getQueryRuleParams())); // successful creation + clients.rulesClient.create.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); // successful creation context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) ); - createRulesBulkRoute(server.router, ml); + createRulesBulkRoute(server.router, ml, isRuleRegistryEnabled); }); describe('status codes', () => { @@ -56,7 +61,7 @@ describe('create_rules_bulk', () => { expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); }); - it('returns 404 if siem client is unavailable', async () => { + test('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; // @ts-expect-error const response = await server.inject(getReadBulkRequest(), contextWithoutSecuritySolution); @@ -66,7 +71,7 @@ describe('create_rules_bulk', () => { }); describe('unhappy paths', () => { - it('returns a 403 error object if ML Authz fails', async () => { + test('returns a 403 error object if ML Authz fails', async () => { (buildMlAuthz as jest.Mock).mockReturnValueOnce({ validateRuleType: jest .fn() @@ -86,26 +91,30 @@ describe('create_rules_bulk', () => { ]); }); - it('returns an error object if the index does not exist', async () => { + test('returns an error object if the index does not exist when rule registry not enabled', async () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) ); const response = await server.inject(getReadBulkRequest(), context); expect(response.status).toEqual(200); - expect(response.body).toEqual([ - { - error: { - message: 'To create a rule, the index must exist first. Index undefined does not exist', - status_code: 400, + + if (!isRuleRegistryEnabled) { + expect(response.body).toEqual([ + { + error: { + message: + 'To create a rule, the index must exist first. Index undefined does not exist', + status_code: 400, + }, + rule_id: 'rule-1', }, - rule_id: 'rule-1', - }, - ]); + ]); + } }); test('returns a duplicate error if rule_id already exists', async () => { - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const response = await server.inject(getReadBulkRequest(), context); expect(response.status).toEqual(200); @@ -136,7 +145,7 @@ describe('create_rules_bulk', () => { ]); }); - it('returns an error object if duplicate rule_ids found in request payload', async () => { + test('returns an error object if duplicate rule_ids found in request payload', async () => { const request = requestMock.create({ method: 'post', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 5f44ab0ada92d..31683c289d4b4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -28,7 +28,8 @@ import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters' export const createRulesBulkRoute = ( router: SecuritySolutionPluginRouter, - ml: SetupPlugins['ml'] + ml: SetupPlugins['ml'], + isRuleRegistryEnabled: boolean ) => { router.post( { @@ -67,9 +68,10 @@ export const createRulesBulkRoute = ( .map(async (payloadRule) => { if (payloadRule.rule_id != null) { const rule = await readRules({ + id: undefined, + isRuleRegistryEnabled, rulesClient, ruleId: payloadRule.rule_id, - id: undefined, }); if (rule != null) { return createBulkErrorObject({ @@ -79,7 +81,11 @@ export const createRulesBulkRoute = ( }); } } - const internalRule = convertCreateAPIToInternalSchema(payloadRule, siemClient); + const internalRule = convertCreateAPIToInternalSchema( + payloadRule, + siemClient, + isRuleRegistryEnabled + ); try { const validationErrors = createRuleValidateTypeDependents(payloadRule); if (validationErrors.length) { @@ -93,7 +99,7 @@ export const createRulesBulkRoute = ( throwHttpError(await mlAuthz.validateRuleType(internalRule.params.type)); const finalIndex = internalRule.params.outputIndex; const indexExists = await getIndexExists(esClient.asCurrentUser, finalIndex); - if (!indexExists) { + if (!isRuleRegistryEnabled && !indexExists) { return createBulkErrorObject({ ruleId: internalRule.params.ruleId, statusCode: 400, @@ -112,7 +118,10 @@ export const createRulesBulkRoute = ( return transformValidateBulkError(internalRule.params.ruleId, createdRule, undefined); } catch (err) { - return transformBulkError(internalRule.params.ruleId, err); + return transformBulkError( + internalRule.params.ruleId, + err as Error & { statusCode?: number | undefined } + ); } }) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index fc48e34a7ca74..d1be96a44930a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -24,7 +24,10 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('create_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('create_rules - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -35,13 +38,15 @@ describe('create_rules', () => { ml = mlServicesMock.createSetupContract(); clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no current rules - clients.rulesClient.create.mockResolvedValue(getAlertMock(getQueryRuleParams())); // creation succeeds + clients.rulesClient.create.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); // creation succeeds clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); // needed to transform: ; context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) ); - createRulesRoute(server.router, ml); + createRulesRoute(server.router, ml, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { @@ -57,7 +62,7 @@ describe('create_rules', () => { expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); }); - it('returns 404 if siem client is unavailable', async () => { + test('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; // @ts-expect-error const response = await server.inject(getCreateRequest(), contextWithoutSecuritySolution); @@ -65,7 +70,7 @@ describe('create_rules', () => { expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); }); - it('returns 200 if license is not platinum', async () => { + test('returns 200 if license is not platinum', async () => { (context.licensing.license.hasAtLeast as jest.Mock).mockReturnValue(false); const response = await server.inject(getCreateRequest(), context); @@ -74,12 +79,12 @@ describe('create_rules', () => { }); describe('creating an ML Rule', () => { - it('is successful', async () => { + test('is successful', async () => { const response = await server.inject(createMlRuleRequest(), context); expect(response.status).toEqual(200); }); - it('returns a 403 if ML Authz fails', async () => { + test('returns a 403 if ML Authz fails', async () => { (buildMlAuthz as jest.Mock).mockReturnValueOnce({ validateRuleType: jest .fn() @@ -96,21 +101,24 @@ describe('create_rules', () => { }); describe('unhappy paths', () => { - test('it returns a 400 if the index does not exist', async () => { + test('it returns a 400 if the index does not exist when rule registry not enabled', async () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) ); const response = await server.inject(getCreateRequest(), context); - expect(response.status).toEqual(400); - expect(response.body).toEqual({ - message: 'To create a rule, the index must exist first. Index undefined does not exist', - status_code: 400, - }); + expect(response.status).toEqual(isRuleRegistryEnabled ? 200 : 400); + + if (!isRuleRegistryEnabled) { + expect(response.body).toEqual({ + message: 'To create a rule, the index must exist first. Index undefined does not exist', + status_code: 400, + }); + } }); test('returns a duplicate error if rule_id already exists', async () => { - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const response = await server.inject(getCreateRequest(), context); expect(response.status).toEqual(409); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index 333fa9c17a75b..9e03e5f8f2143 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -6,7 +6,6 @@ */ import { transformError, getIndexExists } from '@kbn/securitysolution-es-utils'; -import { IRuleDataClient } from '../../../../../../rule_registry/server'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { DETECTION_ENGINE_RULES_URL, @@ -27,7 +26,7 @@ import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters' export const createRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], - ruleDataClient?: IRuleDataClient | null // TODO: Use this for RAC (otherwise delete it) + isRuleRegistryEnabled: boolean ): void => { router.post( { @@ -57,6 +56,7 @@ export const createRulesRoute = ( if (request.body.rule_id != null) { const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, ruleId: request.body.rule_id, id: undefined, @@ -69,7 +69,11 @@ export const createRulesRoute = ( } } - const internalRule = convertCreateAPIToInternalSchema(request.body, siemClient); + const internalRule = convertCreateAPIToInternalSchema( + request.body, + siemClient, + isRuleRegistryEnabled + ); const mlAuthz = buildMlAuthz({ license: context.licensing.license, @@ -83,7 +87,7 @@ export const createRulesRoute = ( esClient.asCurrentUser, internalRule.params.outputIndex ); - if (!indexExists) { + if (!isRuleRegistryEnabled && !indexExists) { return siemResponse.error({ statusCode: 400, body: `To create a rule, the index must exist first. Index ${internalRule.params.outputIndex} does not exist`, @@ -107,14 +111,18 @@ export const createRulesRoute = ( ruleId: createdRule.id, spaceId: context.securitySolution.getSpaceId(), }); - const [validated, errors] = newTransformValidate(createdRule, ruleStatuses[0]); + const [validated, errors] = newTransformValidate( + createdRule, + ruleStatuses[0], + isRuleRegistryEnabled + ); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { return response.ok({ body: validated ?? {} }); } } catch (err) { - const error = transformError(err); + const error = transformError(err as Error); return siemResponse.error({ body: error.message, statusCode: error.statusCode, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts index 66feb3cae724f..7db5651de2c34 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts @@ -18,7 +18,10 @@ import { import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { deleteRulesBulkRoute } from './delete_rules_bulk_route'; -describe('delete_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('delete_rules - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -26,11 +29,11 @@ describe('delete_rules', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists clients.rulesClient.delete.mockResolvedValue({}); // successful deletion clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // rule status request - deleteRulesBulkRoute(server.router); + deleteRulesBulkRoute(server.router, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 7a5b7121eb33b..6aecfff1178bc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -6,6 +6,7 @@ */ import { validate } from '@kbn/securitysolution-io-ts-utils'; + import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { @@ -34,7 +35,10 @@ type Handler = RequestHandler< 'delete' | 'post' >; -export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter) => { +export const deleteRulesBulkRoute = ( + router: SecuritySolutionPluginRouter, + isRuleRegistryEnabled: boolean +) => { const config: Config = { validate: { body: buildRouteValidation( @@ -71,7 +75,7 @@ export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter) => { } try { - const rule = await readRules({ rulesClient, id, ruleId }); + const rule = await readRules({ rulesClient, id, ruleId, isRuleRegistryEnabled }); if (!rule) { return getIdBulkError({ id, ruleId }); } @@ -87,7 +91,12 @@ export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter) => { ruleStatuses, id: rule.id, }); - return transformValidateBulkError(idOrRuleIdOrUnknown, rule, ruleStatuses); + return transformValidateBulkError( + idOrRuleIdOrUnknown, + rule, + ruleStatuses, + isRuleRegistryEnabled + ); } catch (err) { return transformBulkError(idOrRuleIdOrUnknown, err); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts index 5102cb32a4572..35b3ef3d9cf85 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts @@ -19,7 +19,10 @@ import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { deleteRulesRoute } from './delete_rules_route'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -describe('delete_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('delete_rules - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -27,11 +30,11 @@ describe('delete_rules', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); - deleteRulesRoute(server.router); + deleteRulesRoute(server.router, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { @@ -42,7 +45,9 @@ describe('delete_rules', () => { }); test('returns 200 when deleting a single rule with a valid actionClient and alertClient by id', async () => { - clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); + clients.rulesClient.get.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); const response = await server.inject(getDeleteRequestById(), context); expect(response.status).toEqual(200); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts index 499f5c151c66c..77b8dd6fc5b54 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -6,7 +6,6 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { IRuleDataClient } from '../../../../../../rule_registry/server'; import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; import { queryRulesSchema, @@ -23,7 +22,7 @@ import { readRules } from '../../rules/read_rules'; export const deleteRulesRoute = ( router: SecuritySolutionPluginRouter, - ruleDataClient?: IRuleDataClient | null + isRuleRegistryEnabled: boolean ) => { router.delete( { @@ -54,7 +53,7 @@ export const deleteRulesRoute = ( } const ruleStatusClient = context.securitySolution.getExecutionLogClient(); - const rule = await readRules({ rulesClient, id, ruleId }); + const rule = await readRules({ isRuleRegistryEnabled, rulesClient, id, ruleId }); if (!rule) { const error = getIdError({ id, ruleId }); return siemResponse.error({ @@ -74,7 +73,7 @@ export const deleteRulesRoute = ( ruleStatuses, id: rule.id, }); - const transformed = transform(rule, ruleStatuses[0]); + const transformed = transform(rule, ruleStatuses[0], isRuleRegistryEnabled); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'failed to transform alert' }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts index 022118859aa0b..e4b99e63cb6c6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts @@ -21,7 +21,11 @@ import { getExportByObjectIds } from '../../rules/get_export_by_object_ids'; import { getExportAll } from '../../rules/get_export_all'; import { buildSiemResponse } from '../utils'; -export const exportRulesRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { +export const exportRulesRoute = ( + router: SecuritySolutionPluginRouter, + config: ConfigType, + isRuleRegistryEnabled: boolean +) => { router.post( { path: `${DETECTION_ENGINE_RULES_URL}/_export`, @@ -53,7 +57,10 @@ export const exportRulesRoute = (router: SecuritySolutionPluginRouter, config: C body: `Can't export more than ${exportSizeLimit} rules`, }); } else { - const nonPackagedRulesCount = await getNonPackagedRulesCount({ rulesClient }); + const nonPackagedRulesCount = await getNonPackagedRulesCount({ + isRuleRegistryEnabled, + rulesClient, + }); if (nonPackagedRulesCount > exportSizeLimit) { return siemResponse.error({ statusCode: 400, @@ -64,8 +71,8 @@ export const exportRulesRoute = (router: SecuritySolutionPluginRouter, config: C const exported = request.body?.objects != null - ? await getExportByObjectIds(rulesClient, request.body.objects) - : await getExportAll(rulesClient); + ? await getExportByObjectIds(rulesClient, request.body.objects, isRuleRegistryEnabled) + : await getExportAll(rulesClient, isRuleRegistryEnabled); const responseBody = request.query.exclude_export_details ? exported.rulesNdjson diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index 301cf8518b838..d15d31dcd63e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -17,7 +17,10 @@ import { } from '../__mocks__/request_responses'; import { findRulesRoute } from './find_rules_route'; -describe('find_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('find_rules - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -25,12 +28,14 @@ describe('find_rules', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + clients.rulesClient.get.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus()); - findRulesRoute(server.router); + findRulesRoute(server.router, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts index ed39d42c38e4a..26e8d1107237b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts @@ -6,7 +6,6 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { IRuleDataClient } from '../../../../../../rule_registry/server'; import { findRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/find_rules_type_dependents'; import { findRulesSchema, @@ -21,7 +20,7 @@ import { transformFindAlerts } from './utils'; export const findRulesRoute = ( router: SecuritySolutionPluginRouter, - ruleDataClient?: IRuleDataClient | null + isRuleRegistryEnabled: boolean ) => { router.get( { @@ -52,6 +51,7 @@ export const findRulesRoute = ( const execLogClient = context.securitySolution.getExecutionLogClient(); const rules = await findRules({ + isRuleRegistryEnabled, rulesClient, perPage: query.per_page, page: query.page, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts index d9b6f4dd0f10c..053e0b7178de5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts @@ -17,7 +17,10 @@ import { RuleStatusResponse } from '../../rules/types'; import { AlertExecutionStatusErrorReasons } from '../../../../../../alerting/common'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -describe('find_statuses', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('find_statuses - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -25,7 +28,9 @@ describe('find_statuses', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus()); // successful status search - clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); + clients.rulesClient.get.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); findRulesStatusesRoute(server.router); }); @@ -57,7 +62,7 @@ describe('find_statuses', () => { test('returns success if rule status client writes an error status', async () => { // 0. task manager tried to run the rule but couldn't, so the alerting framework // wrote an error to the executionStatus. - const failingExecutionRule = getAlertMock(getQueryRuleParams()); + const failingExecutionRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); failingExecutionRule.executionStatus = { status: 'error', lastExecutionDate: failingExecutionRule.executionStatus.lastExecutionDate, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts index 61c618dc4d5e6..1b171f693d80b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts @@ -51,7 +51,10 @@ jest.mock('../../../timeline/utils/check_timelines_status', () => { }; }); -describe('get_prepackaged_rule_status_route', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('get_prepackaged_rule_status_route - %s', (_, isRuleRegistryEnabled) => { const mockGetCurrentUser = { user: { username: 'mockUser', @@ -63,6 +66,7 @@ describe('get_prepackaged_rule_status_route', () => { let securitySetup: SecurityPluginSetup; beforeEach(() => { + jest.clearAllMocks(); server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); @@ -75,7 +79,18 @@ describe('get_prepackaged_rule_status_route', () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); - getPrepackagedRulesStatusRoute(server.router, createMockConfig(), securitySetup); + (checkTimelinesStatus as jest.Mock).mockResolvedValue({ + timelinesToInstall: [], + timelinesToUpdate: [], + prepackagedTimelines: [], + }); + + getPrepackagedRulesStatusRoute( + server.router, + createMockConfig(), + securitySetup, + isRuleRegistryEnabled + ); }); describe('status codes with actionClient and alertClient', () => { @@ -123,7 +138,7 @@ describe('get_prepackaged_rule_status_route', () => { }); test('1 rule installed, 1 custom rules, 0 rules not installed, and 1 rule to not updated', async () => { - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const request = getPrepackagedRulesStatusRequest(); const response = await server.inject(request, context); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index 38c315462bf55..9a06928eee233 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -32,7 +32,8 @@ import { export const getPrepackagedRulesStatusRoute = ( router: SecuritySolutionPluginRouter, config: ConfigType, - security: SetupPlugins['security'] + security: SetupPlugins['security'], + isRuleRegistryEnabled: boolean ) => { router.get( { @@ -59,6 +60,7 @@ export const getPrepackagedRulesStatusRoute = ( config.prebuiltRulesFromSavedObjects ); const customRules = await findRules({ + isRuleRegistryEnabled, rulesClient, perPage: 1, page: 1, @@ -68,7 +70,10 @@ export const getPrepackagedRulesStatusRoute = ( fields: undefined, }); const frameworkRequest = await buildFrameworkRequest(context, security, request); - const prepackagedRules = await getExistingPrepackagedRules({ rulesClient }); + const prepackagedRules = await getExistingPrepackagedRules({ + rulesClient, + isRuleRegistryEnabled, + }); const rulesToInstall = getRulesToInstall(latestPrepackagedRules, prepackagedRules); const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, prepackagedRules); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts index cd572894f551e..bf29dbe870153 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts @@ -29,7 +29,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('import_rules_route', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('import_rules_route - %s', (_, isRuleRegistryEnabled) => { let config: ReturnType; let server: ReturnType; let request: ReturnType; @@ -45,11 +48,13 @@ describe('import_rules_route', () => { ml = mlServicesMock.createSetupContract(); clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no extant rules - clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); + clients.rulesClient.update.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) ); - importRulesRoute(server.router, config, ml); + importRulesRoute(server.router, config, ml, isRuleRegistryEnabled); }); describe('status codes', () => { @@ -60,7 +65,7 @@ describe('import_rules_route', () => { }); test('returns 500 if more than 10,000 rules are imported', async () => { - const ruleIds = new Array(10001).fill(undefined).map((_, index) => `rule-${index}`); + const ruleIds = new Array(10001).fill(undefined).map((__, index) => `rule-${index}`); const multiRequest = getImportRulesRequest(buildHapiStream(ruleIdsToNdJsonString(ruleIds))); const response = await server.inject(multiRequest, context); @@ -125,18 +130,20 @@ describe('import_rules_route', () => { transformMock.mockRestore(); }); - test('returns an error if the index does not exist', async () => { + test('returns an error if the index does not exist when rule registry not enabled', async () => { clients.appClient.getSignalsIndex.mockReturnValue('mockSignalsIndex'); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) ); const response = await server.inject(request, context); - expect(response.status).toEqual(400); - expect(response.body).toEqual({ - message: - 'To create a rule, the index must exist first. Index mockSignalsIndex does not exist', - status_code: 400, - }); + expect(response.status).toEqual(isRuleRegistryEnabled ? 200 : 400); + if (!isRuleRegistryEnabled) { + expect(response.body).toEqual({ + message: + 'To create a rule, the index must exist first. Index mockSignalsIndex does not exist', + status_code: 400, + }); + } }); test('returns an error when cluster throws error', async () => { @@ -166,7 +173,9 @@ describe('import_rules_route', () => { describe('single rule import', () => { test('returns 200 if rule imported successfully', async () => { - clients.rulesClient.create.mockResolvedValue(getAlertMock(getQueryRuleParams())); + clients.rulesClient.create.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); const response = await server.inject(request, context); expect(response.status).toEqual(200); expect(response.body).toEqual({ @@ -199,7 +208,9 @@ describe('import_rules_route', () => { describe('rule with existing rule_id', () => { test('returns with reported conflict if `overwrite` is set to `false`', async () => { - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // extant rule + clients.rulesClient.find.mockResolvedValue( + getFindResultWithSingleHit(isRuleRegistryEnabled) + ); // extant rule const response = await server.inject(request, context); expect(response.status).toEqual(200); @@ -219,7 +230,9 @@ describe('import_rules_route', () => { }); test('returns with NO reported conflict if `overwrite` is set to `true`', async () => { - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // extant rule + clients.rulesClient.find.mockResolvedValue( + getFindResultWithSingleHit(isRuleRegistryEnabled) + ); // extant rule const overwriteRequest = getImportRulesRequestOverwriteTrue( buildHapiStream(ruleIdsToNdJsonString(['rule-1'])) ); @@ -251,7 +264,7 @@ describe('import_rules_route', () => { }); test('returns 200 if many rules are imported successfully', async () => { - const ruleIds = new Array(9999).fill(undefined).map((_, index) => `rule-${index}`); + const ruleIds = new Array(9999).fill(undefined).map((__, index) => `rule-${index}`); const multiRequest = getImportRulesRequest(buildHapiStream(ruleIdsToNdJsonString(ruleIds))); const response = await server.inject(multiRequest, context); @@ -339,7 +352,9 @@ describe('import_rules_route', () => { describe('rules with existing rule_id', () => { beforeEach(() => { - clients.rulesClient.find.mockResolvedValueOnce(getFindResultWithSingleHit()); // extant rule + clients.rulesClient.find.mockResolvedValueOnce( + getFindResultWithSingleHit(isRuleRegistryEnabled) + ); // extant rule }); test('returns with reported conflict if `overwrite` is set to `false`', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index d3193900859fa..8269fe8b36132 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -53,7 +53,8 @@ const CHUNK_PARSED_OBJECT_SIZE = 50; export const importRulesRoute = ( router: SecuritySolutionPluginRouter, config: ConfigType, - ml: SetupPlugins['ml'] + ml: SetupPlugins['ml'], + isRuleRegistryEnabled: boolean ) => { router.post( { @@ -103,7 +104,7 @@ export const importRulesRoute = ( } const signalsIndex = siemClient.getSignalsIndex(); const indexExists = await getIndexExists(esClient.asCurrentUser, signalsIndex); - if (!indexExists) { + if (!isRuleRegistryEnabled && !indexExists) { return siemResponse.error({ statusCode: 400, body: `To create a rule, the index must exist first. Index ${signalsIndex} does not exist`, @@ -205,6 +206,7 @@ export const importRulesRoute = ( const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; throwHttpError(await mlAuthz.validateRuleType(type)); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, ruleId, id: undefined, @@ -212,6 +214,7 @@ export const importRulesRoute = ( if (rule == null) { await createRules({ + isRuleRegistryEnabled, rulesClient, anomalyThreshold, author, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts index 31f805c563f76..2c3db023dccc4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts @@ -22,7 +22,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('patch_rules_bulk', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('patch_rules_bulk - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -32,10 +35,12 @@ describe('patch_rules_bulk', () => { ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists - clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); // update succeeds + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists + clients.rulesClient.update.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); // update succeeds - patchRulesBulkRoute(server.router, ml); + patchRulesBulkRoute(server.router, ml, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 3aaa82ea56f3f..67d68221d846f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -27,7 +27,8 @@ import { PartialFilter } from '../../types'; export const patchRulesBulkRoute = ( router: SecuritySolutionPluginRouter, - ml: SetupPlugins['ml'] + ml: SetupPlugins['ml'], + isRuleRegistryEnabled: boolean ) => { router.patch( { @@ -121,7 +122,12 @@ export const patchRulesBulkRoute = ( throwHttpError(await mlAuthz.validateRuleType(type)); } - const existingRule = await readRules({ rulesClient, ruleId, id }); + const existingRule = await readRules({ + isRuleRegistryEnabled, + rulesClient, + ruleId, + id, + }); if (existingRule?.params.type) { // reject an unauthorized modification of an ML rule throwHttpError(await mlAuthz.validateRuleType(existingRule?.params.type)); @@ -185,7 +191,7 @@ export const patchRulesBulkRoute = ( ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - return transformValidateBulkError(rule.id, rule, ruleStatuses); + return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled); } else { return getIdBulkError({ id, ruleId }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts index 16d65d6482d21..97773c45ce0d9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts @@ -25,7 +25,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('patch_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('patch_rules - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -35,14 +38,18 @@ describe('patch_rules', () => { ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); - clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); // existing rule - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // existing rule - clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); // successful update + clients.rulesClient.get.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); // existing rule + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // existing rule + clients.rulesClient.update.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); // successful update clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform clients.savedObjectsClient.create.mockResolvedValue(getRuleExecutionStatuses()[0]); // successful transform clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); - patchRulesRoute(server.router, ml); + patchRulesRoute(server.router, ml, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { @@ -69,7 +76,7 @@ describe('patch_rules', () => { }); test('returns error if requesting a non-rule', async () => { - clients.rulesClient.find.mockResolvedValue(nonRuleFindResult()); + clients.rulesClient.find.mockResolvedValue(nonRuleFindResult(isRuleRegistryEnabled)); const response = await server.inject(getPatchRequest(), context); expect(response.status).toEqual(404); expect(response.body).toEqual({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index b564262b4a5c7..cf140f22289de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -6,7 +6,6 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { IRuleDataClient } from '../../../../../../rule_registry/server'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { patchRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/patch_rules_type_dependents'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; @@ -30,7 +29,7 @@ import { PartialFilter } from '../../types'; export const patchRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], - ruleDataClient?: IRuleDataClient | null + isRuleRegistryEnabled: boolean ) => { router.patch( { @@ -124,7 +123,12 @@ export const patchRulesRoute = ( throwHttpError(await mlAuthz.validateRuleType(type)); } - const existingRule = await readRules({ rulesClient, ruleId, id }); + const existingRule = await readRules({ + isRuleRegistryEnabled, + rulesClient, + ruleId, + id, + }); if (existingRule?.params.type) { // reject an unauthorized modification of an ML rule throwHttpError(await mlAuthz.validateRuleType(existingRule?.params.type)); @@ -189,7 +193,11 @@ export const patchRulesRoute = ( spaceId: context.securitySolution.getSpaceId(), }); - const [validated, errors] = transformValidate(rule, ruleStatuses[0]); + const [validated, errors] = transformValidate( + rule, + ruleStatuses[0], + isRuleRegistryEnabled + ); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.test.ts index f8b3b834af857..ebc86acc964e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.test.ts @@ -20,7 +20,10 @@ import { getPerformBulkActionSchemaMock } from '../../../../../common/detection_ jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('perform_bulk_action', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('perform_bulk_action - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -30,9 +33,9 @@ describe('perform_bulk_action', () => { ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); - performBulkActionRoute(server.router, ml); + performBulkActionRoute(server.router, ml, isRuleRegistryEnabled); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts index 70198d081ebfa..0eba5af4e063a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts @@ -25,7 +25,8 @@ const BULK_ACTION_RULES_LIMIT = 10000; export const performBulkActionRoute = ( router: SecuritySolutionPluginRouter, - ml: SetupPlugins['ml'] + ml: SetupPlugins['ml'], + isRuleRegistryEnabled: boolean ) => { router.post( { @@ -58,6 +59,7 @@ export const performBulkActionRoute = ( } const rules = await findRules({ + isRuleRegistryEnabled, rulesClient, perPage: BULK_ACTION_RULES_LIMIT, filter: body.query !== '' ? body.query : undefined, @@ -131,7 +133,8 @@ export const performBulkActionRoute = ( case BulkAction.export: const exported = await getExportByObjectIds( rulesClient, - rules.data.map(({ params }) => ({ rule_id: params.ruleId })) + rules.data.map(({ params }) => ({ rule_id: params.ruleId })), + isRuleRegistryEnabled ); const responseBody = `${exported.rulesNdjson}${exported.exportDetails}`; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts index 586ff027425f8..057cbf4c12966 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts @@ -16,7 +16,10 @@ import { } from '../__mocks__/request_responses'; import { requestMock, requestContextMock, serverMock } from '../__mocks__'; -describe('read_signals', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('read_rules - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -24,11 +27,11 @@ describe('read_signals', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform clients.ruleExecutionLogClient.find.mockResolvedValue([]); - readRulesRoute(server.router); + readRulesRoute(server.router, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { @@ -45,7 +48,7 @@ describe('read_signals', () => { }); test('returns error if requesting a non-rule', async () => { - clients.rulesClient.find.mockResolvedValue(nonRuleFindResult()); + clients.rulesClient.find.mockResolvedValue(nonRuleFindResult(isRuleRegistryEnabled)); const response = await server.inject(getReadRequest(), context); expect(response.status).toEqual(404); expect(response.body).toEqual({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts index 7aef65e7918b2..5672648190653 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -6,7 +6,6 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { IRuleDataClient } from '../../../../../../rule_registry/server'; import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; import { queryRulesSchema, @@ -23,7 +22,7 @@ import { RuleExecutionStatus } from '../../../../../common/detection_engine/sche export const readRulesRoute = ( router: SecuritySolutionPluginRouter, - ruleDataClient?: IRuleDataClient | null + isRuleRegistryEnabled: boolean ) => { router.get( { @@ -55,8 +54,9 @@ export const readRulesRoute = ( const ruleStatusClient = context.securitySolution.getExecutionLogClient(); const rule = await readRules({ - rulesClient, id, + isRuleRegistryEnabled, + rulesClient, ruleId, }); if (rule != null) { @@ -72,7 +72,7 @@ export const readRulesRoute = ( currentStatus.attributes.statusDate = rule.executionStatus.lastExecutionDate.toISOString(); currentStatus.attributes.status = RuleExecutionStatus.failed; } - const transformed = transform(rule, currentStatus); + const transformed = transform(rule, currentStatus, isRuleRegistryEnabled); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts index eeb8b3caf6df5..746a40dfa8dc2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts @@ -23,7 +23,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('update_rules_bulk', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('update_rules_bulk - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -33,10 +36,12 @@ describe('update_rules_bulk', () => { ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + clients.rulesClient.update.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); - updateRulesBulkRoute(server.router, ml); + updateRulesBulkRoute(server.router, ml, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 389c49d3cff4e..6138690070b62 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -22,7 +22,8 @@ import { updateRules } from '../../rules/update_rules'; export const updateRulesBulkRoute = ( router: SecuritySolutionPluginRouter, - ml: SetupPlugins['ml'] + ml: SetupPlugins['ml'], + isRuleRegistryEnabled: boolean ) => { router.put( { @@ -74,6 +75,7 @@ export const updateRulesBulkRoute = ( ruleStatusClient, defaultOutputIndex: siemClient.getSignalsIndex(), ruleUpdate: payloadRule, + isRuleRegistryEnabled, }); if (rule != null) { const ruleStatuses = await ruleStatusClient.find({ @@ -81,7 +83,7 @@ export const updateRulesBulkRoute = ( ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - return transformValidateBulkError(rule.id, rule, ruleStatuses); + return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled); } else { return getIdBulkError({ id: payloadRule.id, ruleId: payloadRule.rule_id }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index db0054088137c..5b3e2737418c2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -23,7 +23,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('update_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('update_rules - %s', (_, isRuleRegistryEnabled) => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -33,12 +36,16 @@ describe('update_rules', () => { ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); - clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); // existing rule - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists - clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); // successful update + clients.rulesClient.get.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); // existing rule + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists + clients.rulesClient.update.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); // successful update clients.ruleExecutionLogClient.find.mockResolvedValue([]); // successful transform: ; - updateRulesRoute(server.router, ml); + updateRulesRoute(server.router, ml, isRuleRegistryEnabled); }); describe('status codes with actionClient and alertClient', () => { @@ -75,7 +82,7 @@ describe('update_rules', () => { }); test('returns error when updating non-rule', async () => { - clients.rulesClient.find.mockResolvedValue(nonRuleFindResult()); + clients.rulesClient.find.mockResolvedValue(nonRuleFindResult(isRuleRegistryEnabled)); const response = await server.inject(getUpdateRequest(), context); expect(response.status).toEqual(404); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index ecf61bec2b20a..7cfe83093a549 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -6,7 +6,6 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { IRuleDataClient } from '../../../../../../rule_registry/server'; import { updateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { updateRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/update_rules_type_dependents'; import type { SecuritySolutionPluginRouter } from '../../../../types'; @@ -24,7 +23,7 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v export const updateRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], - ruleDataClient?: IRuleDataClient | null + isRuleRegistryEnabled: boolean ) => { router.put( { @@ -61,11 +60,12 @@ export const updateRulesRoute = ( const ruleStatusClient = context.securitySolution.getExecutionLogClient(); const rule = await updateRules({ - spaceId: context.securitySolution.getSpaceId(), + defaultOutputIndex: siemClient.getSignalsIndex(), + isRuleRegistryEnabled, rulesClient, ruleStatusClient, - defaultOutputIndex: siemClient.getSignalsIndex(), ruleUpdate: request.body, + spaceId: context.securitySolution.getSpaceId(), }); if (rule != null) { @@ -74,7 +74,11 @@ export const updateRulesRoute = ( ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - const [validated, errors] = transformValidate(rule, ruleStatuses[0]); + const [validated, errors] = transformValidate( + rule, + ruleStatuses[0], + isRuleRegistryEnabled + ); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index 0018a37016980..f07a63b690a2d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -41,16 +41,19 @@ import { type PromiseFromStreams = ImportRulesSchemaDecoded | Error; -describe('utils', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('utils - %s', (_, isRuleRegistryEnabled) => { describe('transformAlertToRule', () => { test('should work with a full data set', () => { - const fullRule = getAlertMock(getQueryRuleParams()); + const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); const rule = transformAlertToRule(fullRule); expect(rule).toEqual(getOutputRuleAlertForRest()); }); test('should omit note if note is undefined', () => { - const fullRule = getAlertMock(getQueryRuleParams()); + const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.params.note = undefined; const rule = transformAlertToRule(fullRule); const { note, ...expectedWithoutNote } = getOutputRuleAlertForRest(); @@ -58,7 +61,7 @@ describe('utils', () => { }); test('should return enabled is equal to false', () => { - const fullRule = getAlertMock(getQueryRuleParams()); + const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.enabled = false; const ruleWithEnabledFalse = transformAlertToRule(fullRule); const expected = getOutputRuleAlertForRest(); @@ -67,7 +70,7 @@ describe('utils', () => { }); test('should return immutable is equal to false', () => { - const fullRule = getAlertMock(getQueryRuleParams()); + const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.params.immutable = false; const ruleWithEnabledFalse = transformAlertToRule(fullRule); const expected = getOutputRuleAlertForRest(); @@ -75,7 +78,7 @@ describe('utils', () => { }); test('should work with tags but filter out any internal tags', () => { - const fullRule = getAlertMock(getQueryRuleParams()); + const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.tags = ['tag 1', 'tag 2', `${INTERNAL_IDENTIFIER}_some_other_value`]; const rule = transformAlertToRule(fullRule); const expected = getOutputRuleAlertForRest(); @@ -84,7 +87,7 @@ describe('utils', () => { }); test('transforms ML Rule fields', () => { - const mlRule = getAlertMock(getMlRuleParams()); + const mlRule = getAlertMock(isRuleRegistryEnabled, getMlRuleParams()); mlRule.params.anomalyThreshold = 55; mlRule.params.machineLearningJobId = ['some_job_id']; mlRule.params.type = 'machine_learning'; @@ -100,7 +103,7 @@ describe('utils', () => { }); test('transforms threat_matching fields', () => { - const threatRule = getAlertMock(getThreatRuleParams()); + const threatRule = getAlertMock(isRuleRegistryEnabled, getThreatRuleParams()); const threatFilters: PartialFilter[] = [ { query: { @@ -153,7 +156,7 @@ describe('utils', () => { test('does not leak a lists structure in the transform which would cause validation issues', () => { const result: RuleAlertType & { lists: [] } = { lists: [], - ...getAlertMock(getQueryRuleParams()), + ...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), }; const rule = transformAlertToRule(result); expect(rule).toEqual( @@ -168,7 +171,7 @@ describe('utils', () => { test('does not leak an exceptions_list structure in the transform which would cause validation issues', () => { const result: RuleAlertType & { exceptions_list: [] } = { exceptions_list: [], - ...getAlertMock(getQueryRuleParams()), + ...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), }; const rule = transformAlertToRule(result); expect(rule).toEqual( @@ -265,7 +268,7 @@ describe('utils', () => { page: 1, perPage: 0, total: 0, - data: [getAlertMock(getQueryRuleParams())], + data: [getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())], }, {} ); @@ -281,14 +284,18 @@ describe('utils', () => { describe('transform', () => { test('outputs 200 if the data is of type siem alert', () => { - const output = transform(getAlertMock(getQueryRuleParams())); + const output = transform( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), + undefined, + isRuleRegistryEnabled + ); const expected = getOutputRuleAlertForRest(); expect(output).toEqual(expected); }); test('returns 500 if the data is not of type siem alert', () => { const unsafeCast = ({ data: [{ random: 1 }] } as unknown) as PartialAlert; - const output = transform(unsafeCast); + const output = transform(unsafeCast, undefined, isRuleRegistryEnabled); expect(output).toBeNull(); }); }); @@ -396,24 +403,34 @@ describe('utils', () => { describe('transformOrBulkError', () => { test('outputs 200 if the data is of type siem alert', () => { - const output = transformOrBulkError('rule-1', getAlertMock(getQueryRuleParams()), { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - actions: [], - ruleThrottle: 'no_actions', - alertThrottle: null, - }); + const output = transformOrBulkError( + 'rule-1', + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), + { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + actions: [], + ruleThrottle: 'no_actions', + alertThrottle: null, + }, + isRuleRegistryEnabled + ); const expected = getOutputRuleAlertForRest(); expect(output).toEqual(expected); }); test('returns 500 if the data is not of type siem alert', () => { const unsafeCast = ({ name: 'something else' } as unknown) as PartialAlert; - const output = transformOrBulkError('rule-1', unsafeCast, { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - actions: [], - ruleThrottle: 'no_actions', - alertThrottle: null, - }); + const output = transformOrBulkError( + 'rule-1', + unsafeCast, + { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + actions: [], + ruleThrottle: 'no_actions', + alertThrottle: null, + }, + isRuleRegistryEnabled + ); const expected: BulkError = { rule_id: 'rule-1', error: { message: 'Internal error transforming', status_code: 500 }, @@ -428,15 +445,15 @@ describe('utils', () => { }); test('given single alert will return the alert transformed', () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); const transformed = transformAlertsToRules([result1]); const expected = getOutputRuleAlertForRest(); expect(transformed).toEqual([expected]); }); test('given two alerts will return the two alerts transformed', () => { - const result1 = getAlertMock(getQueryRuleParams()); - const result2 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = 'some other id'; result2.params.ruleId = 'some other id'; @@ -451,11 +468,16 @@ describe('utils', () => { describe('transformOrImportError', () => { test('returns 1 given success if the alert is an alert type and the existing success count is 0', () => { - const output = transformOrImportError('rule-1', getAlertMock(getQueryRuleParams()), { - success: true, - success_count: 0, - errors: [], - }); + const output = transformOrImportError( + 'rule-1', + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), + { + success: true, + success_count: 0, + errors: [], + }, + isRuleRegistryEnabled + ); const expected: ImportSuccessError = { success: true, errors: [], @@ -465,11 +487,16 @@ describe('utils', () => { }); test('returns 2 given successes if the alert is an alert type and the existing success count is 1', () => { - const output = transformOrImportError('rule-1', getAlertMock(getQueryRuleParams()), { - success: true, - success_count: 1, - errors: [], - }); + const output = transformOrImportError( + 'rule-1', + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), + { + success: true, + success_count: 1, + errors: [], + }, + isRuleRegistryEnabled + ); const expected: ImportSuccessError = { success: true, errors: [], @@ -480,11 +507,16 @@ describe('utils', () => { test('returns 1 error and success of false if the data is not of type siem alert', () => { const unsafeCast = ({ name: 'something else' } as unknown) as PartialAlert; - const output = transformOrImportError('rule-1', unsafeCast, { - success: true, - success_count: 1, - errors: [], - }); + const output = transformOrImportError( + 'rule-1', + unsafeCast, + { + success: true, + success_count: 1, + errors: [], + }, + isRuleRegistryEnabled + ); const expected: ImportSuccessError = { success: false, errors: [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index 6e1faf819c3d5..4f023156fba06 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -135,9 +135,10 @@ export const transformFindAlerts = ( export const transform = ( alert: PartialAlert, - ruleStatus?: SavedObject + ruleStatus?: SavedObject, + isRuleRegistryEnabled?: boolean ): Partial | null => { - if (isAlertType(alert)) { + if (isAlertType(isRuleRegistryEnabled ?? false, alert)) { return transformAlertToRule( alert, isRuleStatusSavedObjectType(ruleStatus) ? ruleStatus : undefined @@ -150,9 +151,10 @@ export const transform = ( export const transformOrBulkError = ( ruleId: string, alert: PartialAlert, - ruleStatus?: unknown + ruleStatus?: unknown, + isRuleRegistryEnabled?: boolean ): Partial | BulkError => { - if (isAlertType(alert)) { + if (isAlertType(isRuleRegistryEnabled ?? false, alert)) { if (isRuleStatusFindType(ruleStatus) && ruleStatus?.saved_objects.length > 0) { return transformAlertToRule(alert, ruleStatus?.saved_objects[0] ?? ruleStatus); } else { @@ -170,9 +172,10 @@ export const transformOrBulkError = ( export const transformOrImportError = ( ruleId: string, alert: PartialAlert, - existingImportSuccessError: ImportSuccessError + existingImportSuccessError: ImportSuccessError, + isRuleRegistryEnabled: boolean ): ImportSuccessError => { - if (isAlertType(alert)) { + if (isAlertType(isRuleRegistryEnabled, alert)) { return createSuccessObject(existingImportSuccessError); } else { return createImportErrorObject({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index 9cbd4de71613a..a7ba1ac77b7bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -66,20 +66,23 @@ export const ruleOutput = (): RulesSchema => ({ timeline_id: 'some-timeline-id', }); -describe('validate', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('validate - %s', (_, isRuleRegistryEnabled) => { describe('transformValidate', () => { test('it should do a validation correctly of a partial alert', () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); - const [validated, errors] = transformValidate(ruleAlert); + const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); + const [validated, errors] = transformValidate(ruleAlert, undefined, isRuleRegistryEnabled); expect(validated).toEqual(ruleOutput()); expect(errors).toEqual(null); }); test('it should do an in-validation correctly of a partial alert', () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); // @ts-expect-error delete ruleAlert.name; - const [validated, errors] = transformValidate(ruleAlert); + const [validated, errors] = transformValidate(ruleAlert, undefined, isRuleRegistryEnabled); expect(validated).toEqual(null); expect(errors).toEqual('Invalid value "undefined" supplied to "name"'); }); @@ -87,16 +90,26 @@ describe('validate', () => { describe('transformValidateBulkError', () => { test('it should do a validation correctly of a rule id', () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); - const validatedOrError = transformValidateBulkError('rule-1', ruleAlert); + const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); + const validatedOrError = transformValidateBulkError( + 'rule-1', + ruleAlert, + undefined, + isRuleRegistryEnabled + ); expect(validatedOrError).toEqual(ruleOutput()); }); test('it should do an in-validation correctly of a rule id', () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); // @ts-expect-error delete ruleAlert.name; - const validatedOrError = transformValidateBulkError('rule-1', ruleAlert); + const validatedOrError = transformValidateBulkError( + 'rule-1', + ruleAlert, + undefined, + isRuleRegistryEnabled + ); const expected: BulkError = { error: { message: 'Invalid value "undefined" supplied to "name"', @@ -109,8 +122,13 @@ describe('validate', () => { test('it should do a validation correctly of a rule id with ruleStatus passed in', () => { const ruleStatuses = getRuleExecutionStatuses(); - const ruleAlert = getAlertMock(getQueryRuleParams()); - const validatedOrError = transformValidateBulkError('rule-1', ruleAlert, ruleStatuses); + const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); + const validatedOrError = transformValidateBulkError( + 'rule-1', + ruleAlert, + ruleStatuses, + isRuleRegistryEnabled + ); const expected: RulesSchema = { ...ruleOutput(), status: RuleExecutionStatus.succeeded, @@ -122,10 +140,15 @@ describe('validate', () => { }); test('it should return error object if "alert" is not expected alert type', () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); // @ts-expect-error delete ruleAlert.alertTypeId; - const validatedOrError = transformValidateBulkError('rule-1', ruleAlert); + const validatedOrError = transformValidateBulkError( + 'rule-1', + ruleAlert, + undefined, + isRuleRegistryEnabled + ); const expected: BulkError = { error: { message: 'Internal error transforming', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts index ccb3201848e3c..c1969c5088bc0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts @@ -29,9 +29,10 @@ import { RuleParams } from '../../schemas/rule_schemas'; export const transformValidate = ( alert: PartialAlert, - ruleStatus?: SavedObject + ruleStatus?: SavedObject, + isRuleRegistryEnabled?: boolean ): [RulesSchema | null, string | null] => { - const transformed = transform(alert, ruleStatus); + const transformed = transform(alert, ruleStatus, isRuleRegistryEnabled); if (transformed == null) { return [null, 'Internal error transforming']; } else { @@ -41,9 +42,10 @@ export const transformValidate = ( export const newTransformValidate = ( alert: PartialAlert, - ruleStatus?: SavedObject + ruleStatus?: SavedObject, + isRuleRegistryEnabled?: boolean ): [FullResponseSchema | null, string | null] => { - const transformed = transform(alert, ruleStatus); + const transformed = transform(alert, ruleStatus, isRuleRegistryEnabled); if (transformed == null) { return [null, 'Internal error transforming']; } else { @@ -54,9 +56,10 @@ export const newTransformValidate = ( export const transformValidateBulkError = ( ruleId: string, alert: PartialAlert, - ruleStatus?: Array> + ruleStatus?: Array>, + isRuleRegistryEnabled?: boolean ): RulesSchema | BulkError => { - if (isAlertType(alert)) { + if (isAlertType(isRuleRegistryEnabled ?? false, alert)) { if (ruleStatus && ruleStatus?.length > 0 && isRuleStatusSavedObjectType(ruleStatus[0])) { const transformed = transformAlertToRule(alert, ruleStatus[0]); const [validated, errors] = validateNonExact(transformed, rulesSchema); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts index e22497071b2b7..04464e5d6f5a7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts @@ -12,7 +12,10 @@ import { buildSiemResponse } from '../utils'; import { readTags } from '../../tags/read_tags'; -export const readTagsRoute = (router: SecuritySolutionPluginRouter) => { +export const readTagsRoute = ( + router: SecuritySolutionPluginRouter, + isRuleRegistryEnabled: boolean +) => { router.get( { path: DETECTION_ENGINE_TAGS_URL, @@ -31,6 +34,7 @@ export const readTagsRoute = (router: SecuritySolutionPluginRouter) => { try { const tags = await readTags({ + isRuleRegistryEnabled, rulesClient, }); return response.ok({ body: tags }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts index c7e1f9f2e6bd7..23a65b456e6bc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts @@ -33,7 +33,10 @@ import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas let rulesClient: ReturnType; -describe('utils', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('utils - %s', (_, isRuleRegistryEnabled) => { describe('transformBulkError', () => { test('returns transformed object if it is a boom object', () => { const boom = new Boom.Boom('some boom message', { statusCode: 400 }); @@ -390,12 +393,12 @@ describe('utils', () => { rulesClient = rulesClientMock.create(); }); it('getFailingRules finds no failing rules', async () => { - rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); + rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); const res = await getFailingRules(['my-fake-id'], rulesClient); expect(res).toEqual({}); }); it('getFailingRules finds a failing rule', async () => { - const foundRule = getAlertMock(getQueryRuleParams()); + const foundRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); foundRule.executionStatus = { status: 'error', lastExecutionDate: foundRule.executionStatus.lastExecutionDate, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 8f078b01daf73..775115ded7c4f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -328,6 +328,6 @@ export const getFailingRules = async ( if (Boom.isBoom(exc)) { throw exc; } - throw new Error(`Failed to get executionStatus with RulesClient: ${exc.message}`); + throw new Error(`Failed to get executionStatus with RulesClient: ${(exc as Error).message}`); } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts index f2dfe69debed0..e2d5da1def707 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts @@ -7,7 +7,7 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { PersistenceServices } from '../../../../../../rule_registry/server'; -import { INDICATOR_ALERT_TYPE_ID } from '../../../../../common/constants'; +import { INDICATOR_RULE_TYPE_ID } from '../../../../../common/constants'; import { threatRuleParams, ThreatRuleParams } from '../../schemas/rule_schemas'; import { threatMatchExecutor } from '../../signals/executors/threat_match'; import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory'; @@ -33,7 +33,7 @@ export const createIndicatorMatchAlertType = (createOptions: CreateRuleOptions) ruleDataService, }); return createSecurityRuleType({ - id: INDICATOR_ALERT_TYPE_ID, + id: INDICATOR_RULE_TYPE_ID, name: 'Indicator Match Rule', validate: { params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml.ts index 14252bf62ef83..e0ad333b76a24 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml.ts @@ -14,7 +14,7 @@ import { SavedObject } from 'src/core/types'; import { buildEsQuery, IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { createPersistenceRuleTypeFactory } from '../../../../../rule_registry/server'; -import { ML_ALERT_TYPE_ID } from '../../../../common/constants'; +import { ML_RULE_TYPE_ID } from '../../../../common/constants'; import { SecurityRuleRegistry } from '../../../plugin'; const createSecurityMlRuleType = createPersistenceRuleTypeFactory(); @@ -38,7 +38,7 @@ import { MachineLearningRuleAttributes } from '../signals/types'; import { createErrorsFromShard, createSearchAfterReturnType, mergeReturns } from '../signals/utils'; export const mlAlertType = createSecurityMlRuleType({ - id: ML_ALERT_TYPE_ID, + id: ML_RULE_TYPE_ID, name: 'Machine Learning Rule', validate: { params: schema.object({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts index cdaeb4be76d02..7d891d430c63c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts @@ -7,7 +7,7 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { PersistenceServices } from '../../../../../../rule_registry/server'; -import { ML_ALERT_TYPE_ID } from '../../../../../common/constants'; +import { ML_RULE_TYPE_ID } from '../../../../../common/constants'; import { machineLearningRuleParams, MachineLearningRuleParams } from '../../schemas/rule_schemas'; import { mlExecutor } from '../../signals/executors/ml'; import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory'; @@ -32,7 +32,7 @@ export const createMlAlertType = (createOptions: CreateRuleOptions) => { ruleDataService, }); return createSecurityRuleType({ - id: ML_ALERT_TYPE_ID, + id: ML_RULE_TYPE_ID, name: 'Machine Learning Rule', validate: { params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index 2f185853754b3..d5af7a4c8b5a4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -7,7 +7,7 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { PersistenceServices } from '../../../../../../rule_registry/server'; -import { QUERY_ALERT_TYPE_ID } from '../../../../../common/constants'; +import { QUERY_RULE_TYPE_ID } from '../../../../../common/constants'; import { queryRuleParams, QueryRuleParams } from '../../schemas/rule_schemas'; import { queryExecutor } from '../../signals/executors/query'; import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory'; @@ -33,7 +33,7 @@ export const createQueryAlertType = (createOptions: CreateRuleOptions) => { ruleDataService, }); return createSecurityRuleType({ - id: QUERY_ALERT_TYPE_ID, + id: QUERY_RULE_TYPE_ID, name: 'Custom Query Rule', validate: { params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts index 34fb7bf5f8291..2c25134cc3760 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts @@ -8,7 +8,8 @@ import { CreateRulesOptions } from './types'; import { rulesClientMock } from '../../../../../alerting/server/mocks'; -export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ +export const getCreateRulesOptionsMock = (isRuleRegistryEnabled: boolean): CreateRulesOptions => ({ + isRuleRegistryEnabled, author: ['Elastic'], buildingBlockType: undefined, rulesClient: rulesClientMock.create(), @@ -61,7 +62,10 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ actions: [], }); -export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ +export const getCreateMlRulesOptionsMock = ( + isRuleRegistryEnabled: boolean +): CreateRulesOptions => ({ + isRuleRegistryEnabled, author: ['Elastic'], buildingBlockType: undefined, rulesClient: rulesClientMock.create(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts index 3dd29977a8f2c..0fd708791712a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts @@ -8,9 +8,12 @@ import { createRules } from './create_rules'; import { getCreateMlRulesOptionsMock } from './create_rules.mock'; -describe('createRules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('createRules - %s', (_, isRuleRegistryEnabled) => { it('calls the rulesClient with legacy ML params', async () => { - const ruleOptions = getCreateMlRulesOptionsMock(); + const ruleOptions = getCreateMlRulesOptionsMock(isRuleRegistryEnabled); await createRules(ruleOptions); expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ @@ -26,7 +29,7 @@ describe('createRules', () => { it('calls the rulesClient with ML params', async () => { const ruleOptions = { - ...getCreateMlRulesOptionsMock(), + ...getCreateMlRulesOptionsMock(isRuleRegistryEnabled), machineLearningJobId: ['new_job_1', 'new_job_2'], }; await createRules(ruleOptions); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index bc415a0de6961..bed6bf4303897 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -20,6 +20,7 @@ import { CreateRulesOptions } from './types'; import { addTags } from './add_tags'; import { PartialFilter, RuleTypeParams } from '../types'; import { transformToAlertThrottle, transformToNotifyWhen } from './utils'; +import { ruleTypeMappings } from '../signals/utils'; export const createRules = async ({ rulesClient, @@ -68,16 +69,18 @@ export const createRules = async ({ to, type, references, + namespace, note, version, exceptionsList, actions, + isRuleRegistryEnabled, }: CreateRulesOptions): Promise> => { const rule = await rulesClient.create({ data: { name, tags: addTags(tags, ruleId, immutable), - alertTypeId: SIGNALS_ID, + alertTypeId: isRuleRegistryEnabled ? ruleTypeMappings[type] : SIGNALS_ID, consumer: SERVER_APP_ID, params: { anomalyThreshold, @@ -125,6 +128,7 @@ export const createRules = async ({ to, type, references, + namespace, note, version, exceptionsList, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts index 92b4dcff61b35..c3f6b0fbead91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts @@ -52,6 +52,7 @@ describe('duplicateRule', () => { query: 'process.args : "chmod"', filters: [], buildingBlockType: undefined, + namespace: undefined, note: undefined, timelineId: undefined, timelineTitle: undefined, @@ -99,6 +100,7 @@ describe('duplicateRule', () => { "license": "", "maxSignals": 100, "meta": undefined, + "namespace": undefined, "note": undefined, "outputIndex": ".siem-signals-default", "query": "process.args : \\"chmod\\"", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.test.ts index 1f91355d7cde2..0f7545c4df936 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.test.ts @@ -6,16 +6,38 @@ */ import { getFilter } from './find_rules'; -import { SIGNALS_ID } from '../../../../common/constants'; +import { + INDICATOR_RULE_TYPE_ID, + ML_RULE_TYPE_ID, + QUERY_RULE_TYPE_ID, + SIGNALS_ID, +} from '../../../../common/constants'; + +const allAlertTypeIds = `(alert.attributes.alertTypeId: ${ML_RULE_TYPE_ID} + OR alert.attributes.alertTypeId: ${QUERY_RULE_TYPE_ID} + OR alert.attributes.alertTypeId: ${INDICATOR_RULE_TYPE_ID})`.replace(/[\n\r]/g, ''); describe('find_rules', () => { - test('it returns a full filter with an AND if sent down', () => { - expect(getFilter('alert.attributes.enabled: true')).toEqual( - `alert.attributes.alertTypeId: ${SIGNALS_ID} AND alert.attributes.enabled: true` - ); - }); + const fullFilterTestCases: Array<[boolean, string]> = [ + [false, `alert.attributes.alertTypeId: ${SIGNALS_ID} AND alert.attributes.enabled: true`], + [true, `${allAlertTypeIds} AND alert.attributes.enabled: true`], + ]; + const nullFilterTestCases: Array<[boolean, string]> = [ + [false, `alert.attributes.alertTypeId: ${SIGNALS_ID}`], + [true, allAlertTypeIds], + ]; + + test.each(fullFilterTestCases)( + 'it returns a full filter with an AND if sent down [rule registry enabled: %p]', + (isRuleRegistryEnabled, expected) => { + expect(getFilter('alert.attributes.enabled: true', isRuleRegistryEnabled)).toEqual(expected); + } + ); - test('it returns existing filter with no AND when not set', () => { - expect(getFilter(null)).toEqual(`alert.attributes.alertTypeId: ${SIGNALS_ID}`); - }); + test.each(nullFilterTestCases)( + 'it returns existing filter with no AND when not set [rule registry enabled: %p]', + (isRuleRegistryEnabled, expected) => { + expect(getFilter(null, isRuleRegistryEnabled)).toEqual(expected); + } + ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts index e41dac066e18a..a1664f2e5a310 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts @@ -8,13 +8,23 @@ import { FindResult } from '../../../../../alerting/server'; import { SIGNALS_ID } from '../../../../common/constants'; import { RuleParams } from '../schemas/rule_schemas'; +import { ruleTypeMappings } from '../signals/utils'; import { FindRuleOptions } from './types'; -export const getFilter = (filter: string | null | undefined) => { +export const getFilter = ( + filter: string | null | undefined, + isRuleRegistryEnabled: boolean = false +) => { + const alertTypeFilter = isRuleRegistryEnabled + ? `(${Object.values(ruleTypeMappings) + .map((type) => (type !== SIGNALS_ID ? `alert.attributes.alertTypeId: ${type}` : undefined)) + .filter((type) => type != null) + .join(' OR ')})` + : `alert.attributes.alertTypeId: ${SIGNALS_ID}`; if (filter == null) { - return `alert.attributes.alertTypeId: ${SIGNALS_ID}`; + return alertTypeFilter; } else { - return `alert.attributes.alertTypeId: ${SIGNALS_ID} AND ${filter}`; + return `${alertTypeFilter} AND ${filter}`; } }; @@ -26,13 +36,14 @@ export const findRules = ({ filter, sortField, sortOrder, + isRuleRegistryEnabled, }: FindRuleOptions): Promise> => { return rulesClient.find({ options: { fields, page, perPage, - filter: getFilter(filter), + filter: getFilter(filter, isRuleRegistryEnabled), sortOrder, sortField, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts index 19a6a4e43d877..b478cc2df09a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts @@ -20,7 +20,10 @@ import { getNonPackagedRulesCount, } from './get_existing_prepackaged_rules'; -describe('get_existing_prepackaged_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('get_existing_prepackaged_rules - %s', (_, isRuleRegistryEnabled) => { afterEach(() => { jest.resetAllMocks(); }); @@ -28,23 +31,23 @@ describe('get_existing_prepackaged_rules', () => { describe('getExistingPrepackagedRules', () => { test('should return a single item in a single page', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - const rules = await getExistingPrepackagedRules({ rulesClient }); - expect(rules).toEqual([getAlertMock(getQueryRuleParams())]); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + const rules = await getExistingPrepackagedRules({ isRuleRegistryEnabled, rulesClient }); + expect(rules).toEqual([getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())]); }); test('should return 3 items over 1 page with all on one page', async () => { const rulesClient = rulesClientMock.create(); - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.params.immutable = true; result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.params.immutable = true; result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - const result3 = getAlertMock(getQueryRuleParams()); + const result3 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result3.params.immutable = true; result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; @@ -68,7 +71,7 @@ describe('get_existing_prepackaged_rules', () => { }) ); - const rules = await getExistingPrepackagedRules({ rulesClient }); + const rules = await getExistingPrepackagedRules({ isRuleRegistryEnabled, rulesClient }); expect(rules).toEqual([result1, result2, result3]); }); }); @@ -76,18 +79,18 @@ describe('get_existing_prepackaged_rules', () => { describe('getNonPackagedRules', () => { test('should return a single item in a single page', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - const rules = await getNonPackagedRules({ rulesClient }); - expect(rules).toEqual([getAlertMock(getQueryRuleParams())]); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + const rules = await getNonPackagedRules({ isRuleRegistryEnabled, rulesClient }); + expect(rules).toEqual([getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())]); }); test('should return 2 items over 1 page', async () => { const rulesClient = rulesClientMock.create(); - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; // first result mock which is for returning the total @@ -105,20 +108,20 @@ describe('get_existing_prepackaged_rules', () => { getFindResultWithMultiHits({ data: [result1, result2], perPage: 2, page: 1, total: 2 }) ); - const rules = await getNonPackagedRules({ rulesClient }); + const rules = await getNonPackagedRules({ isRuleRegistryEnabled, rulesClient }); expect(rules).toEqual([result1, result2]); }); test('should return 3 items over 1 page with all on one page', async () => { const rulesClient = rulesClientMock.create(); - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - const result3 = getAlertMock(getQueryRuleParams()); + const result3 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; // first result mock which is for returning the total @@ -141,7 +144,7 @@ describe('get_existing_prepackaged_rules', () => { }) ); - const rules = await getNonPackagedRules({ rulesClient }); + const rules = await getNonPackagedRules({ isRuleRegistryEnabled, rulesClient }); expect(rules).toEqual([result1, result2, result3]); }); }); @@ -149,18 +152,18 @@ describe('get_existing_prepackaged_rules', () => { describe('getRules', () => { test('should return a single item in a single page', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - const rules = await getRules({ rulesClient, filter: '' }); - expect(rules).toEqual([getAlertMock(getQueryRuleParams())]); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + const rules = await getRules({ isRuleRegistryEnabled, rulesClient, filter: '' }); + expect(rules).toEqual([getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())]); }); test('should return 2 items over two pages, one per page', async () => { const rulesClient = rulesClientMock.create(); - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; // first result mock which is for returning the total @@ -178,7 +181,7 @@ describe('get_existing_prepackaged_rules', () => { getFindResultWithMultiHits({ data: [result1, result2], perPage: 2, page: 1, total: 2 }) ); - const rules = await getRules({ rulesClient, filter: '' }); + const rules = await getRules({ isRuleRegistryEnabled, rulesClient, filter: '' }); expect(rules).toEqual([result1, result2]); }); }); @@ -186,8 +189,8 @@ describe('get_existing_prepackaged_rules', () => { describe('getRulesCount', () => { test('it returns a count', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - const rules = await getRulesCount({ rulesClient, filter: '' }); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + const rules = await getRulesCount({ isRuleRegistryEnabled, rulesClient, filter: '' }); expect(rules).toEqual(1); }); }); @@ -195,8 +198,8 @@ describe('get_existing_prepackaged_rules', () => { describe('getNonPackagedRulesCount', () => { test('it returns a count', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - const rules = await getNonPackagedRulesCount({ rulesClient }); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + const rules = await getNonPackagedRulesCount({ isRuleRegistryEnabled, rulesClient }); expect(rules).toEqual(1); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts index be8bf1303846d..caa32a809b0a8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts @@ -14,21 +14,26 @@ export const FILTER_NON_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IM export const FILTER_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`; export const getNonPackagedRulesCount = async ({ + isRuleRegistryEnabled, rulesClient, }: { + isRuleRegistryEnabled: boolean; rulesClient: RulesClient; }): Promise => { - return getRulesCount({ rulesClient, filter: FILTER_NON_PREPACKED_RULES }); + return getRulesCount({ isRuleRegistryEnabled, rulesClient, filter: FILTER_NON_PREPACKED_RULES }); }; export const getRulesCount = async ({ rulesClient, filter, + isRuleRegistryEnabled, }: { rulesClient: RulesClient; filter: string; + isRuleRegistryEnabled: boolean; }): Promise => { const firstRule = await findRules({ + isRuleRegistryEnabled, rulesClient, filter, perPage: 1, @@ -43,12 +48,15 @@ export const getRulesCount = async ({ export const getRules = async ({ rulesClient, filter, + isRuleRegistryEnabled, }: { rulesClient: RulesClient; filter: string; -}): Promise => { - const count = await getRulesCount({ rulesClient, filter }); + isRuleRegistryEnabled: boolean; +}) => { + const count = await getRulesCount({ rulesClient, filter, isRuleRegistryEnabled }); const rules = await findRules({ + isRuleRegistryEnabled, rulesClient, filter, perPage: count, @@ -58,7 +66,7 @@ export const getRules = async ({ fields: undefined, }); - if (isAlertTypes(rules.data)) { + if (isAlertTypes(isRuleRegistryEnabled, rules.data)) { return rules.data; } else { // If this was ever true, you have a really messed up system. @@ -69,22 +77,28 @@ export const getRules = async ({ export const getNonPackagedRules = async ({ rulesClient, + isRuleRegistryEnabled, }: { rulesClient: RulesClient; + isRuleRegistryEnabled: boolean; }): Promise => { return getRules({ rulesClient, filter: FILTER_NON_PREPACKED_RULES, + isRuleRegistryEnabled, }); }; export const getExistingPrepackagedRules = async ({ rulesClient, + isRuleRegistryEnabled, }: { rulesClient: RulesClient; + isRuleRegistryEnabled: boolean; }): Promise => { return getRules({ rulesClient, filter: FILTER_PREPACKED_RULES, + isRuleRegistryEnabled, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts index 2870bee99e51a..3ca5960d7d4e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts @@ -16,11 +16,14 @@ import { getListArrayMock } from '../../../../common/detection_engine/schemas/ty import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -describe('getExportAll', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('getExportAll - %s', (_, isRuleRegistryEnabled) => { test('it exports everything from the alerts client', async () => { const rulesClient = rulesClientMock.create(); - const result = getFindResultWithSingleHit(); - const alert = getAlertMock(getQueryRuleParams()); + const result = getFindResultWithSingleHit(isRuleRegistryEnabled); + const alert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); alert.params = { ...alert.params, filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], @@ -32,7 +35,7 @@ describe('getExportAll', () => { result.data = [alert]; rulesClient.find.mockResolvedValue(result); - const exports = await getExportAll(rulesClient); + const exports = await getExportAll(rulesClient, isRuleRegistryEnabled); const rulesJson = JSON.parse(exports.rulesNdjson); const detailsJson = JSON.parse(exports.exportDetails); expect(rulesJson).toEqual({ @@ -94,7 +97,7 @@ describe('getExportAll', () => { rulesClient.find.mockResolvedValue(findResult); - const exports = await getExportAll(rulesClient); + const exports = await getExportAll(rulesClient, isRuleRegistryEnabled); expect(exports).toEqual({ rulesNdjson: '', exportDetails: '{"exported_count":0,"missing_rules":[],"missing_rules_count":0}\n', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts index 4a79f0089491f..f44471e6e26f9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts @@ -12,12 +12,13 @@ import { transformAlertsToRules } from '../routes/rules/utils'; import { transformDataToNdjson } from '../../../utils/read_stream/create_stream_from_ndjson'; export const getExportAll = async ( - rulesClient: RulesClient + rulesClient: RulesClient, + isRuleRegistryEnabled: boolean ): Promise<{ rulesNdjson: string; exportDetails: string; }> => { - const ruleAlertTypes = await getNonPackagedRules({ rulesClient }); + const ruleAlertTypes = await getNonPackagedRules({ rulesClient, isRuleRegistryEnabled }); const rules = transformAlertsToRules(ruleAlertTypes); // We do not support importing/exporting actions. When we do, delete this line of code const rulesWithoutActions = rules.map((rule) => ({ ...rule, actions: [] })); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index f4325086e4212..740427e44b560 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -16,7 +16,10 @@ import { getListArrayMock } from '../../../../common/detection_engine/schemas/ty import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -describe('get_export_by_object_ids', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('get_export_by_object_ids - %s', (_, isRuleRegistryEnabled) => { beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); @@ -25,10 +28,10 @@ describe('get_export_by_object_ids', () => { describe('getExportByObjectIds', () => { test('it exports object ids into an expected string with new line characters', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const objects = [{ rule_id: 'rule-1' }]; - const exports = await getExportByObjectIds(rulesClient, objects); + const exports = await getExportByObjectIds(rulesClient, objects, isRuleRegistryEnabled); const exportsObj = { rulesNdjson: JSON.parse(exports.rulesNdjson), exportDetails: JSON.parse(exports.exportDetails), @@ -85,7 +88,7 @@ describe('get_export_by_object_ids', () => { test('it does not export immutable rules', async () => { const rulesClient = rulesClientMock.create(); - const result = getAlertMock(getQueryRuleParams()); + const result = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result.params.immutable = true; const findResult: FindHit = { @@ -95,11 +98,11 @@ describe('get_export_by_object_ids', () => { data: [result], }; - rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); + rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); rulesClient.find.mockResolvedValue(findResult); const objects = [{ rule_id: 'rule-1' }]; - const exports = await getExportByObjectIds(rulesClient, objects); + const exports = await getExportByObjectIds(rulesClient, objects, isRuleRegistryEnabled); expect(exports).toEqual({ rulesNdjson: '', exportDetails: @@ -111,10 +114,10 @@ describe('get_export_by_object_ids', () => { describe('getRulesFromObjects', () => { test('it returns transformed rules from objects sent in', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const objects = [{ rule_id: 'rule-1' }]; - const exports = await getRulesFromObjects(rulesClient, objects); + const exports = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled); const expected: RulesErrors = { exportedCount: 1, missingRules: [], @@ -175,7 +178,7 @@ describe('get_export_by_object_ids', () => { test('it does not transform the rule if the rule is an immutable rule and designates it as a missing rule', async () => { const rulesClient = rulesClientMock.create(); - const result = getAlertMock(getQueryRuleParams()); + const result = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result.params.immutable = true; const findResult: FindHit = { @@ -189,7 +192,7 @@ describe('get_export_by_object_ids', () => { rulesClient.find.mockResolvedValue(findResult); const objects = [{ rule_id: 'rule-1' }]; - const exports = await getRulesFromObjects(rulesClient, objects); + const exports = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled); const expected: RulesErrors = { exportedCount: 0, missingRules: [{ rule_id: 'rule-1' }], @@ -212,7 +215,7 @@ describe('get_export_by_object_ids', () => { rulesClient.find.mockResolvedValue(findResult); const objects = [{ rule_id: 'rule-1' }]; - const exports = await getRulesFromObjects(rulesClient, objects); + const exports = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled); const expected: RulesErrors = { exportedCount: 0, missingRules: [{ rule_id: 'rule-1' }], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts index 812310bcb501a..31a7604306de7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -34,12 +34,13 @@ export interface RulesErrors { export const getExportByObjectIds = async ( rulesClient: RulesClient, - objects: Array<{ rule_id: string }> + objects: Array<{ rule_id: string }>, + isRuleRegistryEnabled: boolean ): Promise<{ rulesNdjson: string; exportDetails: string; }> => { - const rulesAndErrors = await getRulesFromObjects(rulesClient, objects); + const rulesAndErrors = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled); // We do not support importing/exporting actions. When we do, delete this line of code const rulesWithoutActions = rulesAndErrors.rules.map((rule) => ({ ...rule, actions: [] })); const rulesNdjson = transformDataToNdjson(rulesWithoutActions); @@ -49,7 +50,8 @@ export const getExportByObjectIds = async ( export const getRulesFromObjects = async ( rulesClient: RulesClient, - objects: Array<{ rule_id: string }> + objects: Array<{ rule_id: string }>, + isRuleRegistryEnabled: boolean ): Promise => { // If we put more than 1024 ids in one block like "alert.attributes.tags: (id1 OR id2 OR ... OR id1100)" // then the KQL -> ES DSL query generator still puts them all in the same "should" array, but ES defaults @@ -67,6 +69,7 @@ export const getRulesFromObjects = async ( }) .join(' OR '); const rules = await findRules({ + isRuleRegistryEnabled, rulesClient, filter, page: 1, @@ -79,7 +82,7 @@ export const getRulesFromObjects = async ( const matchingRule = rules.data.find((rule) => rule.params.ruleId === ruleId); if ( matchingRule != null && - isAlertType(matchingRule) && + isAlertType(isRuleRegistryEnabled, matchingRule) && matchingRule.params.immutable !== true ) { return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts index 7482097aafd22..4ef84fd1619ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts @@ -9,57 +9,61 @@ import { getRulesToInstall } from './get_rules_to_install'; import { getAlertMock } from '../routes/__mocks__/request_responses'; import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request'; -describe('get_rules_to_install', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('get_rules_to_install - %s', (_, isRuleRegistryEnabled) => { test('should return empty array if both rule sets are empty', () => { const update = getRulesToInstall([], []); expect(update).toEqual([]); }); test('should return empty array if the two rule ids match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem.rule_id = 'rule-1'; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; const update = getRulesToInstall([ruleFromFileSystem], [installedRule]); expect(update).toEqual([]); }); test('should return the rule to install if the id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem.rule_id = 'rule-1'; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-2'; const update = getRulesToInstall([ruleFromFileSystem], [installedRule]); expect(update).toEqual([ruleFromFileSystem]); }); test('should return two rules to install if both the ids of the two rules do not match', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem2.rule_id = 'rule-2'; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-3'; const update = getRulesToInstall([ruleFromFileSystem1, ruleFromFileSystem2], [installedRule]); expect(update).toEqual([ruleFromFileSystem1, ruleFromFileSystem2]); }); test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem2.rule_id = 'rule-2'; - const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem3.rule_id = 'rule-3'; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-3'; const update = getRulesToInstall( [ruleFromFileSystem1, ruleFromFileSystem2, ruleFromFileSystem3], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts index 65f3d4661cc4f..a9e22562606a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts @@ -11,7 +11,7 @@ import { RuleAlertType } from './types'; export const getRulesToInstall = ( rulesFromFileSystem: AddPrepackagedRulesSchemaDecoded[], installedRules: RuleAlertType[] -): AddPrepackagedRulesSchemaDecoded[] => { +) => { return rulesFromFileSystem.filter( (rule) => !installedRules.some((installedRule) => installedRule.params.ruleId === rule.rule_id) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts index 163585e7594ab..dda3bf6efe44c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts @@ -10,7 +10,10 @@ import { getAlertMock } from '../routes/__mocks__/request_responses'; import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -describe('get_rules_to_update', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('get_rules_to_update - %s', (_, isRuleRegistryEnabled) => { describe('get_rules_to_update', () => { test('should return empty array if both rule sets are empty', () => { const update = getRulesToUpdate([], []); @@ -22,7 +25,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-2'; installedRule.params.version = 1; const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]); @@ -34,7 +37,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 2; const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]); @@ -46,7 +49,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 1; const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]); @@ -58,7 +61,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 1; installedRule.params.exceptionsList = []; @@ -72,12 +75,12 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = []; - const installedRule2 = getAlertMock(getQueryRuleParams()); + const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule2.params.ruleId = 'rule-2'; installedRule2.params.version = 1; installedRule2.params.exceptionsList = []; @@ -95,12 +98,12 @@ describe('get_rules_to_update', () => { ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = []; - const installedRule2 = getAlertMock(getQueryRuleParams()); + const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule2.params.ruleId = 'rule-2'; installedRule2.params.version = 1; installedRule2.params.exceptionsList = []; @@ -125,7 +128,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = []; @@ -147,7 +150,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ @@ -179,7 +182,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ @@ -201,7 +204,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ @@ -228,7 +231,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ @@ -239,7 +242,7 @@ describe('get_rules_to_update', () => { type: 'endpoint', }, ]; - const installedRule2 = getAlertMock(getQueryRuleParams()); + const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule2.params.ruleId = 'rule-2'; installedRule2.params.version = 1; installedRule2.params.exceptionsList = [ @@ -278,7 +281,7 @@ describe('get_rules_to_update', () => { }, ]; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ @@ -290,7 +293,7 @@ describe('get_rules_to_update', () => { }, ]; - const installedRule2 = getAlertMock(getQueryRuleParams()); + const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule2.params.ruleId = 'rule-2'; installedRule2.params.version = 1; installedRule2.params.exceptionsList = [ @@ -320,7 +323,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-2'; installedRule.params.version = 1; const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]); @@ -332,7 +335,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 2; const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]); @@ -344,7 +347,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 1; const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]); @@ -356,7 +359,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; - const installedRule = getAlertMock(getQueryRuleParams()); + const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 1; installedRule.params.exceptionsList = []; @@ -380,7 +383,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = []; @@ -402,7 +405,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ @@ -434,7 +437,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ @@ -456,7 +459,7 @@ describe('get_rules_to_update', () => { ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const installedRule1 = getAlertMock(getQueryRuleParams()); + const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); installedRule1.params.ruleId = 'rule-1'; installedRule1.params.version = 1; installedRule1.params.exceptionsList = [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts index 7e098a28552a0..f0017c5e4bdb6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts @@ -18,7 +18,7 @@ import { RuleAlertType } from './types'; export const getRulesToUpdate = ( rulesFromFileSystem: AddPrepackagedRulesSchemaDecoded[], installedRules: RuleAlertType[] -): AddPrepackagedRulesSchemaDecoded[] => { +) => { return rulesFromFileSystem .filter((ruleFromFileSystem) => filterInstalledRules(ruleFromFileSystem, installedRules)) .map((ruleFromFileSystem) => mergeExceptionLists(ruleFromFileSystem, installedRules)); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 1681ac7f1659f..3f7191a970020 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -14,7 +14,8 @@ import { PartialFilter } from '../types'; export const installPrepackagedRules = ( rulesClient: RulesClient, rules: AddPrepackagedRulesSchemaDecoded[], - outputIndex: string + outputIndex: string, + isRuleRegistryEnabled: boolean ): Array>> => rules.reduce>>>((acc, rule) => { const { @@ -60,6 +61,7 @@ export const installPrepackagedRules = ( threshold, timestamp_override: timestampOverride, references, + namespace, note, version, exceptions_list: exceptionsList, @@ -70,6 +72,7 @@ export const installPrepackagedRules = ( return [ ...acc, createRules({ + isRuleRegistryEnabled, rulesClient, anomalyThreshold, author, @@ -116,6 +119,7 @@ export const installPrepackagedRules = ( throttle: null, // At this time there is no pre-packaged actions timestampOverride, references, + namespace, note, version, exceptionsList, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index 9ebec947bcc0e..1d09e4ca5c508 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -11,7 +11,7 @@ import { getAlertMock } from '../routes/__mocks__/request_responses'; import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client'; -export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ +export const getPatchRulesOptionsMock = (isRuleRegistryEnabled: boolean): PatchRulesOptions => ({ author: ['Elastic'], buildingBlockType: undefined, rulesClient: rulesClientMock.create(), @@ -61,10 +61,10 @@ export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ version: 1, exceptionsList: [], actions: [], - rule: getAlertMock(getQueryRuleParams()), + rule: getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), }); -export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ +export const getPatchMlRulesOptionsMock = (isRuleRegistryEnabled: boolean): PatchRulesOptions => ({ author: ['Elastic'], buildingBlockType: undefined, rulesClient: rulesClientMock.create(), @@ -114,5 +114,5 @@ export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ version: 1, exceptionsList: [], actions: [], - rule: getAlertMock(getMlRuleParams()), + rule: getAlertMock(isRuleRegistryEnabled, getMlRuleParams()), }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts index dbfc1427abf95..ee39120a8b6d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts @@ -12,15 +12,18 @@ import { RulesClientMock } from '../../../../../alerting/server/rules_client.moc import { getAlertMock } from '../routes/__mocks__/request_responses'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -describe('patchRules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('patchRules - %s', (_, isRuleRegistryEnabled) => { it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); + const rulesOptionsMock = getPatchRulesOptionsMock(isRuleRegistryEnabled); const ruleOptions: PatchRulesOptions = { ...rulesOptionsMock, enabled: false, }; ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); expect(ruleOptions.rulesClient.disable).toHaveBeenCalledWith( @@ -31,7 +34,7 @@ describe('patchRules', () => { }); it('should call rulesClient.enable if the rule was disabled and enabled is true', async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); + const rulesOptionsMock = getPatchRulesOptionsMock(isRuleRegistryEnabled); const ruleOptions: PatchRulesOptions = { ...rulesOptionsMock, enabled: true, @@ -40,7 +43,7 @@ describe('patchRules', () => { ruleOptions.rule.enabled = false; } ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); expect(ruleOptions.rulesClient.enable).toHaveBeenCalledWith( @@ -51,7 +54,7 @@ describe('patchRules', () => { }); it('calls the rulesClient with legacy ML params', async () => { - const rulesOptionsMock = getPatchMlRulesOptionsMock(); + const rulesOptionsMock = getPatchMlRulesOptionsMock(isRuleRegistryEnabled); const ruleOptions: PatchRulesOptions = { ...rulesOptionsMock, enabled: true, @@ -60,7 +63,7 @@ describe('patchRules', () => { ruleOptions.rule.enabled = false; } ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( @@ -76,7 +79,7 @@ describe('patchRules', () => { }); it('calls the rulesClient with new ML params', async () => { - const rulesOptionsMock = getPatchMlRulesOptionsMock(); + const rulesOptionsMock = getPatchMlRulesOptionsMock(isRuleRegistryEnabled); const ruleOptions: PatchRulesOptions = { ...rulesOptionsMock, machineLearningJobId: ['new_job_1', 'new_job_2'], @@ -86,7 +89,7 @@ describe('patchRules', () => { ruleOptions.rule.enabled = false; } ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( @@ -103,7 +106,7 @@ describe('patchRules', () => { describe('regression tests', () => { it("updates the rule's actions if provided", async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); + const rulesOptionsMock = getPatchRulesOptionsMock(isRuleRegistryEnabled); const ruleOptions: PatchRulesOptions = { ...rulesOptionsMock, actions: [ @@ -118,7 +121,7 @@ describe('patchRules', () => { ], }; ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( @@ -140,7 +143,7 @@ describe('patchRules', () => { }); it('does not update actions if none are specified', async () => { - const ruleOptions = getPatchRulesOptionsMock(); + const ruleOptions = getPatchRulesOptionsMock(isRuleRegistryEnabled); delete ruleOptions.actions; if (ruleOptions.rule != null) { ruleOptions.rule.actions = [ @@ -155,7 +158,7 @@ describe('patchRules', () => { ]; } ((ruleOptions.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index bc1faa5dff470..c3b7e7288dc57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -80,6 +80,7 @@ export const patchRules = async ({ to, type, references, + namespace, note, version, exceptionsList, @@ -131,6 +132,7 @@ export const patchRules = async ({ type, references, version, + namespace, note, exceptionsList, anomalyThreshold, @@ -176,6 +178,7 @@ export const patchRules = async ({ to, type, references, + namespace, note, version: calculatedVersion, exceptionsList, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.test.ts index 33bc002942497..6f89d725a458e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.test.ts @@ -21,7 +21,10 @@ export class TestError extends Error { public output: { statusCode: number }; } -describe('read_rules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('read_rules - %s', (_, isRuleRegistryEnabled) => { beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); @@ -30,23 +33,25 @@ describe('read_rules', () => { describe('readRules', () => { test('should return the output from rulesClient if id is set but ruleId is undefined', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); + rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', ruleId: undefined, }); - expect(rule).toEqual(getAlertMock(getQueryRuleParams())); + expect(rule).toEqual(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); }); test('should return null if saved object found by alerts client given id is not alert type', async () => { const rulesClient = rulesClientMock.create(); - const result = getAlertMock(getQueryRuleParams()); + const result = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); // @ts-expect-error delete result.alertTypeId; rulesClient.get.mockResolvedValue(result); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', ruleId: undefined, @@ -61,6 +66,7 @@ describe('read_rules', () => { }); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', ruleId: undefined, @@ -75,6 +81,7 @@ describe('read_rules', () => { }); try { await readRules({ + isRuleRegistryEnabled, rulesClient, id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', ruleId: undefined, @@ -86,23 +93,25 @@ describe('read_rules', () => { test('should return the output from rulesClient if id is undefined but ruleId is set', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, id: undefined, ruleId: 'rule-1', }); - expect(rule).toEqual(getAlertMock(getQueryRuleParams())); + expect(rule).toEqual(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); }); test('should return null if the output from rulesClient with ruleId set is empty', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); + rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); rulesClient.find.mockResolvedValue({ data: [], page: 0, perPage: 1, total: 0 }); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, id: undefined, ruleId: 'rule-1', @@ -112,23 +121,25 @@ describe('read_rules', () => { test('should return the output from rulesClient if id is null but ruleId is set', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, id: undefined, ruleId: 'rule-1', }); - expect(rule).toEqual(getAlertMock(getQueryRuleParams())); + expect(rule).toEqual(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); }); test('should return null if id and ruleId are undefined', async () => { const rulesClient = rulesClientMock.create(); - rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); const rule = await readRules({ + isRuleRegistryEnabled, rulesClient, id: undefined, ruleId: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts index 141977f2474e0..9578e3d4cb6d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts @@ -20,6 +20,7 @@ import { isAlertType, ReadRuleOptions } from './types'; * a filter query against the tags using `alert.attributes.tags: "__internal:${ruleId}"]` */ export const readRules = async ({ + isRuleRegistryEnabled, rulesClient, id, ruleId, @@ -27,7 +28,7 @@ export const readRules = async ({ if (id != null) { try { const rule = await rulesClient.get({ id }); - if (isAlertType(rule)) { + if (isAlertType(isRuleRegistryEnabled, rule)) { return rule; } else { return null; @@ -42,6 +43,7 @@ export const readRules = async ({ } } else if (ruleId != null) { const ruleFromFind = await findRules({ + isRuleRegistryEnabled, rulesClient, filter: `alert.attributes.tags: "${INTERNAL_RULE_ID_KEY}:${ruleId}"`, page: 1, @@ -50,7 +52,10 @@ export const readRules = async ({ sortField: undefined, sortOrder: undefined, }); - if (ruleFromFind.data.length === 0 || !isAlertType(ruleFromFind.data[0])) { + if ( + ruleFromFind.data.length === 0 || + !isAlertType(isRuleRegistryEnabled, ruleFromFind.data[0]) + ) { return null; } else { return ruleFromFind.data[0]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index 235217761c8b1..c27caaed5449e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -101,6 +101,7 @@ import { BuildingBlockTypeOrUndefined, RuleNameOverrideOrUndefined, EventCategoryOverrideOrUndefined, + NamespaceOrUndefined, } from '../../../../common/detection_engine/schemas/common/schemas'; import { RulesClient, PartialAlert } from '../../../../../alerting/server'; @@ -109,6 +110,7 @@ import { SIGNALS_ID } from '../../../../common/constants'; import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { IRuleExecutionLogClient } from '../rule_execution_log/types'; +import { ruleTypeMappings } from '../signals/utils'; export type RuleAlertType = Alert; @@ -192,15 +194,20 @@ export interface Clients { } export const isAlertTypes = ( + isRuleRegistryEnabled: boolean, partialAlert: Array> ): partialAlert is RuleAlertType[] => { - return partialAlert.every((rule) => isAlertType(rule)); + return partialAlert.every((rule) => isAlertType(isRuleRegistryEnabled, rule)); }; export const isAlertType = ( + isRuleRegistryEnabled: boolean, partialAlert: PartialAlert ): partialAlert is RuleAlertType => { - return partialAlert.alertTypeId === SIGNALS_ID; + const ruleTypeValues = (Object.values(ruleTypeMappings) as unknown) as string[]; + return isRuleRegistryEnabled + ? ruleTypeValues.includes(partialAlert.alertTypeId as string) + : partialAlert.alertTypeId === SIGNALS_ID; }; export const isRuleStatusSavedObjectType = ( @@ -266,9 +273,12 @@ export interface CreateRulesOptions { version: Version; exceptionsList: ListArray; actions: RuleAlertAction[]; + isRuleRegistryEnabled: boolean; + namespace?: NamespaceOrUndefined; } export interface UpdateRulesOptions { + isRuleRegistryEnabled: boolean; spaceId: string; ruleStatusClient: IRuleExecutionLogClient; rulesClient: RulesClient; @@ -327,9 +337,11 @@ export interface PatchRulesOptions { exceptionsList: ListArrayOrUndefined; actions: RuleAlertAction[] | undefined; rule: SanitizedAlert | null; + namespace?: NamespaceOrUndefined; } export interface ReadRuleOptions { + isRuleRegistryEnabled: boolean; rulesClient: RulesClient; id: IdOrUndefined; ruleId: RuleIdOrUndefined; @@ -343,6 +355,7 @@ export interface DeleteRuleOptions { } export interface FindRuleOptions { + isRuleRegistryEnabled: boolean; rulesClient: RulesClient; perPage: PerPageOrUndefined; page: PageOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts index 5cc7f068aa06d..7c9f0c9ec67a3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts @@ -13,7 +13,10 @@ import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/dete import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client'; jest.mock('./patch_rules'); -describe('updatePrepackagedRules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('updatePrepackagedRules - %s', (_, isRuleRegistryEnabled) => { let rulesClient: ReturnType; let ruleStatusClient: ReturnType; @@ -33,14 +36,15 @@ describe('updatePrepackagedRules', () => { ]; const outputIndex = 'outputIndex'; const prepackagedRule = getAddPrepackagedRulesSchemaDecodedMock(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); await updatePrepackagedRules( rulesClient, 'default', ruleStatusClient, [{ ...prepackagedRule, actions }], - outputIndex + outputIndex, + isRuleRegistryEnabled ); expect(patchRules).toHaveBeenCalledWith( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index fcfab2fda1a8b..d9c2ecd1b5732 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -54,7 +54,8 @@ export const updatePrepackagedRules = async ( spaceId: string, ruleStatusClient: IRuleExecutionLogClient, rules: AddPrepackagedRulesSchemaDecoded[], - outputIndex: string + outputIndex: string, + isRuleRegistryEnabled: boolean ): Promise => { const ruleChunks = chunk(UPDATE_CHUNK_SIZE, rules); for (const ruleChunk of ruleChunks) { @@ -63,7 +64,8 @@ export const updatePrepackagedRules = async ( spaceId, ruleStatusClient, ruleChunk, - outputIndex + outputIndex, + isRuleRegistryEnabled ); await Promise.all(rulePromises); } @@ -83,7 +85,8 @@ export const createPromises = ( spaceId: string, ruleStatusClient: IRuleExecutionLogClient, rules: AddPrepackagedRulesSchemaDecoded[], - outputIndex: string + outputIndex: string, + isRuleRegistryEnabled: boolean ): Array | null>> => { return rules.map(async (rule) => { const { @@ -133,7 +136,12 @@ export const createPromises = ( exceptions_list: exceptionsList, } = rule; - const existingRule = await readRules({ rulesClient, ruleId, id: undefined }); + const existingRule = await readRules({ + isRuleRegistryEnabled, + rulesClient, + ruleId, + id: undefined, + }); // TODO: Fix these either with an is conversion or by better typing them within io-ts const filters: PartialFilter[] | undefined = filtersObject as PartialFilter[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts index df9431e00a67c..58d6cf1fd5e6b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts @@ -11,20 +11,21 @@ import { getUpdateRulesSchemaMock, } from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client'; -import { UpdateRulesOptions } from './types'; -export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({ +export const getUpdateRulesOptionsMock = (isRuleRegistryEnabled: boolean) => ({ spaceId: 'default', rulesClient: rulesClientMock.create(), ruleStatusClient: ruleExecutionLogClientMock.create(), defaultOutputIndex: '.siem-signals-default', ruleUpdate: getUpdateRulesSchemaMock(), + isRuleRegistryEnabled, }); -export const getUpdateMlRulesOptionsMock = (): UpdateRulesOptions => ({ +export const getUpdateMlRulesOptionsMock = (isRuleRegistryEnabled: boolean) => ({ spaceId: 'default', rulesClient: rulesClientMock.create(), ruleStatusClient: ruleExecutionLogClientMock.create(), defaultOutputIndex: '.siem-signals-default', ruleUpdate: getUpdateMachineLearningSchemaMock(), + isRuleRegistryEnabled, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts index e46b4fad63a92..1ad5cd7ae934c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts @@ -11,15 +11,18 @@ import { getUpdateRulesOptionsMock, getUpdateMlRulesOptionsMock } from './update import { RulesClientMock } from '../../../../../alerting/server/rules_client.mock'; import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; -describe('updateRules', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('updateRules - %s', (_, isRuleRegistryEnabled) => { it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => { - const rulesOptionsMock = getUpdateRulesOptionsMock(); + const rulesOptionsMock = getUpdateRulesOptionsMock(isRuleRegistryEnabled); rulesOptionsMock.ruleUpdate.enabled = false; ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await updateRules(rulesOptionsMock); @@ -32,15 +35,15 @@ describe('updateRules', () => { }); it('should call rulesClient.enable if the rule was disabled and enabled is true', async () => { - const rulesOptionsMock = getUpdateRulesOptionsMock(); + const rulesOptionsMock = getUpdateRulesOptionsMock(isRuleRegistryEnabled); rulesOptionsMock.ruleUpdate.enabled = true; ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue({ - ...getAlertMock(getQueryRuleParams()), + ...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), enabled: false, }); ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getQueryRuleParams()) + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await updateRules(rulesOptionsMock); @@ -53,15 +56,15 @@ describe('updateRules', () => { }); it('calls the rulesClient with params', async () => { - const rulesOptionsMock = getUpdateMlRulesOptionsMock(); + const rulesOptionsMock = getUpdateMlRulesOptionsMock(isRuleRegistryEnabled); rulesOptionsMock.ruleUpdate.enabled = true; ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( - getAlertMock(getMlRuleParams()) + getAlertMock(isRuleRegistryEnabled, getMlRuleParams()) ); ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue( - getAlertMock(getMlRuleParams()) + getAlertMock(isRuleRegistryEnabled, getMlRuleParams()) ); await updateRules(rulesOptionsMock); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index a3e0ba31f0c3c..f4060f7f831a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -14,11 +14,12 @@ import { readRules } from './read_rules'; import { UpdateRulesOptions } from './types'; import { addTags } from './add_tags'; import { typeSpecificSnakeToCamel } from '../schemas/rule_converters'; -import { InternalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; +import { RuleParams } from '../schemas/rule_schemas'; import { enableRule } from './enable_rule'; import { maybeMute, transformToAlertThrottle, transformToNotifyWhen } from './utils'; export const updateRules = async ({ + isRuleRegistryEnabled, spaceId, rulesClient, ruleStatusClient, @@ -26,6 +27,7 @@ export const updateRules = async ({ ruleUpdate, }: UpdateRulesOptions): Promise | null> => { const existingRule = await readRules({ + isRuleRegistryEnabled, rulesClient, ruleId: ruleUpdate.rule_id, id: ruleUpdate.id, @@ -36,7 +38,7 @@ export const updateRules = async ({ const typeSpecificParams = typeSpecificSnakeToCamel(ruleUpdate); const enabled = ruleUpdate.enabled ?? true; - const newInternalRule: InternalRuleUpdate = { + const newInternalRule = { name: ruleUpdate.name, tags: addTags(ruleUpdate.tags ?? [], existingRule.params.ruleId, existingRule.params.immutable), params: { @@ -63,6 +65,7 @@ export const updateRules = async ({ timestampOverride: ruleUpdate.timestamp_override, to: ruleUpdate.to ?? 'now', references: ruleUpdate.references ?? [], + namespace: ruleUpdate.namespace, note: ruleUpdate.note, // Always use the version from the request if specified. If it isn't specified, leave immutable rules alone and // increment the version of mutable rules by 1. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index 602e422772711..95e8552c4b14b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -81,6 +81,7 @@ describe('utils', () => { type: undefined, references: undefined, version: undefined, + namespace: undefined, note: undefined, anomalyThreshold: undefined, machineLearningJobId: undefined, @@ -131,6 +132,7 @@ describe('utils', () => { type: undefined, references: undefined, version: undefined, + namespace: undefined, note: undefined, anomalyThreshold: undefined, machineLearningJobId: undefined, @@ -181,6 +183,7 @@ describe('utils', () => { type: undefined, references: undefined, version: undefined, + namespace: undefined, note: undefined, anomalyThreshold: undefined, machineLearningJobId: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index d9d5151a64c46..3fdd97b7d933f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -52,6 +52,7 @@ import { RuleNameOverrideOrUndefined, TimestampOverrideOrUndefined, EventCategoryOverrideOrUndefined, + NamespaceOrUndefined, } from '../../../../common/detection_engine/schemas/common/schemas'; import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; @@ -118,6 +119,7 @@ export interface UpdateProperties { version: VersionOrUndefined; exceptionsList: ListArrayOrUndefined; anomalyThreshold: AnomalyThresholdOrUndefined; + namespace: NamespaceOrUndefined; } export const calculateVersion = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index 8a67636c6649d..5214be513a0e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -36,6 +36,7 @@ import { transformToAlertThrottle, transformToNotifyWhen, } from '../rules/utils'; +import { ruleTypeMappings } from '../signals/utils'; // These functions provide conversions from the request API schema to the internal rule schema and from the internal rule schema // to the response API schema. This provides static type-check assurances that the internal schema is in sync with the API schema for @@ -121,14 +122,15 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif export const convertCreateAPIToInternalSchema = ( input: CreateRulesSchema, - siemClient: AppClient + siemClient: AppClient, + isRuleRegistryEnabled: boolean ): InternalRuleCreate => { const typeSpecificParams = typeSpecificSnakeToCamel(input); const newRuleId = input.rule_id ?? uuid.v4(); return { name: input.name, tags: addTags(input.tags ?? [], newRuleId, false), - alertTypeId: SIGNALS_ID, + alertTypeId: isRuleRegistryEnabled ? ruleTypeMappings[input.type] : SIGNALS_ID, consumer: SERVER_APP_ID, params: { author: input.author ?? [], @@ -153,6 +155,7 @@ export const convertCreateAPIToInternalSchema = ( timestampOverride: input.timestamp_override, to: input.to ?? 'now', references: input.references ?? [], + namespace: input.namespace, note: input.note, version: input.version ?? 1, exceptionsList: input.exceptions_list ?? [], @@ -249,6 +252,7 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => { risk_score: params.riskScore, severity: params.severity, building_block_type: params.buildingBlockType, + namespace: params.namespace, note: params.note, license: params.license, output_index: params.outputIndex, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts index 846a4e26410a3..506f40af2ee79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts @@ -36,6 +36,7 @@ const getBaseRuleParams = (): BaseRuleParams => { riskScoreMapping: [], ruleNameOverride: undefined, maxSignals: 10000, + namespace: undefined, note: '# Investigative notes', timelineId: 'some-timeline-id', timelineTitle: 'some-timeline-title', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts index c414ecc8655a3..e9215084614c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts @@ -32,6 +32,7 @@ import { buildingBlockTypeOrUndefined, description, enabled, + namespaceOrUndefined, noteOrUndefined, false_positives, rule_id, @@ -62,7 +63,13 @@ import { updated_at, } from '../../../../common/detection_engine/schemas/common/schemas'; -import { SIGNALS_ID, SERVER_APP_ID } from '../../../../common/constants'; +import { + SIGNALS_ID, + SERVER_APP_ID, + INDICATOR_RULE_TYPE_ID, + ML_RULE_TYPE_ID, + QUERY_RULE_TYPE_ID, +} from '../../../../common/constants'; const nonEqlLanguages = t.keyof({ kuery: null, lucene: null }); export const baseRuleParams = t.exact( @@ -70,6 +77,7 @@ export const baseRuleParams = t.exact( author, buildingBlockType: buildingBlockTypeOrUndefined, description, + namespace: namespaceOrUndefined, note: noteOrUndefined, falsePositives: false_positives, from, @@ -196,10 +204,21 @@ export const notifyWhen = t.union([ t.null, ]); +export const allRuleTypes = t.union([ + t.literal(SIGNALS_ID), + // t.literal(EQL_RULE_TYPE_ID), + t.literal(ML_RULE_TYPE_ID), + t.literal(QUERY_RULE_TYPE_ID), + // t.literal(SAVED_QUERY_RULE_TYPE_ID), + t.literal(INDICATOR_RULE_TYPE_ID), + // t.literal(THRESHOLD_RULE_TYPE_ID), +]); +export type AllRuleTypes = t.TypeOf; + export const internalRuleCreate = t.type({ name, tags, - alertTypeId: t.literal(SIGNALS_ID), + alertTypeId: allRuleTypes, consumer: t.literal(SERVER_APP_ID), schedule: t.type({ interval: t.string, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index 8037a9a201510..52b0799f5fe33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -182,7 +182,7 @@ export const searchAfterAndBulkCreate = async ({ break; } } catch (exc: unknown) { - logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`)); + logger.error(buildRuleMessage(`[-] search_after_bulk_create threw an error ${exc}`)); return mergeReturns([ toReturn, createSearchAfterReturnType({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 9af8680ec726a..2696d6981083e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -177,7 +177,7 @@ describe('signal_rule_alert_type', () => { alertServices.scopedClusterClient.asCurrentUser.fieldCaps.mockResolvedValue( value as ApiResponse ); - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(false, getQueryRuleParams()); alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', @@ -245,7 +245,7 @@ describe('signal_rule_alert_type', () => { }, application: {}, }); - const newRuleAlert = getAlertMock(getQueryRuleParams()); + const newRuleAlert = getAlertMock(false, getQueryRuleParams()); newRuleAlert.params.index = ['some*', 'myfa*', 'anotherindex*']; payload = getPayload(newRuleAlert, alertServices) as jest.Mocked; @@ -274,7 +274,7 @@ describe('signal_rule_alert_type', () => { }, application: {}, }); - const newRuleAlert = getAlertMock(getQueryRuleParams()); + const newRuleAlert = getAlertMock(false, getQueryRuleParams()); newRuleAlert.params.index = ['some*', 'myfa*', 'anotherindex*']; payload = getPayload(newRuleAlert, alertServices) as jest.Mocked; @@ -309,7 +309,7 @@ describe('signal_rule_alert_type', () => { }); it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(false, getQueryRuleParams()); ruleAlert.actions = [ { actionTypeId: '.slack', @@ -333,7 +333,7 @@ describe('signal_rule_alert_type', () => { }); it('should resolve results_link when meta is an empty object to use "/app/security"', async () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(false, getQueryRuleParams()); ruleAlert.params.meta = {}; ruleAlert.actions = [ { @@ -366,7 +366,7 @@ describe('signal_rule_alert_type', () => { }); it('should resolve results_link when meta is undefined use "/app/security"', async () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(false, getQueryRuleParams()); delete ruleAlert.params.meta; ruleAlert.actions = [ { @@ -399,7 +399,7 @@ describe('signal_rule_alert_type', () => { }); it('should resolve results_link with a custom link', async () => { - const ruleAlert = getAlertMock(getQueryRuleParams()); + const ruleAlert = getAlertMock(false, getQueryRuleParams()); ruleAlert.params.meta = { kibana_siem_app_url: 'http://localhost' }; ruleAlert.actions = [ { @@ -433,7 +433,7 @@ describe('signal_rule_alert_type', () => { describe('ML rule', () => { it('should not call checkPrivileges if ML rule', async () => { - const ruleAlert = getAlertMock(getMlRuleParams()); + const ruleAlert = getAlertMock(false, getMlRuleParams()); alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index dc929ed0fdeb1..aee265a2cfdfe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -62,6 +62,12 @@ import { import { WrappedRACAlert } from '../rule_types/types'; import { SearchTypes } from '../../../../common/detection_engine/types'; import { IRuleExecutionLogClient } from '../rule_execution_log/types'; +import { + INDICATOR_RULE_TYPE_ID, + ML_RULE_TYPE_ID, + QUERY_RULE_TYPE_ID, + SIGNALS_ID, +} from '../../../../common/constants'; interface SortExceptionsReturn { exceptionsWithValueLists: ExceptionListItemSchema[]; @@ -999,3 +1005,15 @@ export const getField = (event: SimpleHit, field: string) return get(event._source, field) as T; } }; + +/** + * Maps legacy rule types to RAC rule type IDs. + */ +export const ruleTypeMappings = { + eql: SIGNALS_ID, + machine_learning: ML_RULE_TYPE_ID, + query: QUERY_RULE_TYPE_ID, + saved_query: SIGNALS_ID, + threat_match: INDICATOR_RULE_TYPE_ID, + threshold: SIGNALS_ID, +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts index 1b7bf048646ed..505f8958ca44d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts @@ -11,19 +11,22 @@ import { INTERNAL_RULE_ID_KEY, INTERNAL_IDENTIFIER } from '../../../../common/co import { readRawTags, readTags, convertTagsToSet, convertToTags, isTags } from './read_tags'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -describe('read_tags', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('read_tags - %s', (_, isRuleRegistryEnabled) => { afterEach(() => { jest.resetAllMocks(); }); describe('readRawTags', () => { test('it should return the intersection of tags to where none are repeating', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 2', 'tag 3']; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = ['tag 1', 'tag 2', 'tag 3', 'tag 4']; @@ -31,17 +34,17 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - const tags = await readRawTags({ rulesClient }); + const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']); }); test('it should return the intersection of tags to where some are repeating values', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3']; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4']; @@ -49,17 +52,17 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - const tags = await readRawTags({ rulesClient }); + const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']); }); test('it should work with no tags defined between two results', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = []; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = []; @@ -67,12 +70,12 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - const tags = await readRawTags({ rulesClient }); + const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual([]); }); test('it should work with a single tag which has repeating values in it', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 1', 'tag 1', 'tag 2']; @@ -80,12 +83,12 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - const tags = await readRawTags({ rulesClient }); + const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1', 'tag 2']); }); test('it should work with a single tag which has empty tags', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = []; @@ -93,19 +96,19 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - const tags = await readRawTags({ rulesClient }); + const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual([]); }); }); describe('readTags', () => { test('it should return the intersection of tags to where none are repeating', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 2', 'tag 3']; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = ['tag 1', 'tag 2', 'tag 3', 'tag 4']; @@ -113,17 +116,17 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - const tags = await readTags({ rulesClient }); + const tags = await readTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']); }); test('it should return the intersection of tags to where some are repeating values', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3']; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4']; @@ -131,17 +134,17 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - const tags = await readTags({ rulesClient }); + const tags = await readTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']); }); test('it should work with no tags defined between two results', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = []; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = []; @@ -149,12 +152,12 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - const tags = await readTags({ rulesClient }); + const tags = await readTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual([]); }); test('it should work with a single tag which has repeating values in it', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 1', 'tag 1', 'tag 2']; @@ -162,12 +165,12 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - const tags = await readTags({ rulesClient }); + const tags = await readTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1', 'tag 2']); }); test('it should work with a single tag which has empty tags', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = []; @@ -175,12 +178,12 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - const tags = await readTags({ rulesClient }); + const tags = await readTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual([]); }); test('it should filter out any __internal tags for things such as alert_id', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = [ @@ -192,12 +195,12 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - const tags = await readTags({ rulesClient }); + const tags = await readTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1']); }); test('it should filter out any __internal tags with two different results', async () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = [ @@ -210,7 +213,7 @@ describe('read_tags', () => { 'tag 5', ]; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = [ @@ -225,19 +228,19 @@ describe('read_tags', () => { const rulesClient = rulesClientMock.create(); rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - const tags = await readTags({ rulesClient }); + const tags = await readTags({ isRuleRegistryEnabled, rulesClient }); expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4', 'tag 5']); }); }); describe('convertTagsToSet', () => { test('it should convert the intersection of two tag systems without duplicates', () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3']; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4']; @@ -255,12 +258,12 @@ describe('read_tags', () => { describe('convertToTags', () => { test('it should convert the two tag systems together with duplicates', () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3']; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result2.params.ruleId = 'rule-2'; result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4']; @@ -281,18 +284,18 @@ describe('read_tags', () => { }); test('it should filter out anything that is not a tag', () => { - const result1 = getAlertMock(getQueryRuleParams()); + const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result1.params.ruleId = 'rule-1'; result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3']; - const result2 = getAlertMock(getQueryRuleParams()); + const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result2.id = '99979e67-19a7-455f-b452-8eded6135716'; result2.params.ruleId = 'rule-2'; // @ts-expect-error delete result2.tags; - const result3 = getAlertMock(getQueryRuleParams()); + const result3 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); result3.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; result3.params.ruleId = 'rule-2'; result3.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4']; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts index 2314a8a49f567..183ac6f777963 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts @@ -40,22 +40,27 @@ export const convertTagsToSet = (tagObjects: object[]): Set => { // then this should be replaced with a an aggregation call. // Ref: https://www.elastic.co/guide/en/kibana/master/saved-objects-api.html export const readTags = async ({ + isRuleRegistryEnabled, rulesClient, }: { + isRuleRegistryEnabled: boolean; rulesClient: RulesClient; }): Promise => { - const tags = await readRawTags({ rulesClient }); + const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient }); return tags.filter((tag) => !tag.startsWith(INTERNAL_IDENTIFIER)); }; export const readRawTags = async ({ + isRuleRegistryEnabled, rulesClient, }: { + isRuleRegistryEnabled: boolean; rulesClient: RulesClient; perPage?: number; }): Promise => { // Get just one record so we can get the total count const firstTags = await findRules({ + isRuleRegistryEnabled, rulesClient, fields: ['tags'], perPage: 1, @@ -66,6 +71,7 @@ export const readRawTags = async ({ }); // Get all the rules to aggregate over all the tags of the rules const rules = await findRules({ + isRuleRegistryEnabled, rulesClient, fields: ['tags'], perPage: firstTags.total, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts index 938772ce72367..18c3baccf9aa0 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts @@ -111,6 +111,8 @@ export const allowlistEventFields: AllowlistFields = { events: allowlistBaseEventFields, // behavioral protection re-nests some field sets under Events.* (>=7.15) Events: allowlistBaseEventFields, + // behavioral protection response data under Response.* (>=7.15) + Responses: true, rule: { id: true, name: true, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts index a4f8033f1160d..d04d0ab49afe9 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts @@ -80,6 +80,7 @@ describe('TelemetryEventsSender', () => { executable: null, // null fields are never allowlisted working_directory: '/some/usr/dir', }, + Responses: '{ "result": 0 }', // >= 7.15 Target: { process: { name: 'bar.exe', @@ -89,6 +90,9 @@ describe('TelemetryEventsSender', () => { }, }, }, + threat: { + ignored_object: true, // this field is not allowlisted + }, }, ]; @@ -136,6 +140,7 @@ describe('TelemetryEventsSender', () => { name: 'foo.exe', working_directory: '/some/usr/dir', }, + Responses: '{ "result": 0 }', Target: { process: { name: 'bar.exe', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts index f30f80a4cf14c..c384c1df80ad1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { join, resolve } from 'path'; - import { createPromiseFromStreams } from '@kbn/utils'; import { SecurityPluginSetup } from '../../../../../../../security/server'; @@ -21,17 +19,20 @@ import { getFindResultWithSingleHit, } from '../../../../detection_engine/routes/__mocks__/request_responses'; -import * as lib from './helpers'; -import { importTimelines } from '../../timelines/import_timelines'; +import * as helpers from './helpers'; +import { importTimelines } from '../../timelines/import_timelines/helpers'; import { buildFrameworkRequest } from '../../../utils/common'; import { ImportTimelineResultSchema } from '../../../../../../common/types/timeline'; -jest.mock('../../timelines/import_timelines'); +jest.mock('../../timelines/import_timelines/helpers'); -describe('installPrepackagedTimelines', () => { +describe.each([ + ['Legacy', false], + ['RAC', true], +])('installPrepackagedTimelines - %s', (_, isRuleRegistryEnabled) => { let securitySetup: SecurityPluginSetup; let frameworkRequest: FrameworkRequest; - const spyInstallPrepackagedTimelines = jest.spyOn(lib, 'installPrepackagedTimelines'); + const spyInstallPrepackagedTimelines = jest.spyOn(helpers, 'installPrepackagedTimelines'); const { clients, context } = requestContextMock.createTools(); const config = createMockConfig(); @@ -46,11 +47,11 @@ describe('installPrepackagedTimelines', () => { authz: {}, } as unknown) as SecurityPluginSetup; - clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); jest.doMock('./helpers', () => { return { - ...lib, + ...helpers, installPrepackagedTimelines: spyInstallPrepackagedTimelines, }; }); @@ -60,7 +61,7 @@ describe('installPrepackagedTimelines', () => { }); afterEach(() => { - spyInstallPrepackagedTimelines.mockClear(); + jest.clearAllMocks(); }); afterAll(() => { @@ -68,10 +69,10 @@ describe('installPrepackagedTimelines', () => { }); test('should call importTimelines', async () => { - await lib.installPrepackagedTimelines( + await helpers.installPrepackagedTimelines( config.maxTimelineImportExportSize, frameworkRequest, - true, + false, mockFilePath, mockFileName ); @@ -80,14 +81,12 @@ describe('installPrepackagedTimelines', () => { }); test('should call importTimelines with Readables', async () => { - const dir = resolve(join(__dirname, mockFilePath)); - const file = mockFileName; - await lib.installPrepackagedTimelines( + await helpers.installPrepackagedTimelines( config.maxTimelineImportExportSize, frameworkRequest, true, - dir, - file + mockFilePath, + mockFileName ); const args = await createPromiseFromStreams([(importTimelines as jest.Mock).mock.calls[0][0]]); const expected = JSON.stringify({ @@ -194,14 +193,12 @@ describe('installPrepackagedTimelines', () => { }); test('should call importTimelines with maxTimelineImportExportSize', async () => { - const dir = resolve(join(__dirname, mockFilePath)); - const file = mockFileName; - await lib.installPrepackagedTimelines( + await helpers.installPrepackagedTimelines( config.maxTimelineImportExportSize, frameworkRequest, true, - dir, - file + mockFilePath, + mockFileName ); expect((importTimelines as jest.Mock).mock.calls[0][1]).toEqual( @@ -210,14 +207,12 @@ describe('installPrepackagedTimelines', () => { }); test('should call importTimelines with frameworkRequest', async () => { - const dir = resolve(join(__dirname, mockFilePath)); - const file = mockFileName; - await lib.installPrepackagedTimelines( + await helpers.installPrepackagedTimelines( config.maxTimelineImportExportSize, frameworkRequest, true, - dir, - file + mockFilePath, + mockFileName ); expect(JSON.stringify((importTimelines as jest.Mock).mock.calls[0][2])).toEqual( @@ -226,21 +221,19 @@ describe('installPrepackagedTimelines', () => { }); test('should call importTimelines with immutable', async () => { - const dir = resolve(join(__dirname, mockFilePath)); - const file = mockFileName; - await lib.installPrepackagedTimelines( + await helpers.installPrepackagedTimelines( config.maxTimelineImportExportSize, frameworkRequest, true, - dir, - file + mockFilePath, + mockFileName ); expect((importTimelines as jest.Mock).mock.calls[0][3]).toEqual(true); }); test('should handle errors from getReadables', async () => { - const result = await lib.installPrepackagedTimelines( + const result = await helpers.installPrepackagedTimelines( config.maxTimelineImportExportSize, frameworkRequest, true, diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 4e4d0be5a7411..e1af47de8f152 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -63,10 +63,10 @@ import { SERVER_APP_ID, SIGNALS_ID, NOTIFICATIONS_ID, - QUERY_ALERT_TYPE_ID, + QUERY_RULE_TYPE_ID, DEFAULT_SPACE_ID, - INDICATOR_ALERT_TYPE_ID, - ML_ALERT_TYPE_ID, + INDICATOR_RULE_TYPE_ID, + ML_RULE_TYPE_ID, } from '../common/constants'; import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency'; @@ -272,7 +272,7 @@ export class Plugin implements IPlugin { // Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules // All REST rule creation, deletion, updating, etc...... - createRulesRoute(router, ml, ruleDataClient); - readRulesRoute(router, ruleDataClient); - updateRulesRoute(router, ml, ruleDataClient); - patchRulesRoute(router, ml, ruleDataClient); - deleteRulesRoute(router, ruleDataClient); - findRulesRoute(router, ruleDataClient); - - // TODO: pass ruleDataClient to all relevant routes - - addPrepackedRulesRoute(router, config, security); - getPrepackagedRulesStatusRoute(router, config, security); - createRulesBulkRoute(router, ml); - updateRulesBulkRoute(router, ml); - patchRulesBulkRoute(router, ml); - deleteRulesBulkRoute(router); - performBulkActionRoute(router, ml); + createRulesRoute(router, ml, isRuleRegistryEnabled); + readRulesRoute(router, isRuleRegistryEnabled); + updateRulesRoute(router, ml, isRuleRegistryEnabled); + patchRulesRoute(router, ml, isRuleRegistryEnabled); + deleteRulesRoute(router, isRuleRegistryEnabled); + findRulesRoute(router, isRuleRegistryEnabled); + + // TODO: pass isRuleRegistryEnabled to all relevant routes + + addPrepackedRulesRoute(router, config, security, isRuleRegistryEnabled); + getPrepackagedRulesStatusRoute(router, config, security, isRuleRegistryEnabled); + createRulesBulkRoute(router, ml, isRuleRegistryEnabled); + updateRulesBulkRoute(router, ml, isRuleRegistryEnabled); + patchRulesBulkRoute(router, ml, isRuleRegistryEnabled); + deleteRulesBulkRoute(router, isRuleRegistryEnabled); + performBulkActionRoute(router, ml, isRuleRegistryEnabled); createTimelinesRoute(router, config, security); patchTimelinesRoute(router, config, security); - importRulesRoute(router, config, ml); - exportRulesRoute(router, config); + importRulesRoute(router, config, ml, isRuleRegistryEnabled); + exportRulesRoute(router, config, isRuleRegistryEnabled); importTimelinesRoute(router, config, security); exportTimelinesRoute(router, config, security); @@ -123,7 +123,7 @@ export const initRoutes = ( deleteIndexRoute(router); // Detection Engine tags routes that have the REST endpoints of /api/detection_engine/tags - readTagsRoute(router); + readTagsRoute(router, isRuleRegistryEnabled); // Privileges API to get the generic user privileges readPrivilegesRoute(router, hasEncryptionKey); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/index.ts index 2fc2c42be617f..b97b8ed4f6549 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/index.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { MatrixHistogramTypeToAggName } from '../../../../../../common'; import { buildAlertsHistogramQuery } from './query.alerts_histogram.dsl'; export const alertsMatrixHistogramConfig = { buildDsl: buildAlertsHistogramQuery, - aggName: 'aggregations.alertsGroup.buckets', + aggName: MatrixHistogramTypeToAggName.alerts, parseKey: 'alerts.buckets', }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/index.ts index 396d1e8bd004b..ec307173ec20c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/index.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { MatrixHistogramTypeToAggName } from '../../../../../../common'; import { buildAnomaliesHistogramQuery } from './query.anomalies_histogram.dsl'; export const anomaliesMatrixHistogramConfig = { buildDsl: buildAnomaliesHistogramQuery, - aggName: 'aggregations.anomalyActionGroup.buckets', + aggName: MatrixHistogramTypeToAggName.anomalies, parseKey: 'anomalies.buckets', }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/index.ts index c147b32be2c00..17f7d78167232 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/index.ts @@ -5,19 +5,20 @@ * 2.0. */ +import { MatrixHistogramTypeToAggName } from '../../../../../../common'; import { getEntitiesParser } from '../helpers'; import { buildAuthenticationsHistogramQuery } from './query.authentications_histogram.dsl'; import { buildAuthenticationsHistogramQueryEntities } from './query.authentications_histogram_entities.dsl'; export const authenticationsMatrixHistogramConfig = { buildDsl: buildAuthenticationsHistogramQuery, - aggName: 'aggregations.eventActionGroup.buckets', + aggName: MatrixHistogramTypeToAggName.authentications, parseKey: 'events.buckets', }; export const authenticationsMatrixHistogramEntitiesConfig = { buildDsl: buildAuthenticationsHistogramQueryEntities, - aggName: 'aggregations.events.buckets', + aggName: MatrixHistogramTypeToAggName.authenticationsEntities, parseKey: 'events.buckets', parser: getEntitiesParser, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/index.ts index 26f07d881618a..643b3f657ef0c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/index.ts @@ -7,10 +7,11 @@ import { buildDnsHistogramQuery } from './query.dns_histogram.dsl'; import { getDnsParsedData } from './helpers'; +import { MatrixHistogramTypeToAggName } from '../../../../../../common'; export const dnsMatrixHistogramConfig = { buildDsl: buildDnsHistogramQuery, - aggName: 'aggregations.dns_name_query_count.buckets', + aggName: MatrixHistogramTypeToAggName.dns, parseKey: 'dns_question_name.buckets', parser: getDnsParsedData, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/index.ts index 0edfbe7df001b..a280950c37cd6 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/index.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { MatrixHistogramTypeToAggName } from '../../../../../../common'; import { buildEventsHistogramQuery } from './query.events_histogram.dsl'; export const eventsMatrixHistogramConfig = { buildDsl: buildEventsHistogramQuery, - aggName: 'aggregations.eventActionGroup.buckets', + aggName: MatrixHistogramTypeToAggName.events, parseKey: 'events.buckets', }; diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index b5c0972031a8f..06be065c28a71 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -138,7 +138,6 @@ export interface UsageData extends UsageStats { graph?: number; uptime?: number; savedObjectsManagement?: number; - timelion?: number; dev_tools?: number; advancedSettings?: number; infrastructure?: number; @@ -269,12 +268,6 @@ export function getSpacesUsageCollector( description: 'The number of spaces which have this feature disabled.', }, }, - timelion: { - type: 'long', - _meta: { - description: 'The number of spaces which have this feature disabled.', - }, - }, dev_tools: { type: 'long', _meta: { diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 642189f716539..5cf39b89376d4 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -6655,12 +6655,6 @@ "description": "The number of spaces which have this feature disabled." } }, - "timelion": { - "type": "long", - "_meta": { - "description": "The number of spaces which have this feature disabled." - } - }, "dev_tools": { "type": "long", "_meta": { diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts index 47cd1ed92d661..5371d7004a864 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts @@ -7,9 +7,6 @@ import { useState, useLayoutEffect } from 'react'; -// That could be different from security and observability. Get it as parameter? -const INITIAL_DATA_GRID_HEIGHT = 967; - // It will recalculate DataGrid height after this time interval. const TIME_INTERVAL = 50; @@ -18,8 +15,17 @@ const TIME_INTERVAL = 50; * 3 (three) is a number, numeral and digit. It is the natural number following 2 and preceding 4, and is the smallest * odd prime number and the only prime preceding a square number. It has religious or cultural significance in many societies. */ + const MAGIC_GAP = 3; +// Hard coded height for every page size +const DATA_GRID_HEIGHT_BY_PAGE_SIZE: { [key: number]: number } = { + 10: 457, + 25: 967, + 50: 1817, + 100: 3517, +}; + /** * HUGE HACK!!! * DataGrtid height isn't properly calculated when the grid has horizontal scroll. @@ -30,13 +36,15 @@ const MAGIC_GAP = 3; * Please delete me and allow DataGrid to calculate its height when the bug is fixed. */ export const useDataGridHeightHack = (pageSize: number, rowCount: number) => { - const [height, setHeight] = useState(INITIAL_DATA_GRID_HEIGHT); + const [height, setHeight] = useState(DATA_GRID_HEIGHT_BY_PAGE_SIZE[pageSize]); useLayoutEffect(() => { setTimeout(() => { const gridVirtualized = document.querySelector('#body-data-grid .euiDataGrid__virtualized'); - if ( + if (rowCount === pageSize) { + setHeight(DATA_GRID_HEIGHT_BY_PAGE_SIZE[pageSize]); + } else if ( gridVirtualized && gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll ) { diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/sort/__snapshots__/sort_indicator.test.tsx.snap b/x-pack/plugins/timelines/public/components/t_grid/body/sort/__snapshots__/sort_indicator.test.tsx.snap index 596a05c4c8ab4..8a7b179da059f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/sort/__snapshots__/sort_indicator.test.tsx.snap +++ b/x-pack/plugins/timelines/public/components/t_grid/body/sort/__snapshots__/sort_indicator.test.tsx.snap @@ -5,6 +5,7 @@ exports[`SortIndicator rendering renders correctly against snapshot 1`] = ` content="Sorted descending" data-test-subj="sort-indicator-tooltip" delay="regular" + display="inlineBlock" position="top" > = ({ height const { http } = useKibana().services; return ( - + diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap index 5a24e30a0657e..20b0691b55bf9 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap @@ -4,6 +4,7 @@ exports[`Transform: Transform List Actions Minimal initializatio Start diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap index e3740ae9a0978..fd97412fa1875 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap @@ -4,6 +4,7 @@ exports[`Transform: Transform List Actions Minimal initialization Stop diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap index d85f9379159d0..fc7f3ca713f4a 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap @@ -4,6 +4,7 @@ exports[`Transform: Transform List Minimal initializat URL", "timelion.uiSettings.graphiteURLLabel": "Graphite URL", @@ -4910,8 +4745,6 @@ "timelion.uiSettings.minimumIntervalLabel": "最低間隔", "timelion.uiSettings.quandlKeyDescription": "{experimentalLabel} www.quandl.com からの API キーです", "timelion.uiSettings.quandlKeyLabel": "Quandl キー", - "timelion.uiSettings.showTutorialDescription": "Timelion アプリの起動時にデフォルトでチュートリアルを表示しますか?", - "timelion.uiSettings.showTutorialLabel": "チュートリアルを表示", "timelion.uiSettings.targetBucketsDescription": "自動間隔の使用時に目標となるバケット数です。", "timelion.uiSettings.targetBucketsLabel": "目標バケット数", "timelion.uiSettings.timeFieldDescription": "{esParam} の使用時にタイムスタンプを含むデフォルトのフィールドです", @@ -6895,20 +6728,16 @@ "xpack.apm.serviceProfiling.valueTypeLabel.samples": "サンプル", "xpack.apm.serviceProfiling.valueTypeLabel.unknown": "その他", "xpack.apm.serviceProfiling.valueTypeLabel.wallTime": "Wall", - "xpack.apm.servicesTable.7xOldDataMessage": "また、移行が必要な古いデータがある可能性もあります。", - "xpack.apm.servicesTable.7xUpgradeServerMessage": "バージョン7.xより前からのアップグレードですか?また、\n APM Server インスタンスを7.0以降にアップグレードしていることも確認してください。", "xpack.apm.servicesTable.environmentColumnLabel": "環境", "xpack.apm.servicesTable.healthColumnLabel": "ヘルス", "xpack.apm.servicesTable.latencyAvgColumnLabel": "レイテンシ(平均)", "xpack.apm.servicesTable.metricsExplanationLabel": "これらのメトリックは何か。", "xpack.apm.servicesTable.nameColumnLabel": "名前", - "xpack.apm.servicesTable.noServicesLabel": "APM サービスがインストールされていないようです。追加しましょう!", "xpack.apm.servicesTable.notFoundLabel": "サービスが見つかりません", "xpack.apm.servicesTable.throughputColumnLabel": "スループット", "xpack.apm.servicesTable.tooltip.metricsExplanation": "サービスメトリックは、トランザクションタイプ「要求」、「ページ読み込み」、または上位の使用可能なトランザクションタイプのいずれかで集計されます。", "xpack.apm.servicesTable.transactionColumnLabel": "トランザクションタイプ", "xpack.apm.servicesTable.transactionErrorRate": "失敗したトランザクション率", - "xpack.apm.servicesTable.UpgradeAssistantLink": "Kibana アップグレードアシスタントで詳細をご覧ください", "xpack.apm.settings.agentConfig": "エージェントの編集", "xpack.apm.settings.agentConfig.createConfigButton.tooltip": "エージェント構成を作成する権限がありません", "xpack.apm.settings.agentConfig.descriptionText": "APMアプリ内からエージェント構成を微調整してください。変更はAPMエージェントに自動的に伝達されるので、再デプロイする必要はありません。", @@ -7154,7 +6983,6 @@ "xpack.apm.tutorial.apmServer.fleet.apmIntegration.button": "APM統合", "xpack.apm.tutorial.apmServer.fleet.manageApmIntegration.button": "FleetでAPM統合を管理", "xpack.apm.tutorial.apmServer.fleet.message": "APMA統合は、APMデータ用にElasticsearchテンプレートとIngest Nodeパイプラインをインストールします。", - "xpack.apm.tutorial.apmServer.fleet.title": "Elastic APM(ベータ版)がFleetで提供されました。", "xpack.apm.tutorial.apmServer.statusCheck.btnLabel": "APM Server ステータスを確認", "xpack.apm.tutorial.apmServer.statusCheck.errorMessage": "APM Server が検出されました。7.0 以上に更新され、動作中であることを確認してください。", "xpack.apm.tutorial.apmServer.statusCheck.successMessage": "APM Server が正しくセットアップされました", @@ -22427,7 +22255,6 @@ "xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network": "ネットワーク", "xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.process": "プロセス", "xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.a": "ユーザー通知オプションを選択すると、{ protectionName }が防御または検出されたときに、ホストユーザーに通知を表示します。", - "xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.b": " ユーザー通知は、以下のテキストボックスでカスタマイズできます。括弧内のタグを使用すると、該当するアクション(防御または検出など)とファイル名を動的に入力できます。", "xpack.securitySolution.endpoint.policyDetailsConfig.protectionLevel": "保護レベル", "xpack.securitySolution.endpoint.policyDetailsConfig.userNotification": "ユーザー通知", "xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dllDriverLoad": "DLL とドライバーの読み込み", @@ -23540,10 +23367,8 @@ "xpack.securitySolution.topN.closeButtonLabel": "閉じる", "xpack.securitySolution.topN.rawEventsSelectLabel": "未加工イベント", "xpack.securitySolution.trustedapps.aboutInfo": "パフォーマンスを改善したり、ホストで実行されている他のアプリケーションとの競合を解消したりするには、信頼できるアプリケーションを追加します。", - "xpack.securitySolution.trustedapps.card.editButtonLabel": "編集", "xpack.securitySolution.trustedapps.card.operator.is": "is", "xpack.securitySolution.trustedapps.card.operator.matches": "一致", - "xpack.securitySolution.trustedapps.card.removeButtonLabel": "削除", "xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg": "[{row}] 無効なハッシュ値", "xpack.securitySolution.trustedapps.create.conditionFieldInvalidPathMsg": "[{row}] パスの形式が正しくありません。値を検証してください", "xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] フィールドエントリには値が必要です", @@ -23571,10 +23396,7 @@ "xpack.securitySolution.trustedapps.deletionError.title": "削除失敗", "xpack.securitySolution.trustedapps.deletionSuccess.text": "「{name}」は信頼できるアプリケーションリストから削除されました。", "xpack.securitySolution.trustedapps.deletionSuccess.title": "正常に削除されました", - "xpack.securitySolution.trustedapps.list.actions.delete": "削除", - "xpack.securitySolution.trustedapps.list.actions.delete.description": "このエントリを削除", "xpack.securitySolution.trustedapps.list.addButton": "信頼できるアプリケーションを追加", - "xpack.securitySolution.trustedapps.list.columns.actions": "アクション", "xpack.securitySolution.trustedapps.list.pageTitle": "信頼できるアプリケーション", "xpack.securitySolution.trustedapps.list.search.placeholder": "次のフィールドで検索:名前、説明、値", "xpack.securitySolution.trustedapps.listEmptyState.message": "現在、エンドポイントには信頼できるアプリケーションがありません。", @@ -23597,18 +23419,9 @@ "xpack.securitySolution.trustedapps.middleware.editIdMissing": "IDが指定されていません", "xpack.securitySolution.trustedapps.policySelect.globalSectionTitle": "割り当て", "xpack.securitySolution.trustedapps.policySelect.globalSwitchTitle": "信頼できるアプリケーションをグローバルに適用", - "xpack.securitySolution.trustedapps.trustedapp.createdAt": "日付が作成されました", - "xpack.securitySolution.trustedapps.trustedapp.createdBy": "作成者", - "xpack.securitySolution.trustedapps.trustedapp.description": "説明", - "xpack.securitySolution.trustedapps.trustedapp.effectScope": "効果範囲", "xpack.securitySolution.trustedapps.trustedapp.entry.field": "フィールド", "xpack.securitySolution.trustedapps.trustedapp.entry.operator": "演算子", "xpack.securitySolution.trustedapps.trustedapp.entry.value": "値", - "xpack.securitySolution.trustedapps.trustedapp.name": "名前", - "xpack.securitySolution.trustedapps.trustedapp.os": "OS", - "xpack.securitySolution.trustedapps.trustedapp.updatedAt": "変更日", - "xpack.securitySolution.trustedapps.trustedapp.updatedBy": "変更者:", - "xpack.securitySolution.trustedapps.updateSuccess.title": "成功!", "xpack.securitySolution.trustedapps.view.toggle.grid": "グリッドビュー", "xpack.securitySolution.trustedapps.view.toggle.list": "リストビュー", "xpack.securitySolution.trustedapps.viewTypeToggle.controlLegend": "ビュータイプ", @@ -27093,4 +26906,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2d30e0dcbbd8c..0eb4b569708f8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3026,7 +3026,6 @@ "home.tutorial.card.sampleDataDescription": "开始使用这些“一键式”数据集浏览 Kibana。", "home.tutorial.card.sampleDataTitle": "样例数据", "home.tutorial.elasticCloudButtonLabel": "Elastic Cloud", - "home.tutorial.instruction_variant.fleet": "Fleet 中的 Elastic APM(公测版)", "home.tutorial.instruction.copyButtonLabel": "复制代码片段", "home.tutorial.instructionSet.checkStatusButtonLabel": "检查状态", "home.tutorial.instructionSet.customizeLabel": "定制您的代码片段", @@ -4430,8 +4429,6 @@ "savedObjectsManagement.managementSectionLabel": "已保存对象", "savedObjectsManagement.objects.savedObjectsDescription": "导入、导出和管理您的已保存搜索、可视化和仪表板。", "savedObjectsManagement.objects.savedObjectsTitle": "已保存对象", - "savedObjectsManagement.objectsTable.deleteConfirmModal.cannotDeleteCallout.content": "部分选定的对象无法删除,且不会列在表摘要中", - "savedObjectsManagement.objectsTable.deleteConfirmModal.cannotDeleteCallout.title": "一些对象无法删除", "savedObjectsManagement.objectsTable.deleteConfirmModal.sharedObjectsCallout.content": "共享对象已从其所在的各个工作区中移除。", "savedObjectsManagement.objectsTable.deleteConfirmModal.sharedObjectsCallout.title": "{sharedObjectsCount, plural, one {# 个已保存对象已共享} other {您的已保存对象有 # 个已共享}}", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel": "取消", @@ -4620,80 +4617,12 @@ "telemetry.welcomeBanner.enableButtonLabel": "启用", "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "隐私声明", "telemetry.welcomeBanner.title": "帮助我们改进 Elastic Stack", - "timelion.badge.readOnly.text": "只读", - "timelion.badge.readOnly.tooltip": "无法保存 Timelion 工作表", - "timelion.breadcrumbs.create": "创建", - "timelion.breadcrumbs.root": "Timelion", - "timelion.cells.actions.fullscreenAriaLabel": "全屏图表", - "timelion.cells.actions.fullscreenTooltip": "全屏", - "timelion.cells.actions.removeAriaLabel": "删除图表", - "timelion.cells.actions.removeTooltip": "移除", - "timelion.cells.actions.reorderAriaLabel": "拖动以重新排序", - "timelion.cells.actions.reorderTooltip": "拖动以重新排序", - "timelion.chart.seriesList.noSchemaWarning": "没有此类面板类型:{renderType}", - "timelion.deprecation.here": "请将其迁移至仪表板。", - "timelion.deprecation.message": "Timelion 应用自 7.0 版起已过时,将在 7.16 中移除。要继续使用 Timelion 工作表,{timeLionDeprecationLink}。", "timelion.emptyExpressionErrorMessage": "Timelion 错误:未提供表达式", - "timelion.expressionInputAriaLabel": "Timelion 表达式", - "timelion.expressionInputPlaceholder": "请尝试使用 {esQuery} 查询", - "timelion.expressionSuggestions.arg.infoTitle": "信息", - "timelion.expressionSuggestions.arg.listTitle": "参数:", - "timelion.expressionSuggestions.arg.nameTitle": "参数类型", - "timelion.expressionSuggestions.arg.typesTitle": "接受的类型", "timelion.expressionSuggestions.argument.description.acceptsText": "接受", "timelion.expressionSuggestions.func.description.chainableHelpText": "可串接", - "timelion.expressionSuggestions.func.description.chainableText": "{help}(可链接)", "timelion.expressionSuggestions.func.description.dataSourceHelpText": "数据源", - "timelion.expressionSuggestions.func.description.dataSourceText": "{help}(数据源)", "timelion.fitFunctions.carry.downSampleErrorMessage": "请勿使用“carry”拟合方法降低采样率,而是使用“scale”或“average”", - "timelion.fullscreen.exitAriaLabel": "退出全屏", - "timelion.fullscreen.exitTooltip": "退出全屏", "timelion.function.help": "Timelion 可视化", - "timelion.help.configuration.firstTimeConfigurationLinkText": "首次配置", - "timelion.help.configuration.notValid.advancedSettingsPathText": "管理/Kibana/高级设置", - "timelion.help.configuration.notValid.notValidSettingsErrorMessage": "无法验证 Elasticsearch 设置:{reason}。请检查您的高级设置,然后重试。({count})", - "timelion.help.configuration.notValid.paragraph1": "如果正在使用 Logstash,则无需进行任何配置,即可开始使用 Timelion 浏览日志数据。要搜索其他索引,请前往 {advancedSettingsPath} 并配置 {esDefaultIndex} 和 {esTimefield} 设置,以匹配您的索引。", - "timelion.help.configuration.notValid.paragraph2": "您还会看到一些其他 Timelion 设置。您现在无需担心这些设置。稍后就会发现,您可以根据需要即时设置其中大部分的设置。", - "timelion.help.configuration.notValid.validateButtonLabel": "验证配置", - "timelion.help.configuration.notValidTitle": "首次配置", - "timelion.help.configuration.valid.advancedSettingsPathText": "管理/Kibana/高级设置", - "timelion.help.configuration.valid.intervalIsAutoText": "全部准备就绪!", - "timelion.help.configuration.valid.intervals.content.intervalIsNotAutoText": "将其设为 {auto} 以允许 Timelion 选择适当的间隔。", - "timelion.help.configuration.valid.intervalsTextPart1": "输入栏右侧的时间间隔选择器可让您控制采样频率。当前设为 {interval}。", - "timelion.help.configuration.valid.intervalsTextPart2": "如果 Timelion 认为您的时间范围和时间间隔组合生成的数据点过多,将引发错误。可以通过在 {advancedSettingsPath} 中配置 {maxBuckets},来调整该限制。", - "timelion.help.configuration.valid.intervalsTitle": "时间间隔", - "timelion.help.configuration.valid.paragraph1Part1": "我们已验证您的默认索引和时间字段,所有设置都看起来没有什么问题。我们找到 {statsMin} 到 {statsMax} 的数据。您大概已准备就绪。如果有问题,请参阅", - "timelion.help.configuration.valid.paragraph1Part2": ",了解有关配置 Elasticsearch 数据源的信息。", - "timelion.help.configuration.valid.paragraph2": "您应该能看到一个图表,但还需要做一些调整才能看到所有感兴趣的数据:", - "timelion.help.configuration.valid.paragraph3": "现在,您应看到一个数据点计数随时间变化的折线图。", - "timelion.help.configuration.valid.timeRangeText": "使用时间筛选,选择包含要可视化的数据的时间段。请确保选择包含上述所示全部或部分时间范围的时间段。", - "timelion.help.configuration.valid.timeRangeTitle": "时间范围", - "timelion.help.configuration.validTitle": "真棒,Elasticsearch 配置正确!", - "timelion.help.dataTransforming.functionReferenceLinkText": "函数引用", - "timelion.help.dataTransforming.paragraph1": "至此,您已经掌握了基础知识,接下来开始体验 Timelion 的强大功能。让我们来计算随着时间的推移数据的某个子集占整体数据的百分比。例如,Web 流量中来自美国的占多少百分比?", - "timelion.help.dataTransforming.paragraph2": "首先,我们需要查找包含 US 的所有事件:{esUsQuery}。", - "timelion.help.dataTransforming.paragraph3": "接下来,要计算美国事件与全部事件的比率。要将 {us} 除以全部,我们可以使用 {divide} 函数:{divideDataQuery}。", - "timelion.help.dataTransforming.paragraph4": "还不错,但这是一个介于 0 到 1 之间的数字。要将其转换为百分比,只需乘以 100:{multiplyDataQuery}。", - "timelion.help.dataTransforming.paragraph5": "现在,我们知道了来自美国的流量所占的百分比,并且可以看到随时间的推移它的变化情况!Timelion 具有很多内置算术函数,例如 {sum}、{subtract}、{multiply} 和 {divide}。其中很多函数可以取序列或数字。此外,还有其他有用的数据转换函数,例如 {movingaverage}、{abs} 和 {derivative}。", - "timelion.help.dataTransforming.paragraph6Part1": "现在您已熟悉了语法,然后,请参阅", - "timelion.help.dataTransforming.paragraph6Part2": ",了解如何使用所有可用的 Timelion 函数。您可以通过单击工具栏上的\\{Docs\\},随时查看参考资料。要返回此教程,请单击参考资料顶部的\\{Tutorial\\}链接。", - "timelion.help.dataTransformingTitle": "转换您的数据:真正的乐趣所在!", - "timelion.help.dontShowHelpButtonLabel": "不再显示此内容", - "timelion.help.expressions.examples.customStylingDescription": "{descriptionTitle}将第一个序列着色为红色,并为第二个序列使用 1 像素宽的条。", - "timelion.help.expressions.examples.customStylingDescriptionTitle": "定制样式。", - "timelion.help.expressions.examples.groupedExpressionsDescription": "{descriptionTitle}您还可以将表达式组链锁到函数。这里两个序列都显示为点,而不是线。", - "timelion.help.expressions.examples.groupedExpressionsDescriptionTitle": "将表达式分组。", - "timelion.help.expressions.examples.namedArgumentsDescription": "{descriptionTitle}请不要尝试记住指定参数所需的顺序,使用命名参数可使表达式更易于读写。", - "timelion.help.expressions.examples.namedArgumentsDescriptionTitle": "命名参数。", - "timelion.help.expressions.examples.twoExpressionsDescription": "{descriptionTitle}同一个图表上的两个表达式。", - "timelion.help.expressions.examples.twoExpressionsDescriptionTitle": "乐趣倍增。", - "timelion.help.expressions.functionReferenceLinkText": "函数引用", - "timelion.help.expressions.paragraph1": "每个表达式都以数据源函数开头。接着,您可以将新函数新函数到数据源后面,以转换和增加数据源。", - "timelion.help.expressions.paragraph2": "顺便提一下,从现在开始,您可能比我们更了解自己的数据。可以随意将示例查询替换为更有意义的查询!", - "timelion.help.expressions.paragraph3": "我们将实验一下,单击工具栏中的”{strongAdd}” 以再添加一个图表或添加三个图表。然后,选择一个图表,复制下列其中一个表达式并粘贴到输入栏,然后按 Enter 键。清除,再重复上述操作来尝试其他表达式。", - "timelion.help.expressions.paragraph4": "Timelion 还提供了其他视图转换函数,可用于定制图表的外观。有关完整列表,请参阅", - "timelion.help.expressions.strongAddText": "添加", - "timelion.help.expressionsTitle": "使用表达式表达您自己", "timelion.help.functions.absHelpText": "返回序列列表中每个值的绝对值", "timelion.help.functions.aggregate.args.functionHelpText": "以下选项之一:{functions}", "timelion.help.functions.aggregateHelpText": "基于对序列中所有点的处理结果创建静态线。可用函数:{functions}", @@ -4824,64 +4753,9 @@ "timelion.help.functions.yaxis.args.unitsHelpText": "用于设置 Y 轴标签格式的函数。以下之一:{formatters}", "timelion.help.functions.yaxis.args.yaxisHelpText": "用于绘制此序列的带编号的 Y 轴,例如,.yaxis(2) 对应于第 2 个 Y 轴。", "timelion.help.functions.yaxisHelpText": "配置各种 Y 轴选项,其中最重要的选项可能是添加第 N 个(例如第 2 个)Y 轴的功能", - "timelion.help.mainPage.functionReference.detailsTable.acceptedTypesColumnLabel": "接受的类型", - "timelion.help.mainPage.functionReference.detailsTable.argumentNameColumnLabel": "参数类型", - "timelion.help.mainPage.functionReference.detailsTable.informationColumnLabel": "信息", - "timelion.help.mainPage.functionReference.gettingStartedText": "请单击任何函数以了解更多信息。马上开始使用?", - "timelion.help.mainPage.functionReference.noArgumentsFunctionErrorMessage": "此函数不接受任何参数。这很简单,不是吗?", - "timelion.help.mainPage.functionReference.welcomePageLinkText": "查看教程", - "timelion.help.mainPage.functionReferenceTitle": "函数引用", - "timelion.help.mainPage.keyboardTips.autoComplete.downArrowDescription": "将焦点切换到自动完成菜单。使用箭头进一步选择词", - "timelion.help.mainPage.keyboardTips.autoComplete.downArrowLabel": "向下箭头", - "timelion.help.mainPage.keyboardTips.autoComplete.enterTabDescription": "选择自动完成菜单中当前选定的词或最顶部的词", - "timelion.help.mainPage.keyboardTips.autoComplete.escDescription": "关闭自动完成菜单", - "timelion.help.mainPage.keyboardTips.autoCompleteTitle": "当自动完成可见时", - "timelion.help.mainPage.keyboardTips.generalEditing.submitRequestText": "提交请求", - "timelion.help.mainPage.keyboardTips.generalEditingTitle": "常规编辑", - "timelion.help.mainPage.keyboardTipsTitle": "键盘提示", - "timelion.help.mainPageTitle": "帮助", - "timelion.help.nextPageButtonLabel": "下一页", - "timelion.help.previousPageButtonLabel": "上一页", - "timelion.help.querying.countMetricAggregationLinkText": "Elasticsearch 指标聚合", - "timelion.help.querying.countTextPart1": "除了具有卓越的事件计数功能,Elasticsearch 数据源还支持任何", - "timelion.help.querying.countTextPart2": "返回单个值的查询。最有用的包括 {min}、{max}、{avg}、{sum} 和 {cardinality}。假设您需要 {srcIp} 字段的唯一计数。只需使用 {cardinality} 指标:{esCardinalityQuery}。要获取 {bytes} 字段的平均值,可以使用 {avg} 指标:{esAvgQuery}。", - "timelion.help.querying.countTitle": "不只是计数", - "timelion.help.querying.esAsteriskQueryDescriptionText": "嗨 Elasticsearch,请在我的默认索引中查找所有内容", - "timelion.help.querying.esIndexQueryDescriptionText": "使用 * 作为 logstash-* 索引的 q (query)", - "timelion.help.querying.luceneQueryLinkText": "Lucene 查询字符串", - "timelion.help.querying.paragraph1": "至此,我们已验证您的 Elasticsearch 数据源工作正常,因此您可以开始提交查询。如果是初学者,请在输入栏中输入 {esPattern},然后按 Enter 键。", - "timelion.help.querying.paragraph2Part1": "即 {esAsteriskQueryDescription}。如果要查找子集,可以输入 {htmlQuery} 之类的内容以算出与 {html} 匹配的事件个数,或输入 {bobQuery} 来查找在 {user} 字段中包含 {bob} 且 {bytes} 字段大于 100 的事件。注意,需要用单引号引起此查询,因为其包含空格。您可以输入任何内容", - "timelion.help.querying.paragraph2Part2": "作为 {esQuery} 函数的第一个参数。", - "timelion.help.querying.passingArgumentsText": "Timelion 具有很多快捷方式,让您可以轻松完成各种常见操作。对于不包含空格或特殊字符的简单参数,无需使用引号。而且很多函数都具有默认值。例如 {esEmptyQuery} 和 {esStarQuery} 有相同的作用。参数也有名称,您无需以特定顺序指定它们。例如,可以输入 {esLogstashQuery},让 Elasticsearch 数据源知道 {esIndexQueryDescription}。", - "timelion.help.querying.passingArgumentsTitle": "传递参数", - "timelion.help.queryingTitle": "查询 Elasticsearch 数据源", - "timelion.help.unknownErrorMessage": "未知错误", - "timelion.help.welcome.content.emphasizedEverythingText": "所有内容", - "timelion.help.welcome.content.functionReferenceLinkText": "跳到函数引用", - "timelion.help.welcome.content.paragraph1": "Timelion 是抓取精准、适用于{emphasizedEverything}的可插拔时间序列接口。如果您的数据存储可以生成时间序列,就能够随意使用 Timelion 的所有强大功能。借助 Timelion,可使用易于掌握的表达式语法,对跨多个数据源的数据集进行比较、合并和整理。尽管本教程主要介绍 Elasticsearch,但您很快会发现,本文所介绍的内容适用于 Timelion 支持的任何数据源。", - "timelion.help.welcome.content.paragraph2": "准备开始了吗?单击“{strongNext}”。想要跳过教程并查看文档?", - "timelion.help.welcome.content.strongNextText": "下一步", - "timelion.help.welcomeTitle": "欢迎来到 {strongTimelionLabel}!", - "timelion.intervals.customIntervalAriaLabel": "定制时间间隔", - "timelion.intervals.selectIntervalAriaLabel": "选择时间间隔", "timelion.noFunctionErrorMessage": "没有此类函数:{name}", - "timelion.panels.noRenderFunctionErrorMessage": "面板必须具有渲染函数", "timelion.panels.timechart.unknownIntervalErrorMessage": "时间间隔未知", "timelion.requestHandlerErrorTitle": "Timelion 请求错误", - "timelion.savedObjectFinder.addNewItemButtonLabel": "添加新的 {item}", - "timelion.savedObjectFinder.manageItemsButtonLabel": "管理 {items}", - "timelion.savedObjectFinder.noMatchesFoundDescription": "未找到任何匹配的 {items}。", - "timelion.savedObjectFinder.pageItemsFromHitCountDescription": "{pageFirstItem}-{pageLastItem}/{hitCount}", - "timelion.savedObjectFinder.sortByButtonLabeAscendingScreenReaderOnly": "升序", - "timelion.savedObjectFinder.sortByButtonLabeDescendingScreenReaderOnly": "降序", - "timelion.savedObjectFinder.sortByButtonLabel": "名称", - "timelion.savedObjectFinder.sortByButtonLabelScreenReaderOnly": "排序依据", - "timelion.savedObjects.howToSaveAsNewDescription": "在 Kibana 的以前版本中,更改 {savedObjectName} 的名称将创建具有新名称的副本。使用“另存为新 {savedObjectName}”复选框来立即执行此操作。", - "timelion.savedObjects.saveAsNewLabel": "另存为新的 {savedObjectName}", - "timelion.saveExpression.successNotificationText": "已保存表达式“{title}”", - "timelion.saveSheet.successNotificationText": "已保存工作表“{title}”", - "timelion.search.submitAriaLabel": "搜索", - "timelion.searchErrorTitle": "Timelion 请求错误", "timelion.serverSideErrors.argumentsOverflowErrorMessage": "太多参数传递到:{functionName}", "timelion.serverSideErrors.bucketsOverflowErrorMessage": "最大桶数已超过:允许 {bucketCount}/{maxBuckets}选择较大的时间间隔或较短的时间范围", "timelion.serverSideErrors.colorFunction.colorNotProvidedErrorMessage": "未提供颜色", @@ -4902,47 +4776,8 @@ "timelion.serverSideErrors.yaxisFunction.notSupportedUnitTypeErrorMessage": "{units} 为不受支持的单元类型。", "timelion.serverSideErrors.yaxisFunction.notValidCurrencyFormatErrorMessage": "货币必须使用三个字母的代码", "timelion.timelionDescription": "在图表上显示时间序列数据。", - "timelion.topNavMenu.addChartButtonAriaLabel": "添加图表", - "timelion.topNavMenu.addChartButtonLabel": "添加", - "timelion.topNavMenu.delete.modal.confirmButtonLabel": "删除", - "timelion.topNavMenu.delete.modal.successNotificationText": "已删除“{title}”", - "timelion.topNavMenu.delete.modal.warningText": "您无法恢复已删除的工作表。", - "timelion.topNavMenu.delete.modalTitle": "是否删除 Timelion 工作表 “{title}”?", - "timelion.topNavMenu.deleteSheetButtonAriaLabel": "删除当前工作表", - "timelion.topNavMenu.deleteSheetButtonLabel": "删除", - "timelion.topNavMenu.helpButtonAriaLabel": "帮助", - "timelion.topNavMenu.helpButtonLabel": "帮助", - "timelion.topNavMenu.newSheetButtonAriaLabel": "新建工作表", - "timelion.topNavMenu.newSheetButtonLabel": "新建", - "timelion.topNavMenu.openSheetButtonAriaLabel": "打开工作表", - "timelion.topNavMenu.openSheetButtonLabel": "打开", - "timelion.topNavMenu.openSheetTitle": "打开工作表", - "timelion.topNavMenu.options.columnsCountLabel": "列(列数必须均分为 12)", - "timelion.topNavMenu.options.rowsCountLabel": "行(这是基于当前窗口高度的目标)", - "timelion.topNavMenu.optionsButtonAriaLabel": "选项", - "timelion.topNavMenu.optionsButtonLabel": "选项", - "timelion.topNavMenu.save.saveAsDashboardPanel.inputPlaceholder": "命名此面板", - "timelion.topNavMenu.save.saveAsDashboardPanel.selectedExpressionLabel": "当前选择的表达式", - "timelion.topNavMenu.save.saveAsDashboardPanel.submitButtonLabel": "保存", - "timelion.topNavMenu.save.saveAsDashboardPanelDescription": "是否需要将图表添加到 Kibana 仪表板?我们可以做到这一点!此选项会将当前选择的表达式另存为面板,该面板可像任何其他内容一样添加到 Kibana 仪表板。注意,如果使用对其他面板的引用,则需要删除该引用,方法是:将引用的表达式直接复制到要保存的表达式中。单击图表可选择要保存的其他表达式。", - "timelion.topNavMenu.save.saveAsDashboardPanelLabel": "将表达式另存为", - "timelion.topNavMenu.save.saveAsDashboardPanelTitle": "将当前表达式另存为 Kibana 仪表板面板", - "timelion.topNavMenu.save.saveEntireSheet.inputAriaLabel": "名称", - "timelion.topNavMenu.save.saveEntireSheet.inputPlaceholder": "命名此工作表......", - "timelion.topNavMenu.save.saveEntireSheet.submitButtonLabel": "保存", - "timelion.topNavMenu.save.saveEntireSheetDescription": "如果您主要在 Timelion 应用中使用 Timelion 表达式,且不需要将 Timelion 图表添加到 Kibana 仪表板,则需要使用此选项。如果您使用对其他面板的引用,可能也需要此选项。", - "timelion.topNavMenu.save.saveEntireSheetLabel": "将工作表另存为", - "timelion.topNavMenu.save.saveEntireSheetTitle": "保存整个 Timelion 工作表", - "timelion.topNavMenu.saveSheetButtonAriaLabel": "保存工作表", - "timelion.topNavMenu.saveSheetButtonLabel": "保存", - "timelion.topNavMenu.sheetOptionsTitle": "工作表选项", - "timelion.topNavMenu.statsDescription": "查询时间 {queryTime}ms /处理时间 {processingTime}ms", - "timelion.uiSettings.defaultColumnsDescription": "默认情况下 Timelion 工作表上的列数目", - "timelion.uiSettings.defaultColumnsLabel": "默认列", "timelion.uiSettings.defaultIndexDescription": "要使用 {esParam} 搜索的默认 Elasticsearch 索引", "timelion.uiSettings.defaultIndexLabel": "默认索引", - "timelion.uiSettings.defaultRowsDescription": "默认情况下 Timelion 工作表上的行数目", - "timelion.uiSettings.defaultRowsLabel": "默认行数", "timelion.uiSettings.experimentalLabel": "实验性", "timelion.uiSettings.graphiteURLDescription": "{experimentalLabel} Graphite 主机的 URL", "timelion.uiSettings.graphiteURLLabel": "Graphite URL", @@ -4955,8 +4790,6 @@ "timelion.uiSettings.minimumIntervalLabel": "最小时间间隔", "timelion.uiSettings.quandlKeyDescription": "{experimentalLabel} 来自 www.quandl.com 的 API 密钥", "timelion.uiSettings.quandlKeyLabel": "Quandl 密钥", - "timelion.uiSettings.showTutorialDescription": "进入 Timelion 应用时我是否应该默认显示教程?", - "timelion.uiSettings.showTutorialLabel": "显示教程", "timelion.uiSettings.targetBucketsDescription": "使用自动时间间隔时想要的存储桶数目", "timelion.uiSettings.targetBucketsLabel": "目标存储桶", "timelion.uiSettings.timeFieldDescription": "使用 {esParam} 时包含时间戳的默认字段", @@ -6949,21 +6782,17 @@ "xpack.apm.serviceProfiling.valueTypeLabel.samples": "样例", "xpack.apm.serviceProfiling.valueTypeLabel.unknown": "其他", "xpack.apm.serviceProfiling.valueTypeLabel.wallTime": "现实时间", - "xpack.apm.servicesTable.7xOldDataMessage": "可能还有需要迁移的旧数据。", - "xpack.apm.servicesTable.7xUpgradeServerMessage": "从 7.x 之前的版本升级?另外,确保您已将\n APM Server 实例升级到至少 7.0。", "xpack.apm.servicesTable.environmentColumnLabel": "环境", "xpack.apm.servicesTable.environmentCount": "{environmentCount, plural, one {1 个环境} other {# 个环境}}", "xpack.apm.servicesTable.healthColumnLabel": "运行状况", "xpack.apm.servicesTable.latencyAvgColumnLabel": "延迟(平均值)", "xpack.apm.servicesTable.metricsExplanationLabel": "这些指标是什么?", "xpack.apm.servicesTable.nameColumnLabel": "名称", - "xpack.apm.servicesTable.noServicesLabel": "似乎您没有安装任何 APM 服务。让我们添加一些!", "xpack.apm.servicesTable.notFoundLabel": "未找到任何服务", "xpack.apm.servicesTable.throughputColumnLabel": "吞吐量", "xpack.apm.servicesTable.tooltip.metricsExplanation": "服务指标将基于事务类型“request”、“page-load”或排名靠前的可用事务类型进行聚合。", "xpack.apm.servicesTable.transactionColumnLabel": "事务类型", "xpack.apm.servicesTable.transactionErrorRate": "失败事务率", - "xpack.apm.servicesTable.UpgradeAssistantLink": "通过访问 Kibana 升级助手来了解详情", "xpack.apm.settings.agentConfig": "代理配置", "xpack.apm.settings.agentConfig.createConfigButton.tooltip": "您无权创建代理配置", "xpack.apm.settings.agentConfig.descriptionText": "从 APM 应用中微调您的代理配置。更改将自动传播到 APM 代理,因此无需重新部署。", @@ -7212,7 +7041,6 @@ "xpack.apm.tutorial.apmServer.fleet.apmIntegration.button": "APM 集成", "xpack.apm.tutorial.apmServer.fleet.manageApmIntegration.button": "在 Fleet 中管理 APM 集成", "xpack.apm.tutorial.apmServer.fleet.message": "APM 集成安装用于 APM 数据的 Elasticsearch 模板和采集节点管道。", - "xpack.apm.tutorial.apmServer.fleet.title": "现在 Fleet 中包含 Elastic APM(公测版)!", "xpack.apm.tutorial.apmServer.statusCheck.btnLabel": "检查 APM Server 状态", "xpack.apm.tutorial.apmServer.statusCheck.errorMessage": "未检测到任何 APM Server。请确保其正在运行并且您已升级到 7.0 或更高版本。", "xpack.apm.tutorial.apmServer.statusCheck.successMessage": "您已正确设置 APM Server", @@ -22776,7 +22604,6 @@ "xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network": "网络", "xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.process": "进程", "xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.a": "选择用户通知选项后,在阻止或检测到{ protectionName }时将向主机用户显示通知。", - "xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.b": " 可在下方文本框中定制用户通知。括号中的标签可用于动态填充适用操作(如已阻止或已检测)和文件名。", "xpack.securitySolution.endpoint.policyDetailsConfig.protectionLevel": "防护级别", "xpack.securitySolution.endpoint.policyDetailsConfig.userNotification": "用户通知", "xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dllDriverLoad": "DLL 和驱动程序加载", @@ -23921,10 +23748,8 @@ "xpack.securitySolution.topN.closeButtonLabel": "关闭", "xpack.securitySolution.topN.rawEventsSelectLabel": "原始事件", "xpack.securitySolution.trustedapps.aboutInfo": "添加受信任的应用程序,以提高性能或缓解与主机上运行的其他应用程序的冲突。", - "xpack.securitySolution.trustedapps.card.editButtonLabel": "编辑", "xpack.securitySolution.trustedapps.card.operator.is": "是", "xpack.securitySolution.trustedapps.card.operator.matches": "匹配", - "xpack.securitySolution.trustedapps.card.removeButtonLabel": "移除", "xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg": "[{row}] 无效的哈希值", "xpack.securitySolution.trustedapps.create.conditionFieldInvalidPathMsg": "[{row}] 路径的格式可能不正确;请验证值", "xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] 字段条目必须包含值", @@ -23952,10 +23777,7 @@ "xpack.securitySolution.trustedapps.deletionError.title": "移除失败", "xpack.securitySolution.trustedapps.deletionSuccess.text": "“{name}”已从受信任的应用程序列表中移除。", "xpack.securitySolution.trustedapps.deletionSuccess.title": "已成功移除", - "xpack.securitySolution.trustedapps.list.actions.delete": "移除", - "xpack.securitySolution.trustedapps.list.actions.delete.description": "移除此条目", "xpack.securitySolution.trustedapps.list.addButton": "添加受信任的应用程序", - "xpack.securitySolution.trustedapps.list.columns.actions": "操作", "xpack.securitySolution.trustedapps.list.pageTitle": "受信任的应用程序", "xpack.securitySolution.trustedapps.list.search.placeholder": "搜索下面的字段:name、description、value", "xpack.securitySolution.trustedapps.list.totalCount": "正在显示 {totalItemsCount, plural, other {# 个受信任的应用程序}}", @@ -23979,18 +23801,9 @@ "xpack.securitySolution.trustedapps.middleware.editIdMissing": "未提供 ID", "xpack.securitySolution.trustedapps.policySelect.globalSectionTitle": "分配", "xpack.securitySolution.trustedapps.policySelect.globalSwitchTitle": "全局应用受信任的应用程序", - "xpack.securitySolution.trustedapps.trustedapp.createdAt": "创建日期", - "xpack.securitySolution.trustedapps.trustedapp.createdBy": "创建者", - "xpack.securitySolution.trustedapps.trustedapp.description": "描述", - "xpack.securitySolution.trustedapps.trustedapp.effectScope": "作用范围", "xpack.securitySolution.trustedapps.trustedapp.entry.field": "字段", "xpack.securitySolution.trustedapps.trustedapp.entry.operator": "运算符", "xpack.securitySolution.trustedapps.trustedapp.entry.value": "值", - "xpack.securitySolution.trustedapps.trustedapp.name": "名称", - "xpack.securitySolution.trustedapps.trustedapp.os": "OS", - "xpack.securitySolution.trustedapps.trustedapp.updatedAt": "修改日期", - "xpack.securitySolution.trustedapps.trustedapp.updatedBy": "修改者", - "xpack.securitySolution.trustedapps.updateSuccess.title": "成功!", "xpack.securitySolution.trustedapps.view.toggle.grid": "网格视图", "xpack.securitySolution.trustedapps.view.toggle.list": "列表视图", "xpack.securitySolution.trustedapps.viewTypeToggle.controlLegend": "视图类型", @@ -27539,4 +27352,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap index 4526274747c09..95c5a9dbc418b 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap @@ -296,7 +296,7 @@ exports[`DonutChart component passes correct props without errors for valid prop }, "axisTitle": Object { "fill": "rgba(52, 55, 65, 1)", - "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", "fontSize": 12, "padding": Object { @@ -327,8 +327,8 @@ exports[`DonutChart component passes correct props without errors for valid prop }, }, "tickLabel": Object { - "fill": "rgba(105, 112, 125, 1)", - "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, + "fill": "rgba(106, 113, 125, 1)", + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", "fontSize": 10, "padding": Object { @@ -347,8 +347,8 @@ exports[`DonutChart component passes correct props without errors for valid prop }, "barSeriesStyle": Object { "displayValue": Object { - "fill": "rgba(105, 112, 125, 1)", - "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, + "fill": "rgba(106, 113, 125, 1)", + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", "fontSize": 8, }, @@ -395,6 +395,42 @@ exports[`DonutChart component passes correct props without errors for valid prop "strokeWidth": 1, }, }, + "goal": Object { + "majorCenterLabel": Object { + "fill": "rgba(52, 55, 65, 1)", + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + }, + "majorLabel": Object { + "fill": "rgba(52, 55, 65, 1)", + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + }, + "minorCenterLabel": Object { + "fill": "rgba(106, 113, 125, 1)", + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + }, + "minorLabel": Object { + "fill": "rgba(106, 113, 125, 1)", + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + }, + "progressLine": Object { + "stroke": "rgba(52, 55, 65, 1)", + }, + "targetLine": Object { + "stroke": "rgba(52, 55, 65, 1)", + }, + "tickLabel": Object { + "fill": "rgba(106, 113, 125, 1)", + "fontFamily": "'Inter', 'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + }, + "tickLine": Object { + "stroke": "rgba(152, 162, 179, 1)", + }, + }, "lineSeriesStyle": Object { "line": Object { "strokeWidth": 2, diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__snapshots__/integration_link.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__snapshots__/integration_link.test.tsx.snap index 502af2eb91051..8fb33fad798c9 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__snapshots__/integration_link.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__snapshots__/integration_link.test.tsx.snap @@ -10,6 +10,7 @@ exports[`IntegrationLink component renders a disabled link when href is undefine ; + const result: ActionType<{}, {}, ParamsType> = { + id: 'test.no-attempts-rate-limit', + name: 'Test: Rate Limit', + minimumLicenseRequired: 'gold', + maxAttempts: 0, + validate: { + params: paramsSchema, + }, + async executor({ config, params, services }) { + await services.scopedClusterClient.index({ + index: params.index, + refresh: 'wait_for', + body: { + params, + config, + reference: params.reference, + source: 'action:test.rate-limit', + }, + }); + return { + status: 'error', + retry: new Date(params.retryAt), + actionId: '', + }; + }, + }; + return result; +} + function getAuthorizationActionType(core: CoreSetup) { const paramsSchema = schema.object({ callClusterAuthorizationIndex: schema.string(), 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 93535826d14e7..3131649e7c742 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 @@ -39,8 +39,7 @@ export default function alertTests({ getService }: FtrProviderContext) { const esTestIndexTool = new ESTestIndexTool(es, retry); const taskManagerUtils = new TaskManagerUtils(es, retry); - // FLAKY: https://github.com/elastic/kibana/issues/106492 - describe.skip('alerts', () => { + describe('alerts', () => { const authorizationIndex = '.kibana-test-authorization'; const objectRemover = new ObjectRemover(supertest); @@ -502,19 +501,6 @@ instanceStateValue: true }) ); - // Enqueue non ephemerically so we the latter code can query properly - const enqueueResponse = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts_fixture/${createdAction.id}/enqueue_action`) - .set('kbn-xsrf', 'foo') - .send({ - params: { - reference, - index: ES_TEST_INDEX_NAME, - retryAt: retryDate.getTime(), - }, - }); - expect(enqueueResponse.status).to.eql(204); - switch (scenario.id) { case 'no_kibana_privileges at space1': case 'global_read at space1': 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 fe67decd7d191..d43fb2e7d835f 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 @@ -23,8 +23,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { const retry = getService('retry'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // FLAKY https://github.com/elastic/kibana/issues/111001 - describe.skip('delete', () => { + describe('delete', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); 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 2126e7383e321..3454ef5c94d9f 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 @@ -17,8 +17,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // FLAKY https://github.com/elastic/kibana/issues/111022 - describe.skip('find', () => { + describe('find', () => { const objectRemover = new ObjectRemover(supertest); afterEach(() => objectRemover.removeAll()); 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 5925f2bcc812f..0400557209348 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 @@ -21,8 +21,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // FLAKY https://github.com/elastic/kibana/issues/111496 - describe.skip('get', () => { + describe('get', () => { const objectRemover = new ObjectRemover(supertest); afterEach(() => objectRemover.removeAll()); 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 d0ab2e0189a44..e628f0b3d950e 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 @@ -32,8 +32,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { .then((response: SupertestResponse) => response.body); } - // FLAKY: https://github.com/elastic/kibana/issues/110801 - describe.skip('update', () => { + describe('update', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts index f937e63840937..533570ae4c16d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts @@ -97,21 +97,8 @@ export default function ({ getService }: FtrProviderContext) { }, }) .expect(204); - await esTestIndexTool.waitForDocs('action:test.failing', reference, 1); - await supertest - .put( - `${getUrlPrefix( - Spaces.space1.id - )}/api/alerts_fixture/Actions-cleanup_failed_action_executions/reschedule_task` - ) - .set('kbn-xsrf', 'foo') - .send({ - runAt: new Date().toISOString(), - }) - .expect(200); - await retry.try(async () => { const searchResult = await es.search({ index: '.kibana_task_manager', @@ -139,5 +126,81 @@ export default function ({ getService }: FtrProviderContext) { expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.eql(0); }); }); + + it('should never leaved a failed task, even if max attempts is reached', async () => { + // We have to provide the test.rate-limit the next runAt, for testing purposes + const retryDate = new Date(Date.now() + 1); + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: 'test.no-attempts-rate-limit', + config: {}, + secrets: {}, + }) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + + const reference = `actions-enqueue-2:${Spaces.space1.id}:${createdAction.id}`; + await supertest + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts_fixture/${createdAction.id}/enqueue_action` + ) + .set('kbn-xsrf', 'foo') + .send({ + params: { + reference, + index: ES_TEST_INDEX_NAME, + retryAt: retryDate.getTime(), + }, + }) + .expect(204); + + await retry.try(async () => { + const runningSearchResult = await es.search({ + index: '.kibana_task_manager', + body: { + query: { + bool: { + must: [ + { + term: { + 'task.taskType': 'actions:test.no-attempts-rate-limit', + }, + }, + { + term: { + 'task.status': 'running', + }, + }, + ], + }, + }, + }, + }); + expect((runningSearchResult.body.hits.total as estypes.SearchTotalHits).value).to.eql(1); + }); + + await retry.try(async () => { + const searchResult = await es.search({ + index: '.kibana_task_manager', + body: { + query: { + bool: { + must: [ + { + term: { + 'task.taskType': 'actions:test.no-attempts-rate-limit', + }, + }, + ], + }, + }, + }, + }); + expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.eql(0); + }); + }); }); } diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index 6a6a0e13a1e1e..c157c8ae354ac 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -102,7 +102,6 @@ export default function ({ getService }: FtrProviderContext) { 'enterpriseSearch', 'advancedSettings', 'indexPatterns', - 'timelion', 'graph', 'monitoring', 'savedObjectsManagement', diff --git a/x-pack/test/api_integration/apis/security/license_downgrade.ts b/x-pack/test/api_integration/apis/security/license_downgrade.ts index dcdcc039bc9d6..a56bb5908ca05 100644 --- a/x-pack/test/api_integration/apis/security/license_downgrade.ts +++ b/x-pack/test/api_integration/apis/security/license_downgrade.ts @@ -26,7 +26,6 @@ export default function ({ getService }: FtrProviderContext) { 'minimal_read', 'url_create', 'store_search_session', - 'generate_report', ]; const trialPrivileges = await supertest .get('/api/security/privileges') diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index bbb0fc60cb3ce..42666e10341f1 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -26,7 +26,7 @@ export default function ({ getService }: FtrProviderContext) { features: { graph: ['all', 'read'], savedObjectsTagging: ['all', 'read'], - canvas: ['all', 'read', 'minimal_all', 'minimal_read', 'generate_report'], + canvas: ['all', 'read'], maps: ['all', 'read'], fleet: ['all', 'read'], actions: ['all', 'read'], @@ -45,16 +45,8 @@ export default function ({ getService }: FtrProviderContext) { 'minimal_read', 'url_create', 'store_search_session', - 'generate_report', - ], - visualize: [ - 'all', - 'read', - 'minimal_all', - 'minimal_read', - 'url_create', - 'generate_report', ], + visualize: ['all', 'read', 'minimal_all', 'minimal_read', 'url_create'], dashboard: [ 'all', 'read', @@ -62,14 +54,11 @@ export default function ({ getService }: FtrProviderContext) { 'minimal_read', 'url_create', 'store_search_session', - 'generate_report', - 'download_csv_report', ], dev_tools: ['all', 'read'], advancedSettings: ['all', 'read'], indexPatterns: ['all', 'read'], savedObjectsManagement: ['all', 'read'], - timelion: ['all', 'read'], osquery: [ 'all', 'read', diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index dc00be028412b..368d5361b7e1c 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -28,7 +28,6 @@ export default function ({ getService }: FtrProviderContext) { indexPatterns: ['all', 'read'], savedObjectsManagement: ['all', 'read'], savedObjectsTagging: ['all', 'read'], - timelion: ['all', 'read'], graph: ['all', 'read'], maps: ['all', 'read'], canvas: ['all', 'read'], diff --git a/x-pack/test/api_integration/apis/spaces/get_active_space.ts b/x-pack/test/api_integration/apis/spaces/get_active_space.ts index e1edfdf5a7092..627b0847aee35 100644 --- a/x-pack/test/api_integration/apis/spaces/get_active_space.ts +++ b/x-pack/test/api_integration/apis/spaces/get_active_space.ts @@ -17,7 +17,7 @@ export default function ({ getService }: FtrProviderContext) { await spacesService.create({ id: 'foo-space', name: 'Foo Space', - disabledFeatures: ['timelion'], + disabledFeatures: [], color: '#AABBCC', }); }); @@ -63,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200, { id: 'foo-space', name: 'Foo Space', - disabledFeatures: ['timelion'], + disabledFeatures: [], color: '#AABBCC', }); }); diff --git a/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json b/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json index 74d91a6215c79..1da39cf296537 100644 --- a/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json +++ b/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json @@ -195,9 +195,6 @@ "search": { "total": 0 }, - "timelion_sheet": { - "total": 0 - }, "versions": [ { "count": 1, diff --git a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json index f1cc32f33dd2c..ad1ff9e82bb45 100644 --- a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json +++ b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json @@ -972,9 +972,6 @@ "graph_workspace": { "total": 0 }, - "timelion_sheet": { - "total": 0 - }, "indices": 1, "plugins": {} }, diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts b/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts index 2412b91e6ee68..508a6584e9246 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts @@ -77,7 +77,6 @@ export default function ({ getService }: FtrProviderContext) { expect(stats.stack_stats.kibana.graph_workspace.total).to.be.a('number'); expect(stats.stack_stats.kibana.index_pattern.total).to.be.a('number'); expect(stats.stack_stats.kibana.search.total).to.be.a('number'); - expect(stats.stack_stats.kibana.timelion_sheet.total).to.be.a('number'); expect(stats.stack_stats.kibana.visualization.total).to.be.a('number'); expect(stats.stack_stats.kibana.plugins.apm.services_per_agent).to.be.an('object'); @@ -157,7 +156,6 @@ export default function ({ getService }: FtrProviderContext) { 'stack_stats.kibana.os', 'stack_stats.kibana.plugins', 'stack_stats.kibana.search', - 'stack_stats.kibana.timelion_sheet', 'stack_stats.kibana.versions', 'stack_stats.kibana.visualization', 'stack_stats.xpack.ccr', diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts index c8bb844238020..d3e5b08233487 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts @@ -24,6 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { serviceName: 'opbeans-java', transactionType: 'request' as string | undefined, environment: 'ENVIRONMENT_ALL', + interval: '5m', }, }, }); diff --git a/x-pack/test/apm_api_integration/tests/historical_data/has_data.ts b/x-pack/test/apm_api_integration/tests/historical_data/has_data.ts new file mode 100644 index 0000000000000..ec3f0e5e7f362 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/historical_data/has_data.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + + registry.when( + 'Historical data when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(`/api/apm/has_data`); + + expect(response.status).to.be(200); + expect(response.body.hasData).to.be(false); + }); + } + ); + + registry.when( + 'Historical data when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns hasData: true', async () => { + const response = await supertest.get(`/api/apm/has_data`); + + expect(response.status).to.be(200); + expect(response.body.hasData).to.be(true); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index c0690fd2f8260..6c989e61bc6bf 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -224,6 +224,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./csm/web_core_vitals')); }); + describe('historical_data/has_data', function () { + loadTestFile(require.resolve('./historical_data/has_data')); + }); + registry.run(providerContext); }); } diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts index 23b2ca7cfefe9..8e79d6cda58e1 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -37,7 +37,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expect(response.status).to.be(200); - expect(response.body.hasHistoricalData).to.be(false); expect(response.body.hasLegacyData).to.be(false); expect(response.body.items.length).to.be(0); }); @@ -66,10 +65,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.eql(200); }); - it('returns hasHistoricalData: true', () => { - expect(response.body.hasHistoricalData).to.be(true); - }); - it('returns hasLegacyData: false', () => { expect(response.body.hasLegacyData).to.be(false); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts b/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts index 969315cb3f98d..53225e4ea2ce0 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts @@ -17,6 +17,7 @@ import { getSignalStatus, createSignalsIndex, deleteSignalsIndex } from '../../u // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('query_signals_route and find_alerts_route', () => { describe('validation checks', () => { @@ -61,6 +62,49 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('backwards compatibility', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/endpoint/resolver/signals'); + await createSignalsIndex(supertest); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/endpoint/resolver/signals'); + await deleteSignalsIndex(supertest); + }); + + it('should be able to filter old signals on host.os.name.caseless using runtime field', async () => { + const query = { + query: { + bool: { + should: [{ match_phrase: { 'host.os.name.caseless': 'windows' } }], + }, + }, + }; + const { body } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(query) + .expect(200); + expect(body.hits.total.value).to.eql(3); + }); + + it('should be able to filter old signals using field aliases', async () => { + const query = { + query: { + bool: { + should: [{ match_phrase: { 'kibana.alert.workflow_status': 'open' } }], + }, + }, + }; + const { body } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(query) + .expect(200); + expect(body.hits.total.value).to.eql(3); + }); + }); + describe('find_alerts_route', () => { describe('validation checks', () => { it('should not give errors when querying and the signals index does not exist yet', async () => { diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/mappings.json b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/mappings.json index 422e2fa74af00..a083c8f7e3bcf 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/mappings.json +++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/mappings.json @@ -44,7 +44,6 @@ "search": "181661168bbadd1eff5902361e2a0d5c", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "todo": "082a2cc96a590268344d5cd74c159ac4", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", @@ -2163,47 +2162,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "todo": { "properties": { "icon": { diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects_different_key/mappings.json b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects_different_key/mappings.json index 6de44ddece61d..22b79eb9bbd8e 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects_different_key/mappings.json +++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects_different_key/mappings.json @@ -44,7 +44,6 @@ "search": "181661168bbadd1eff5902361e2a0d5c", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "todo": "082a2cc96a590268344d5cd74c159ac4", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", @@ -2190,47 +2189,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "todo": { "properties": { "icon": { diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/key_rotation/mappings.json b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/key_rotation/mappings.json index 26364c89dba3d..74083cbfa949a 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/key_rotation/mappings.json +++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/key_rotation/mappings.json @@ -81,7 +81,6 @@ "maps-telemetry": "5ef305b18111b77789afefbd36b66171", "namespace": "2f4316de49999235636386fe51dc06c1", "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "config": "c63748b75f39d0c54de12d12c1ccbc20", "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", @@ -2170,47 +2169,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts index 3b5e30e994b71..5a73f31c8427f 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts @@ -33,7 +33,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { kibana: [ { feature: { - canvas: ['minimal_all'], + canvas: ['all'], }, spaces: ['*'], }, diff --git a/x-pack/test/functional/apps/canvas/reports.ts b/x-pack/test/functional/apps/canvas/reports.ts index 32dd89e025855..468430c390030 100644 --- a/x-pack/test/functional/apps/canvas/reports.ts +++ b/x-pack/test/functional/apps/canvas/reports.ts @@ -21,17 +21,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Canvas PDF Report Generation', () => { before('initialize tests', async () => { log.debug('ReportingPage:initTests'); - await security.role.create('test_reporting_user', { + await security.role.create('test_canvas_user', { elasticsearch: { cluster: [], indices: [], run_as: [] }, kibana: [ { spaces: ['*'], base: [], - feature: { canvas: ['minimal_read', 'generate_report'] }, + feature: { canvas: ['read'] }, }, ], }); - await security.testUser.setRoles(['kibana_admin', 'test_reporting_user']); + await security.testUser.setRoles([ + 'test_canvas_user', + 'reporting_user', // NOTE: the built-in role granting full reporting access is deprecated. See xpack.reporting.roles.enabled + ]); await kibanaServer.importExport.load(archive); await browser.setWindowSize(1600, 850); }); diff --git a/x-pack/test/functional/apps/dashboard/reporting/__snapshots__/download_csv.snap b/x-pack/test/functional/apps/dashboard/reporting/__snapshots__/download_csv.snap index 10384b865c82e..98dc901eaf306 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/__snapshots__/download_csv.snap +++ b/x-pack/test/functional/apps/dashboard/reporting/__snapshots__/download_csv.snap @@ -1,53 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`dashboard Reporting Download CSV E-Commerce Data Download CSV export of a saved search panel 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,45,591503,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0006400064, ZO0150601506\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,12,591709,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0638206382, ZO0038800388\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,52,590937,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0297602976, ZO0565605656\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,29,590976,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0561405614, ZO0281602816\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,41,591598,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0276702767, ZO0291702917\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,44,590927,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0046600466, ZO0050800508\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,46,591299,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0229002290, ZO0674406744\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,36,591133,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0529905299, ZO0617006170\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,13,591175,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0299402994, ZO0433504335\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,14,591149,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0584905849, ZO0578405784\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,27,591754,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0335803358, ZO0325903259\\" -" -`; - -exports[`dashboard Reporting Download CSV E-Commerce Data Downloads a filtered CSV export of a saved search panel 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,45,591503,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0006400064, ZO0150601506\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,12,591709,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0638206382, ZO0038800388\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,52,590937,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0297602976, ZO0565605656\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,29,590976,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0561405614, ZO0281602816\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,41,591598,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0276702767, ZO0291702917\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,44,590927,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0046600466, ZO0050800508\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,46,591299,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0229002290, ZO0674406744\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,36,591133,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0529905299, ZO0617006170\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,13,591175,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0299402994, ZO0433504335\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,14,591149,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0584905849, ZO0578405784\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,27,591754,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0335803358, ZO0325903259\\" -" -`; - exports[`dashboard Reporting Download CSV Field Formatters and Scripted Fields Download CSV export of a saved search panel 1`] = ` "date,\\"_id\\",name,gender,value,year,\\"years_ago\\",\\"date_informal\\" -\\"Jan 1, 1984 @ 00:00:00.000\\",\\"1984-Fethany-F\\",Fethany,F,5,1984,\\"35.00000000000000000000\\",\\"Jan 1st 84\\" -\\"Jan 1, 1983 @ 00:00:00.000\\",\\"1983-Fethany-F\\",Fethany,F,\\"1,043\\",1983,\\"36.00000000000000000000\\",\\"Jan 1st 83\\" \\"Jan 1, 1982 @ 00:00:00.000\\",\\"1982-Fethany-F\\",Fethany,F,780,1982,\\"37.00000000000000000000\\",\\"Jan 1st 82\\" -\\"Jan 1, 1981 @ 00:00:00.000\\",\\"1981-Fethany-F\\",Fethany,F,655,1981,\\"38.00000000000000000000\\",\\"Jan 1st 81\\" -\\"Jan 1, 1980 @ 00:00:00.000\\",\\"1980-Fethany-F\\",Fethany,F,702,1980,\\"39.00000000000000000000\\",\\"Jan 1st 80\\" " `; diff --git a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts index 94540aa8b4c46..2d6812c61e554 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts +++ b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts @@ -83,7 +83,39 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await clickDownloadCsv(); const csvFile = await getDownload(getCsvPath('Ecommerce Data')); - expectSnapshot(csvFile).toMatch(); + const lines = csvFile.trim().split('\n'); + expectSnapshot(csvFile.length).toMatchInline(`782100`); + expectSnapshot(lines.length).toMatchInline(`4676`); + + // instead of matching all 4676 lines of CSV text, this verifies the first 10 and last 10 lines + expectSnapshot(lines.slice(0, 10)).toMatchInline(` + Array [ + "\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,45,591503,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0006400064, ZO0150601506\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,12,591709,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0638206382, ZO0038800388\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,52,590937,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0297602976, ZO0565605656\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,29,590976,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0561405614, ZO0281602816\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,41,591598,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0276702767, ZO0291702917\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,44,590927,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0046600466, ZO0050800508\\"", + ] + `); + expectSnapshot(lines.slice(-10)).toMatchInline(` + Array [ + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,24,550580,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0144801448, ZO0219602196\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Accessories, Men's Clothing\\",EUR,33,551324,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0316803168, ZO0566905669\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Accessories, Men's Clothing\\",EUR,51,551355,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0313403134, ZO0561205612\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Accessories\\",EUR,28,550957,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0133101331, ZO0189401894\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Accessories, Men's Clothing\\",EUR,39,551154,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0466704667, ZO0617306173\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Accessories\\",EUR,27,551204,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0212602126, ZO0200702007\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,45,550466,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0260702607, ZO0363203632\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,15,550503,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0587505875, ZO0566405664\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Accessories, Women's Shoes\\",EUR,27,550538,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0699406994, ZO0246202462\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,13,550568,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0388403884, ZO0447604476\\"", + ] + `); }); it('Downloads a filtered CSV export of a saved search panel', async function () { @@ -91,13 +123,45 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); // add a filter - await filterBar.addFilter('currency', 'is', 'EUR'); + await filterBar.addFilter('category', 'is', `Men's Shoes`); await clickActionsMenu('EcommerceData'); await clickDownloadCsv(); const csvFile = await getDownload(getCsvPath('Ecommerce Data')); - expectSnapshot(csvFile).toMatch(); + const lines = csvFile.trim().split('\n'); + expectSnapshot(csvFile.length).toMatchInline(`165557`); + expectSnapshot(lines.length).toMatchInline(`945`); + + // instead of matching all 4676 lines of CSV text, this verifies the first 10 and last 10 lines + expectSnapshot(lines.slice(0, 10)).toMatchInline(` + Array [ + "\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,4,591148,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0290302903, ZO0513705137\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,14,591562,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0544305443, ZO0108001080\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,30,591411,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0693506935, ZO0532405324\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,38,722629,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0424204242, ZO0403504035, ZO0506705067, ZO0395603956\\"", + ] + `); + expectSnapshot(lines.slice(-10)).toMatchInline(` + Array [ + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,37,550425,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0256602566, ZO0516305163\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,52,719265,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0619506195, ZO0297802978, ZO0125001250, ZO0693306933\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,34,550990,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0404404044, ZO0570605706\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,7,550663,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0399703997, ZO0560905609\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,34,551697,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0387903879, ZO0693206932\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,9,550784,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0683206832, ZO0687706877\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,15,550412,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0508905089, ZO0681206812\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,23,551556,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0512405124, ZO0551405514\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,550473,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0688006880, ZO0450504505\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,13,550568,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0388403884, ZO0447604476\\"", + ] + `); }); it('Gets the correct filename if panel titles are hidden', async () => { @@ -128,8 +192,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.loadSavedDashboard('names dashboard'); await PageObjects.timePicker.setAbsoluteRange( - 'Jan 01, 1980 @ 00:00:00.000', - 'Dec 31, 1984 @ 23:59:59.000' + 'Nov 26, 1981 @ 21:54:15.526', + 'Mar 5, 1982 @ 18:17:44.821' ); await PageObjects.common.sleep(1000); diff --git a/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts b/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts index 312eba7bd6380..a2523c6d44244 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts +++ b/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts @@ -39,7 +39,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await kibanaServer.importExport.load(ecommerceSOPath); await browser.setWindowSize(1600, 850); - await security.role.create('test_reporting_user', { + await security.role.create('test_dashboard_user', { elasticsearch: { cluster: [], indices: [ @@ -55,12 +55,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { { spaces: ['*'], base: [], - feature: { dashboard: ['minimal_all', 'generate_report'] }, + feature: { dashboard: ['minimal_all'] }, }, ], }); - await security.testUser.setRoles(['test_reporting_user']); + await security.testUser.setRoles([ + 'test_dashboard_user', + 'reporting_user', // NOTE: the built-in role granting full reporting access is deprecated. See the xpack.reporting.roles.enabled setting + ]); }); after('clean up archives', async () => { await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce'); diff --git a/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap b/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap index c7666bf00dd53..09f4698b51bb8 100644 --- a/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap +++ b/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap @@ -1,88 +1,421 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`discover Discover CSV Export Generate CSV: archived search generates a report with data 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,45,591503,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0006400064, ZO0150601506\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,12,591709,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0638206382, ZO0038800388\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,52,590937,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0297602976, ZO0565605656\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,29,590976,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0561405614, ZO0281602816\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,41,591598,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0276702767, ZO0291702917\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,44,590927,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0046600466, ZO0050800508\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,46,591299,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0229002290, ZO0674406744\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,36,591133,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0529905299, ZO0617006170\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,13,591175,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0299402994, ZO0433504335\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,14,591149,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0584905849, ZO0578405784\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,27,591754,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0335803358, ZO0325903259\\" -" -`; - -exports[`discover Discover CSV Export Generate CSV: archived search generates a report with discover:searchFieldsFromSource = true 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,45,591503,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0006400064, ZO0150601506\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,12,591709,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0638206382, ZO0038800388\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,52,590937,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0297602976, ZO0565605656\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,29,590976,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0561405614, ZO0281602816\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,41,591598,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0276702767, ZO0291702917\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,44,590927,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0046600466, ZO0050800508\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,46,591299,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0229002290, ZO0674406744\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,36,591133,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0529905299, ZO0617006170\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,13,591175,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0299402994, ZO0433504335\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,14,591149,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0584905849, ZO0578405784\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,27,591754,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0335803358, ZO0325903259\\" -" +exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: default 1`] = ` +Array [ + "\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user", + "3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,\\"(empty)\\",Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{", + " \\"\\"coordinates\\"\\": [", + " 54.4,", + " 24.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.375, 33, 10.344, 6.109\\",\\"80, 60, 21.984, 11.992\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",174,174,4,4,order,sultan", + "9gMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",EUR,Pia,Pia,\\"Pia Richards\\",\\"Pia Richards\\",FEMALE,45,Richards,Richards,\\"(empty)\\",Saturday,5,\\"pia@richards-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591503,\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"10.703, 9.867\\",\\"20.984, 20.984\\",\\"14,761, 11,632\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"1, 1\\",\\"ZO0006400064, ZO0150601506\\",\\"0, 0\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"0, 0\\",\\"ZO0006400064, ZO0150601506\\",\\"41.969\\",\\"41.969\\",2,2,order,pia", + "BgMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing\\",\\"Women's Clothing\\",EUR,Brigitte,Brigitte,\\"Brigitte Meyer\\",\\"Brigitte Meyer\\",FEMALE,12,Meyer,Meyer,\\"(empty)\\",Saturday,5,\\"brigitte@meyer-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591709,\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"7.988, 33\\",\\"7.988, 33\\",\\"Women's Clothing, Women's Clothing\\",\\"Women's Clothing, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"3.6, 17.484\\",\\"7.988, 33\\",\\"20,734, 7,539\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"1, 1\\",\\"ZO0638206382, ZO0038800388\\",\\"0, 0\\",\\"7.988, 33\\",\\"7.988, 33\\",\\"0, 0\\",\\"ZO0638206382, ZO0038800388\\",\\"40.969\\",\\"40.969\\",2,2,order,brigitte", + "KQMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Abd,Abd,\\"Abd Mccarthy\\",\\"Abd Mccarthy\\",MALE,52,Mccarthy,Mccarthy,\\"(empty)\\",Saturday,5,\\"abd@mccarthy-family.zzz\\",Cairo,Africa,EG,\\"{", + " \\"\\"coordinates\\"\\": [", + " 31.3,", + " 30.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Cairo Governorate\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590937,\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"28.984, 12.992\\",\\"28.984, 12.992\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"13.344, 6.109\\",\\"28.984, 12.992\\",\\"14,438, 23,607\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"1, 1\\",\\"ZO0297602976, ZO0565605656\\",\\"0, 0\\",\\"28.984, 12.992\\",\\"28.984, 12.992\\",\\"0, 0\\",\\"ZO0297602976, ZO0565605656\\",\\"41.969\\",\\"41.969\\",2,2,order,abd", + "KgMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Robert,Robert,\\"Robert Banks\\",\\"Robert Banks\\",MALE,29,Banks,Banks,\\"(empty)\\",Saturday,5,\\"robert@banks-family.zzz\\",\\"-\\",Asia,SA,\\"{", + " \\"\\"coordinates\\"\\": [", + " 45,", + " 25", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"-\\",\\"Elitelligence, Oceanavigations\\",\\"Elitelligence, Oceanavigations\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590976,\\"sold_product_590976_21270, sold_product_590976_13220\\",\\"sold_product_590976_21270, sold_product_590976_13220\\",\\"11.992, 24.984\\",\\"11.992, 24.984\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Oceanavigations\\",\\"Elitelligence, Oceanavigations\\",\\"6.23, 12.25\\",\\"11.992, 24.984\\",\\"21,270, 13,220\\",\\"Print T-shirt - white, Chinos - dark grey\\",\\"Print T-shirt - white, Chinos - dark grey\\",\\"1, 1\\",\\"ZO0561405614, ZO0281602816\\",\\"0, 0\\",\\"11.992, 24.984\\",\\"11.992, 24.984\\",\\"0, 0\\",\\"ZO0561405614, ZO0281602816\\",\\"36.969\\",\\"36.969\\",2,2,order,robert", + "awMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Jim,Jim,\\"Jim Hansen\\",\\"Jim Hansen\\",MALE,41,Hansen,Hansen,\\"(empty)\\",Saturday,5,\\"jim@hansen-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Low Tide Media\\",\\"Low Tide Media\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591636,\\"sold_product_591636_1295, sold_product_591636_6498\\",\\"sold_product_591636_1295, sold_product_591636_6498\\",\\"50, 50\\",\\"50, 50\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Low Tide Media\\",\\"Low Tide Media, Low Tide Media\\",\\"25, 27.484\\",\\"50, 50\\",\\"1,295, 6,498\\",\\"Smart lace-ups - dark brown, Suit jacket - dark blue\\",\\"Smart lace-ups - dark brown, Suit jacket - dark blue\\",\\"1, 1\\",\\"ZO0385003850, ZO0408604086\\",\\"0, 0\\",\\"50, 50\\",\\"50, 50\\",\\"0, 0\\",\\"ZO0385003850, ZO0408604086\\",100,100,2,2,order,jim", + "eQMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes\\",\\"Men's Shoes\\",EUR,Thad,Thad,\\"Thad Lamb\\",\\"Thad Lamb\\",MALE,30,Lamb,Lamb,\\"(empty)\\",Saturday,5,\\"thad@lamb-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Elitelligence,Elitelligence,\\"Jul 12, 2019 @ 00:00:00.000\\",591539,\\"sold_product_591539_15260, sold_product_591539_14221\\",\\"sold_product_591539_15260, sold_product_591539_14221\\",\\"20.984, 18.984\\",\\"20.984, 18.984\\",\\"Men's Shoes, Men's Shoes\\",\\"Men's Shoes, Men's Shoes\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Elitelligence\\",\\"Elitelligence, Elitelligence\\",\\"10.078, 10.25\\",\\"20.984, 18.984\\",\\"15,260, 14,221\\",\\"Casual lace-ups - dark blue, Trainers - navy\\",\\"Casual lace-ups - dark blue, Trainers - navy\\",\\"1, 1\\",\\"ZO0505605056, ZO0513605136\\",\\"0, 0\\",\\"20.984, 18.984\\",\\"20.984, 18.984\\",\\"0, 0\\",\\"ZO0505605056, ZO0513605136\\",\\"39.969\\",\\"39.969\\",2,2,order,thad", + "egMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Jim,Jim,\\"Jim Bryant\\",\\"Jim Bryant\\",MALE,41,Bryant,Bryant,\\"(empty)\\",Saturday,5,\\"jim@bryant-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Oceanavigations,Oceanavigations,\\"Jul 12, 2019 @ 00:00:00.000\\",591598,\\"sold_product_591598_20597, sold_product_591598_2774\\",\\"sold_product_591598_20597, sold_product_591598_2774\\",\\"10.992, 85\\",\\"10.992, 85\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Oceanavigations\\",\\"Oceanavigations, Oceanavigations\\",\\"5.93, 45.875\\",\\"10.992, 85\\",\\"20,597, 2,774\\",\\"Tie - brown/black , Classic coat - black\\",\\"Tie - brown/black , Classic coat - black\\",\\"1, 1\\",\\"ZO0276702767, ZO0291702917\\",\\"0, 0\\",\\"10.992, 85\\",\\"10.992, 85\\",\\"0, 0\\",\\"ZO0276702767, ZO0291702917\\",96,96,2,2,order,jim", + "fwMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing\\",\\"Women's Clothing\\",EUR,Betty,Betty,\\"Betty Roberson\\",\\"Betty Roberson\\",FEMALE,44,Roberson,Roberson,\\"(empty)\\",Saturday,5,\\"betty@roberson-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.7", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Tigress Enterprises\\",\\"Tigress Enterprises\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590927,\\"sold_product_590927_15436, sold_product_590927_22598\\",\\"sold_product_590927_15436, sold_product_590927_22598\\",\\"28.984, 28.984\\",\\"28.984, 28.984\\",\\"Women's Clothing, Women's Clothing\\",\\"Women's Clothing, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises, Tigress Enterprises\\",\\"Tigress Enterprises, Tigress Enterprises\\",\\"14.781, 14.781\\",\\"28.984, 28.984\\",\\"15,436, 22,598\\",\\"Jersey dress - anthra/black, Shift dress - black\\",\\"Jersey dress - anthra/black, Shift dress - black\\",\\"1, 1\\",\\"ZO0046600466, ZO0050800508\\",\\"0, 0\\",\\"28.984, 28.984\\",\\"28.984, 28.984\\",\\"0, 0\\",\\"ZO0046600466, ZO0050800508\\",\\"57.969\\",\\"57.969\\",2,2,order,betty", + "gAMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing, Men's Shoes\\",\\"Men's Clothing, Men's Shoes\\",EUR,Robbie,Robbie,\\"Robbie Hubbard\\",\\"Robbie Hubbard\\",MALE,48,Hubbard,Hubbard,\\"(empty)\\",Saturday,5,\\"robbie@hubbard-family.zzz\\",Dubai,Asia,AE,\\"{", + " \\"\\"coordinates\\"\\": [", + " 55.3,", + " 25.3", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Dubai,\\"Low Tide Media, Angeldale\\",\\"Low Tide Media, Angeldale\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590970,\\"sold_product_590970_11228, sold_product_590970_12060\\",\\"sold_product_590970_11228, sold_product_590970_12060\\",\\"24.984, 50\\",\\"24.984, 50\\",\\"Men's Clothing, Men's Shoes\\",\\"Men's Clothing, Men's Shoes\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Angeldale\\",\\"Low Tide Media, Angeldale\\",\\"12, 25.984\\",\\"24.984, 50\\",\\"11,228, 12,060\\",\\"Tracksuit top - offwhite multicolor, Lace-ups - black/red\\",\\"Tracksuit top - offwhite multicolor, Lace-ups - black/red\\",\\"1, 1\\",\\"ZO0455604556, ZO0680806808\\",\\"0, 0\\",\\"24.984, 50\\",\\"24.984, 50\\",\\"0, 0\\",\\"ZO0455604556, ZO0680806808\\",75,75,2,2,order,robbie", + "6AMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",EUR,Abigail,Abigail,\\"Abigail Willis\\",\\"Abigail Willis\\",FEMALE,46,Willis,Willis,\\"(empty)\\",Saturday,5,\\"abigail@willis-family.zzz\\",Birmingham,Europe,GB,\\"{", + " \\"\\"coordinates\\"\\": [", + " -1.9,", + " 52.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Birmingham,\\"Tigress Enterprises MAMA, Angeldale\\",\\"Tigress Enterprises MAMA, Angeldale\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591299,\\"sold_product_591299_19895, sold_product_591299_5787\\",\\"sold_product_591299_19895, sold_product_591299_5787\\",\\"33, 85\\",\\"33, 85\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises MAMA, Angeldale\\",\\"Tigress Enterprises MAMA, Angeldale\\",\\"17.156, 44.188\\",\\"33, 85\\",\\"19,895, 5,787\\",\\"Summer dress - black/offwhite, Ankle boots - black\\",\\"Summer dress - black/offwhite, Ankle boots - black\\",\\"1, 1\\",\\"ZO0229002290, ZO0674406744\\",\\"0, 0\\",\\"33, 85\\",\\"33, 85\\",\\"0, 0\\",\\"ZO0229002290, ZO0674406744\\",118,118,2,2,order,abigail", + "6QMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Boris,Boris,\\"Boris Greene\\",\\"Boris Greene\\",MALE,36,Greene,Greene,\\"(empty)\\",Saturday,5,\\"boris@greene-family.zzz\\",\\"-\\",Europe,GB,\\"{", + " \\"\\"coordinates\\"\\": [", + " -0.1,", + " 51.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"-\\",\\"Elitelligence, Spritechnologies\\",\\"Elitelligence, Spritechnologies\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591133,\\"sold_product_591133_19496, sold_product_591133_20143\\",\\"sold_product_591133_19496, sold_product_591133_20143\\",\\"24.984, 10.992\\",\\"24.984, 10.992\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Spritechnologies\\",\\"Elitelligence, Spritechnologies\\",\\"12.742, 5.711\\",\\"24.984, 10.992\\",\\"19,496, 20,143\\",\\"Tracksuit bottoms - mottled grey, Sports shirt - black\\",\\"Tracksuit bottoms - mottled grey, Sports shirt - black\\",\\"1, 1\\",\\"ZO0529905299, ZO0617006170\\",\\"0, 0\\",\\"24.984, 10.992\\",\\"24.984, 10.992\\",\\"0, 0\\",\\"ZO0529905299, ZO0617006170\\",\\"35.969\\",\\"35.969\\",2,2,order,boris", + "6gMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Jackson,Jackson,\\"Jackson Conner\\",\\"Jackson Conner\\",MALE,13,Conner,Conner,\\"(empty)\\",Saturday,5,\\"jackson@conner-family.zzz\\",\\"Los Angeles\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -118.2,", + " 34.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",California,\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591175,\\"sold_product_591175_23703, sold_product_591175_11555\\",\\"sold_product_591175_23703, sold_product_591175_11555\\",\\"28.984, 65\\",\\"28.984, 65\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"13.922, 31.844\\",\\"28.984, 65\\",\\"23,703, 11,555\\",\\"Sweatshirt - grey multicolor, Short coat - dark blue\\",\\"Sweatshirt - grey multicolor, Short coat - dark blue\\",\\"1, 1\\",\\"ZO0299402994, ZO0433504335\\",\\"0, 0\\",\\"28.984, 65\\",\\"28.984, 65\\",\\"0, 0\\",\\"ZO0299402994, ZO0433504335\\",94,94,2,2,order,jackson", + "7QMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Yuri,Yuri,\\"Yuri Cross\\",\\"Yuri Cross\\",MALE,21,Cross,Cross,\\"(empty)\\",Saturday,5,\\"yuri@cross-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591297,\\"sold_product_591297_21234, sold_product_591297_17466\\",\\"sold_product_591297_21234, sold_product_591297_17466\\",\\"75, 28.984\\",\\"75, 28.984\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"36.75, 13.344\\",\\"75, 28.984\\",\\"21,234, 17,466\\",\\"Lace-up boots - brown, Jumper - multicoloured\\",\\"Lace-up boots - brown, Jumper - multicoloured\\",\\"1, 1\\",\\"ZO0257502575, ZO0451704517\\",\\"0, 0\\",\\"75, 28.984\\",\\"75, 28.984\\",\\"0, 0\\",\\"ZO0257502575, ZO0451704517\\",104,104,2,2,order,yuri", + "7gMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Irwin,Irwin,\\"Irwin Ramsey\\",\\"Irwin Ramsey\\",MALE,14,Ramsey,Ramsey,\\"(empty)\\",Saturday,5,\\"irwin@ramsey-family.zzz\\",Bogotu00e1,\\"South America\\",CO,\\"{", +] `; -exports[`discover Discover CSV Export Generate CSV: archived search generates a report with filtered data 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,45,591503,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0006400064, ZO0150601506\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,12,591709,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0638206382, ZO0038800388\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,52,590937,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0297602976, ZO0565605656\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,29,590976,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0561405614, ZO0281602816\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,41,591598,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0276702767, ZO0291702917\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,44,590927,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0046600466, ZO0050800508\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,46,591299,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0229002290, ZO0674406744\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,36,591133,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0529905299, ZO0617006170\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,13,591175,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0299402994, ZO0433504335\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,14,591149,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0584905849, ZO0578405784\\" -\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,27,591754,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0335803358, ZO0325903259\\" -" +exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: default 2`] = ` +Array [ + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Dubai,\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale\\",\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale\\",\\"Jun 12, 2019 @ 00:00:00.000\\",731056,\\"sold_product_731056_22440, sold_product_731056_13969, sold_product_731056_20215, sold_product_731056_23401\\",\\"sold_product_731056_22440, sold_product_731056_13969, sold_product_731056_20215, sold_product_731056_23401\\",\\"33, 20.984, 75, 13.992\\",\\"33, 20.984, 75, 13.992\\",\\"Women's Clothing, Women's Accessories, Women's Shoes, Women's Accessories\\",\\"Women's Clothing, Women's Accessories, Women's Shoes, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale, Pyramidustries\\",\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale, Pyramidustries\\",\\"15.18, 10.289, 39, 6.719\\",\\"33, 20.984, 75, 13.992\\",\\"22,440, 13,969, 20,215, 23,401\\",\\"Jersey dress - fan/black, Across body bag - Blue Violety, Boots - black, Hat - nude\\",\\"Jersey dress - fan/black, Across body bag - Blue Violety, Boots - black, Hat - nude\\",\\"1, 1, 1, 1\\",\\"ZO0230102301, ZO0210602106, ZO0679006790, ZO0187301873\\",\\"0, 0, 0, 0\\",\\"33, 20.984, 75, 13.992\\",\\"33, 20.984, 75, 13.992\\",\\"0, 0, 0, 0\\",\\"ZO0230102301, ZO0210602106, ZO0679006790, ZO0187301873\\",143,143,4,4,order,rabbia", + "4AMtOW0BH63Xcmy453H9,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Yahya,Yahya,\\"Yahya Pope\\",\\"Yahya Pope\\",MALE,23,Pope,Pope,\\"(empty)\\",Thursday,3,\\"yahya@pope-family.zzz\\",Marrakesh,Africa,MA,\\"{", + " \\"\\"coordinates\\"\\": [", + " -8,", + " 31.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Marrakech-Tensift-Al Haouz\\",Elitelligence,Elitelligence,\\"Jun 12, 2019 @ 00:00:00.000\\",551556,\\"sold_product_551556_1583, sold_product_551556_11991\\",\\"sold_product_551556_1583, sold_product_551556_11991\\",\\"33, 7.988\\",\\"33, 7.988\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Elitelligence\\",\\"Elitelligence, Elitelligence\\",\\"17.813, 3.76\\",\\"33, 7.988\\",\\"1,583, 11,991\\",\\"High-top trainers - black, Basic T-shirt - black\\",\\"High-top trainers - black, Basic T-shirt - black\\",\\"1, 1\\",\\"ZO0512405124, ZO0551405514\\",\\"0, 0\\",\\"33, 7.988\\",\\"33, 7.988\\",\\"0, 0\\",\\"ZO0512405124, ZO0551405514\\",\\"40.969\\",\\"40.969\\",2,2,order,yahya", + "4QMtOW0BH63Xcmy453H9,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,George,George,\\"George Morrison\\",\\"George Morrison\\",MALE,32,Morrison,Morrison,\\"(empty)\\",Thursday,3,\\"george@morrison-family.zzz\\",Birmingham,Europe,GB,\\"{", + " \\"\\"coordinates\\"\\": [", + " -1.9,", + " 52.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Birmingham,\\"Spritechnologies, Elitelligence\\",\\"Spritechnologies, Elitelligence\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551609,\\"sold_product_551609_3728, sold_product_551609_23435\\",\\"sold_product_551609_3728, sold_product_551609_23435\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Spritechnologies, Elitelligence\\",\\"Spritechnologies, Elitelligence\\",\\"10.703, 10.289\\",\\"20.984, 20.984\\",\\"3,728, 23,435\\",\\"Base layer - khaki, Shirt - red\\",\\"Base layer - khaki, Shirt - red\\",\\"1, 1\\",\\"ZO0630606306, ZO0524705247\\",\\"0, 0\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"0, 0\\",\\"ZO0630606306, ZO0524705247\\",\\"41.969\\",\\"41.969\\",2,2,order,george", + "\\"_wMtOW0BH63Xcmy453H9\\",ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Yuri,Yuri,\\"Yuri Morris\\",\\"Yuri Morris\\",MALE,21,Morris,Morris,\\"(empty)\\",Thursday,3,\\"yuri@morris-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Angeldale, Low Tide Media\\",\\"Angeldale, Low Tide Media\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550473,\\"sold_product_550473_20161, sold_product_550473_6991\\",\\"sold_product_550473_20161, sold_product_550473_6991\\",\\"65, 38\\",\\"65, 38\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Angeldale, Low Tide Media\\",\\"Angeldale, Low Tide Media\\",\\"30.547, 20.125\\",\\"65, 38\\",\\"20,161, 6,991\\",\\"Lace-up boots - dark tan, Jumper - dark blue\\",\\"Lace-up boots - dark tan, Jumper - dark blue\\",\\"1, 1\\",\\"ZO0688006880, ZO0450504505\\",\\"0, 0\\",\\"65, 38\\",\\"65, 38\\",\\"0, 0\\",\\"ZO0688006880, ZO0450504505\\",103,103,2,2,order,yuri", + "AAMtOW0BH63Xcmy453L9,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",EUR,Brigitte,Brigitte,\\"Brigitte Long\\",\\"Brigitte Long\\",FEMALE,12,Long,Long,\\"(empty)\\",Thursday,3,\\"brigitte@long-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Pyramidustries,Pyramidustries,\\"Jun 12, 2019 @ 00:00:00.000\\",550542,\\"sold_product_550542_11839, sold_product_550542_21052\\",\\"sold_product_550542_11839, sold_product_550542_21052\\",\\"24.984, 9.992\\",\\"24.984, 9.992\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries\\",\\"Pyramidustries, Pyramidustries\\",\\"12.992, 4.898\\",\\"24.984, 9.992\\",\\"11,839, 21,052\\",\\"High heeled sandals - cognac, Snood - black/red/green/yellow\\",\\"High heeled sandals - cognac, Snood - black/red/green/yellow\\",\\"1, 1\\",\\"ZO0137301373, ZO0192601926\\",\\"0, 0\\",\\"24.984, 9.992\\",\\"24.984, 9.992\\",\\"0, 0\\",\\"ZO0137301373, ZO0192601926\\",\\"34.969\\",\\"34.969\\",2,2,order,brigitte", + "AQMtOW0BH63Xcmy453L9,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",EUR,rania,rania,\\"rania Barnes\\",\\"rania Barnes\\",FEMALE,24,Barnes,Barnes,\\"(empty)\\",Thursday,3,\\"rania@barnes-family.zzz\\",Cairo,Africa,EG,\\"{", + " \\"\\"coordinates\\"\\": [", + " 31.3,", + " 30.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Cairo Governorate\\",\\"Pyramidustries, Pyramidustries active\\",\\"Pyramidustries, Pyramidustries active\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550580,\\"sold_product_550580_20312, sold_product_550580_10155\\",\\"sold_product_550580_20312, sold_product_550580_10155\\",\\"50, 16.984\\",\\"50, 16.984\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries active\\",\\"Pyramidustries, Pyramidustries active\\",\\"24, 8.656\\",\\"50, 16.984\\",\\"20,312, 10,155\\",\\"Lace-up boots - cognac, Sports shirt - black\\",\\"Lace-up boots - cognac, Sports shirt - black\\",\\"1, 1\\",\\"ZO0144801448, ZO0219602196\\",\\"0, 0\\",\\"50, 16.984\\",\\"50, 16.984\\",\\"0, 0\\",\\"ZO0144801448, ZO0219602196\\",67,67,2,2,order,rani", + "BwMtOW0BH63Xcmy453L9,ecommerce,\\"-\\",\\"-\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",EUR,\\"Abdulraheem Al\\",\\"Abdulraheem Al\\",\\"Abdulraheem Al Tyler\\",\\"Abdulraheem Al Tyler\\",MALE,33,Tyler,Tyler,\\"(empty)\\",Thursday,3,\\"abdulraheem al@tyler-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{", + " \\"\\"coordinates\\"\\": [", + " 54.4,", + " 24.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Abu Dhabi\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551324,\\"sold_product_551324_14742, sold_product_551324_19089\\",\\"sold_product_551324_14742, sold_product_551324_19089\\",\\"33, 12.992\\",\\"33, 12.992\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"15.18, 7.012\\",\\"33, 12.992\\",\\"14,742, 19,089\\",\\"Laptop bag - brown, Vest - white/dark blue\\",\\"Laptop bag - brown, Vest - white/dark blue\\",\\"1, 1\\",\\"ZO0316803168, ZO0566905669\\",\\"0, 0\\",\\"33, 12.992\\",\\"33, 12.992\\",\\"0, 0\\",\\"ZO0316803168, ZO0566905669\\",\\"45.969\\",\\"45.969\\",2,2,order,abdulraheem", + "mwMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",EUR,Marwan,Marwan,\\"Marwan James\\",\\"Marwan James\\",MALE,51,James,James,\\"(empty)\\",Thursday,3,\\"marwan@james-family.zzz\\",Marrakesh,Africa,MA,\\"{", + " \\"\\"coordinates\\"\\": [", + " -8,", + " 31.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Marrakech-Tensift-Al Haouz\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551355,\\"sold_product_551355_21057, sold_product_551355_23405\\",\\"sold_product_551355_21057, sold_product_551355_23405\\",\\"13.992, 11.992\\",\\"13.992, 11.992\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"6.859, 5.762\\",\\"13.992, 11.992\\",\\"21,057, 23,405\\",\\"Scarf - navy/grey, Basic T-shirt - blue\\",\\"Scarf - navy/grey, Basic T-shirt - blue\\",\\"1, 1\\",\\"ZO0313403134, ZO0561205612\\",\\"0, 0\\",\\"13.992, 11.992\\",\\"13.992, 11.992\\",\\"0, 0\\",\\"ZO0313403134, ZO0561205612\\",\\"25.984\\",\\"25.984\\",2,2,order,marwan", + "twMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",EUR,Sonya,Sonya,\\"Sonya Mccormick\\",\\"Sonya Mccormick\\",FEMALE,28,Mccormick,Mccormick,\\"(empty)\\",Thursday,3,\\"sonya@mccormick-family.zzz\\",Bogotu00e1,\\"South America\\",CO,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74.1,", + " 4.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Bogota D.C.\\",Pyramidustries,Pyramidustries,\\"Jun 12, 2019 @ 00:00:00.000\\",550957,\\"sold_product_550957_22980, sold_product_550957_19828\\",\\"sold_product_550957_22980, sold_product_550957_19828\\",\\"24.984, 16.984\\",\\"24.984, 16.984\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries\\",\\"Pyramidustries, Pyramidustries\\",\\"11.5, 9.172\\",\\"24.984, 16.984\\",\\"22,980, 19,828\\",\\"Classic heels - petrol, Watch - nude\\",\\"Classic heels - petrol, Watch - nude\\",\\"1, 1\\",\\"ZO0133101331, ZO0189401894\\",\\"0, 0\\",\\"24.984, 16.984\\",\\"24.984, 16.984\\",\\"0, 0\\",\\"ZO0133101331, ZO0189401894\\",\\"41.969\\",\\"41.969\\",2,2,order,sonya", + "7AMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",EUR,Kamal,Kamal,\\"Kamal Hansen\\",\\"Kamal Hansen\\",MALE,39,Hansen,Hansen,\\"(empty)\\",Thursday,3,\\"kamal@hansen-family.zzz\\",Istanbul,Asia,TR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 29,", + " 41", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Istanbul,\\"Low Tide Media, Spritechnologies\\",\\"Low Tide Media, Spritechnologies\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551154,\\"sold_product_551154_13181, sold_product_551154_23660\\",\\"sold_product_551154_13181, sold_product_551154_23660\\",\\"42, 11.992\\",\\"42, 11.992\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Spritechnologies\\",\\"Low Tide Media, Spritechnologies\\",\\"21, 6.109\\",\\"42, 11.992\\",\\"13,181, 23,660\\",\\"Briefcase - navy, Sports shirt - Seashell\\",\\"Briefcase - navy, Sports shirt - Seashell\\",\\"1, 1\\",\\"ZO0466704667, ZO0617306173\\",\\"0, 0\\",\\"42, 11.992\\",\\"42, 11.992\\",\\"0, 0\\",\\"ZO0466704667, ZO0617306173\\",\\"53.969\\",\\"53.969\\",2,2,order,kamal", + "7QMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing, Women's Accessories\\",\\"Women's Clothing, Women's Accessories\\",EUR,Elyssa,Elyssa,\\"Elyssa Graves\\",\\"Elyssa Graves\\",FEMALE,27,Graves,Graves,\\"(empty)\\",Thursday,3,\\"elyssa@graves-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Pyramidustries,Pyramidustries,\\"Jun 12, 2019 @ 00:00:00.000\\",551204,\\"sold_product_551204_16805, sold_product_551204_12896\\",\\"sold_product_551204_16805, sold_product_551204_12896\\",\\"13.992, 20.984\\",\\"13.992, 20.984\\",\\"Women's Clothing, Women's Accessories\\",\\"Women's Clothing, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries\\",\\"Pyramidustries, Pyramidustries\\",\\"7.129, 9.656\\",\\"13.992, 20.984\\",\\"16,805, 12,896\\",\\"Bustier - white, Across body bag - cognac\\",\\"Bustier - white, Across body bag - cognac\\",\\"1, 1\\",\\"ZO0212602126, ZO0200702007\\",\\"0, 0\\",\\"13.992, 20.984\\",\\"13.992, 20.984\\",\\"0, 0\\",\\"ZO0212602126, ZO0200702007\\",\\"34.969\\",\\"34.969\\",2,2,order,elyssa", + "7gMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",EUR,Pia,Pia,\\"Pia Rose\\",\\"Pia Rose\\",FEMALE,45,Rose,Rose,\\"(empty)\\",Thursday,3,\\"pia@rose-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Oceanavigations, Primemaster\\",\\"Oceanavigations, Primemaster\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550466,\\"sold_product_550466_19198, sold_product_550466_16409\\",\\"sold_product_550466_19198, sold_product_550466_16409\\",\\"50, 100\\",\\"50, 100\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Primemaster\\",\\"Oceanavigations, Primemaster\\",\\"24, 52\\",\\"50, 100\\",\\"19,198, 16,409\\",\\"Summer dress - grey, Boots - passion\\",\\"Summer dress - grey, Boots - passion\\",\\"1, 1\\",\\"ZO0260702607, ZO0363203632\\",\\"0, 0\\",\\"50, 100\\",\\"50, 100\\",\\"0, 0\\",\\"ZO0260702607, ZO0363203632\\",150,150,2,2,order,pia", + "7wMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Wagdi,Wagdi,\\"Wagdi Boone\\",\\"Wagdi Boone\\",MALE,15,Boone,Boone,\\"(empty)\\",Thursday,3,\\"wagdi@boone-family.zzz\\",\\"-\\",Asia,SA,\\"{", + " \\"\\"coordinates\\"\\": [", + " 45,", + " 25", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"-\\",Elitelligence,Elitelligence,\\"Jun 12, 2019 @ 00:00:00.000\\",550503,\\"sold_product_550503_13211, sold_product_550503_24369\\",\\"sold_product_550503_13211, sold_product_550503_24369\\",\\"34, 11.992\\",\\"34, 11.992\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Elitelligence\\",\\"Elitelligence, Elitelligence\\",\\"15.641, 6.109\\",\\"34, 11.992\\",\\"13,211, 24,369\\",\\"Tracksuit top - black, Print T-shirt - khaki\\",\\"Tracksuit top - black, Print T-shirt - khaki\\",\\"1, 1\\",\\"ZO0587505875, ZO0566405664\\",\\"0, 0\\",\\"34, 11.992\\",\\"34, 11.992\\",\\"0, 0\\",\\"ZO0587505875, ZO0566405664\\",\\"45.969\\",\\"45.969\\",2,2,order,wagdi", + "8AMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Accessories, Women's Shoes\\",\\"Women's Accessories, Women's Shoes\\",EUR,Elyssa,Elyssa,\\"Elyssa Hale\\",\\"Elyssa Hale\\",FEMALE,27,Hale,Hale,\\"(empty)\\",Thursday,3,\\"elyssa@hale-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Angeldale, Oceanavigations\\",\\"Angeldale, Oceanavigations\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550538,\\"sold_product_550538_15047, sold_product_550538_18189\\",\\"sold_product_550538_15047, sold_product_550538_18189\\",\\"75, 60\\",\\"75, 60\\",\\"Women's Accessories, Women's Shoes\\",\\"Women's Accessories, Women's Shoes\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Angeldale, Oceanavigations\\",\\"Angeldale, Oceanavigations\\",\\"34.5, 28.797\\",\\"75, 60\\",\\"15,047, 18,189\\",\\"Handbag - black, Ankle boots - grey\\",\\"Handbag - black, Ankle boots - grey\\",\\"1, 1\\",\\"ZO0699406994, ZO0246202462\\",\\"0, 0\\",\\"75, 60\\",\\"75, 60\\",\\"0, 0\\",\\"ZO0699406994, ZO0246202462\\",135,135,2,2,order,elyssa", + "8QMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Jackson,Jackson,\\"Jackson Love\\",\\"Jackson Love\\",MALE,13,Love,Love,\\"(empty)\\",Thursday,3,\\"jackson@love-family.zzz\\",\\"Los Angeles\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -118.2,", + " 34.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",California,\\"Low Tide Media\\",\\"Low Tide Media\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550568,\\"sold_product_550568_17210, sold_product_550568_12524\\",\\"sold_product_550568_17210, sold_product_550568_12524\\",\\"50, 24.984\\",\\"50, 24.984\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Low Tide Media\\",\\"Low Tide Media, Low Tide Media\\",\\"25, 12.492\\",\\"50, 24.984\\",\\"17,210, 12,524\\",\\"Casual lace-ups - navy, Jumper - dark grey multicolor\\",\\"Casual lace-ups - navy, Jumper - dark grey multicolor\\",\\"1, 1\\",\\"ZO0388403884, ZO0447604476\\",\\"0, 0\\",\\"50, 24.984\\",\\"50, 24.984\\",\\"0, 0\\",\\"ZO0388403884, ZO0447604476\\",75,75,2,2,order,jackson", +] `; -exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: default 1`] = ` -"\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user -3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,\\"(empty)\\",Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{ - \\"\\"coordinates\\"\\": [ - 54.4, - 24.5 - ], - \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.375, 33, 10.344, 6.109\\",\\"80, 60, 21.984, 11.992\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",174,174,4,4,order,sultan -" +exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: discover:searchFieldsFromSource 1`] = ` +Array [ + "\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user", + "3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,\\"(empty)\\",Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{", + " \\"\\"coordinates\\"\\": [", + " 54.4,", + " 24.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.375, 33, 10.344, 6.109\\",\\"80, 60, 21.984, 11.992\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",174,174,4,4,order,sultan", + "9gMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",EUR,Pia,Pia,\\"Pia Richards\\",\\"Pia Richards\\",FEMALE,45,Richards,Richards,\\"(empty)\\",Saturday,5,\\"pia@richards-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591503,\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"10.703, 9.867\\",\\"20.984, 20.984\\",\\"14,761, 11,632\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"1, 1\\",\\"ZO0006400064, ZO0150601506\\",\\"0, 0\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"0, 0\\",\\"ZO0006400064, ZO0150601506\\",\\"41.969\\",\\"41.969\\",2,2,order,pia", + "BgMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing\\",\\"Women's Clothing\\",EUR,Brigitte,Brigitte,\\"Brigitte Meyer\\",\\"Brigitte Meyer\\",FEMALE,12,Meyer,Meyer,\\"(empty)\\",Saturday,5,\\"brigitte@meyer-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591709,\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"7.988, 33\\",\\"7.988, 33\\",\\"Women's Clothing, Women's Clothing\\",\\"Women's Clothing, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"3.6, 17.484\\",\\"7.988, 33\\",\\"20,734, 7,539\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"1, 1\\",\\"ZO0638206382, ZO0038800388\\",\\"0, 0\\",\\"7.988, 33\\",\\"7.988, 33\\",\\"0, 0\\",\\"ZO0638206382, ZO0038800388\\",\\"40.969\\",\\"40.969\\",2,2,order,brigitte", + "KQMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Abd,Abd,\\"Abd Mccarthy\\",\\"Abd Mccarthy\\",MALE,52,Mccarthy,Mccarthy,\\"(empty)\\",Saturday,5,\\"abd@mccarthy-family.zzz\\",Cairo,Africa,EG,\\"{", + " \\"\\"coordinates\\"\\": [", + " 31.3,", + " 30.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Cairo Governorate\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590937,\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"28.984, 12.992\\",\\"28.984, 12.992\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"13.344, 6.109\\",\\"28.984, 12.992\\",\\"14,438, 23,607\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"1, 1\\",\\"ZO0297602976, ZO0565605656\\",\\"0, 0\\",\\"28.984, 12.992\\",\\"28.984, 12.992\\",\\"0, 0\\",\\"ZO0297602976, ZO0565605656\\",\\"41.969\\",\\"41.969\\",2,2,order,abd", + "KgMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Robert,Robert,\\"Robert Banks\\",\\"Robert Banks\\",MALE,29,Banks,Banks,\\"(empty)\\",Saturday,5,\\"robert@banks-family.zzz\\",\\"-\\",Asia,SA,\\"{", + " \\"\\"coordinates\\"\\": [", + " 45,", + " 25", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"-\\",\\"Elitelligence, Oceanavigations\\",\\"Elitelligence, Oceanavigations\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590976,\\"sold_product_590976_21270, sold_product_590976_13220\\",\\"sold_product_590976_21270, sold_product_590976_13220\\",\\"11.992, 24.984\\",\\"11.992, 24.984\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Oceanavigations\\",\\"Elitelligence, Oceanavigations\\",\\"6.23, 12.25\\",\\"11.992, 24.984\\",\\"21,270, 13,220\\",\\"Print T-shirt - white, Chinos - dark grey\\",\\"Print T-shirt - white, Chinos - dark grey\\",\\"1, 1\\",\\"ZO0561405614, ZO0281602816\\",\\"0, 0\\",\\"11.992, 24.984\\",\\"11.992, 24.984\\",\\"0, 0\\",\\"ZO0561405614, ZO0281602816\\",\\"36.969\\",\\"36.969\\",2,2,order,robert", + "awMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Jim,Jim,\\"Jim Hansen\\",\\"Jim Hansen\\",MALE,41,Hansen,Hansen,\\"(empty)\\",Saturday,5,\\"jim@hansen-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Low Tide Media\\",\\"Low Tide Media\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591636,\\"sold_product_591636_1295, sold_product_591636_6498\\",\\"sold_product_591636_1295, sold_product_591636_6498\\",\\"50, 50\\",\\"50, 50\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Low Tide Media\\",\\"Low Tide Media, Low Tide Media\\",\\"25, 27.484\\",\\"50, 50\\",\\"1,295, 6,498\\",\\"Smart lace-ups - dark brown, Suit jacket - dark blue\\",\\"Smart lace-ups - dark brown, Suit jacket - dark blue\\",\\"1, 1\\",\\"ZO0385003850, ZO0408604086\\",\\"0, 0\\",\\"50, 50\\",\\"50, 50\\",\\"0, 0\\",\\"ZO0385003850, ZO0408604086\\",100,100,2,2,order,jim", + "eQMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes\\",\\"Men's Shoes\\",EUR,Thad,Thad,\\"Thad Lamb\\",\\"Thad Lamb\\",MALE,30,Lamb,Lamb,\\"(empty)\\",Saturday,5,\\"thad@lamb-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Elitelligence,Elitelligence,\\"Jul 12, 2019 @ 00:00:00.000\\",591539,\\"sold_product_591539_15260, sold_product_591539_14221\\",\\"sold_product_591539_15260, sold_product_591539_14221\\",\\"20.984, 18.984\\",\\"20.984, 18.984\\",\\"Men's Shoes, Men's Shoes\\",\\"Men's Shoes, Men's Shoes\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Elitelligence\\",\\"Elitelligence, Elitelligence\\",\\"10.078, 10.25\\",\\"20.984, 18.984\\",\\"15,260, 14,221\\",\\"Casual lace-ups - dark blue, Trainers - navy\\",\\"Casual lace-ups - dark blue, Trainers - navy\\",\\"1, 1\\",\\"ZO0505605056, ZO0513605136\\",\\"0, 0\\",\\"20.984, 18.984\\",\\"20.984, 18.984\\",\\"0, 0\\",\\"ZO0505605056, ZO0513605136\\",\\"39.969\\",\\"39.969\\",2,2,order,thad", + "egMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Jim,Jim,\\"Jim Bryant\\",\\"Jim Bryant\\",MALE,41,Bryant,Bryant,\\"(empty)\\",Saturday,5,\\"jim@bryant-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Oceanavigations,Oceanavigations,\\"Jul 12, 2019 @ 00:00:00.000\\",591598,\\"sold_product_591598_20597, sold_product_591598_2774\\",\\"sold_product_591598_20597, sold_product_591598_2774\\",\\"10.992, 85\\",\\"10.992, 85\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Oceanavigations\\",\\"Oceanavigations, Oceanavigations\\",\\"5.93, 45.875\\",\\"10.992, 85\\",\\"20,597, 2,774\\",\\"Tie - brown/black , Classic coat - black\\",\\"Tie - brown/black , Classic coat - black\\",\\"1, 1\\",\\"ZO0276702767, ZO0291702917\\",\\"0, 0\\",\\"10.992, 85\\",\\"10.992, 85\\",\\"0, 0\\",\\"ZO0276702767, ZO0291702917\\",96,96,2,2,order,jim", + "fwMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing\\",\\"Women's Clothing\\",EUR,Betty,Betty,\\"Betty Roberson\\",\\"Betty Roberson\\",FEMALE,44,Roberson,Roberson,\\"(empty)\\",Saturday,5,\\"betty@roberson-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.7", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Tigress Enterprises\\",\\"Tigress Enterprises\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590927,\\"sold_product_590927_15436, sold_product_590927_22598\\",\\"sold_product_590927_15436, sold_product_590927_22598\\",\\"28.984, 28.984\\",\\"28.984, 28.984\\",\\"Women's Clothing, Women's Clothing\\",\\"Women's Clothing, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises, Tigress Enterprises\\",\\"Tigress Enterprises, Tigress Enterprises\\",\\"14.781, 14.781\\",\\"28.984, 28.984\\",\\"15,436, 22,598\\",\\"Jersey dress - anthra/black, Shift dress - black\\",\\"Jersey dress - anthra/black, Shift dress - black\\",\\"1, 1\\",\\"ZO0046600466, ZO0050800508\\",\\"0, 0\\",\\"28.984, 28.984\\",\\"28.984, 28.984\\",\\"0, 0\\",\\"ZO0046600466, ZO0050800508\\",\\"57.969\\",\\"57.969\\",2,2,order,betty", + "gAMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing, Men's Shoes\\",\\"Men's Clothing, Men's Shoes\\",EUR,Robbie,Robbie,\\"Robbie Hubbard\\",\\"Robbie Hubbard\\",MALE,48,Hubbard,Hubbard,\\"(empty)\\",Saturday,5,\\"robbie@hubbard-family.zzz\\",Dubai,Asia,AE,\\"{", + " \\"\\"coordinates\\"\\": [", + " 55.3,", + " 25.3", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Dubai,\\"Low Tide Media, Angeldale\\",\\"Low Tide Media, Angeldale\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590970,\\"sold_product_590970_11228, sold_product_590970_12060\\",\\"sold_product_590970_11228, sold_product_590970_12060\\",\\"24.984, 50\\",\\"24.984, 50\\",\\"Men's Clothing, Men's Shoes\\",\\"Men's Clothing, Men's Shoes\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Angeldale\\",\\"Low Tide Media, Angeldale\\",\\"12, 25.984\\",\\"24.984, 50\\",\\"11,228, 12,060\\",\\"Tracksuit top - offwhite multicolor, Lace-ups - black/red\\",\\"Tracksuit top - offwhite multicolor, Lace-ups - black/red\\",\\"1, 1\\",\\"ZO0455604556, ZO0680806808\\",\\"0, 0\\",\\"24.984, 50\\",\\"24.984, 50\\",\\"0, 0\\",\\"ZO0455604556, ZO0680806808\\",75,75,2,2,order,robbie", + "6AMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",EUR,Abigail,Abigail,\\"Abigail Willis\\",\\"Abigail Willis\\",FEMALE,46,Willis,Willis,\\"(empty)\\",Saturday,5,\\"abigail@willis-family.zzz\\",Birmingham,Europe,GB,\\"{", + " \\"\\"coordinates\\"\\": [", + " -1.9,", + " 52.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Birmingham,\\"Tigress Enterprises MAMA, Angeldale\\",\\"Tigress Enterprises MAMA, Angeldale\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591299,\\"sold_product_591299_19895, sold_product_591299_5787\\",\\"sold_product_591299_19895, sold_product_591299_5787\\",\\"33, 85\\",\\"33, 85\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises MAMA, Angeldale\\",\\"Tigress Enterprises MAMA, Angeldale\\",\\"17.156, 44.188\\",\\"33, 85\\",\\"19,895, 5,787\\",\\"Summer dress - black/offwhite, Ankle boots - black\\",\\"Summer dress - black/offwhite, Ankle boots - black\\",\\"1, 1\\",\\"ZO0229002290, ZO0674406744\\",\\"0, 0\\",\\"33, 85\\",\\"33, 85\\",\\"0, 0\\",\\"ZO0229002290, ZO0674406744\\",118,118,2,2,order,abigail", + "6QMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Boris,Boris,\\"Boris Greene\\",\\"Boris Greene\\",MALE,36,Greene,Greene,\\"(empty)\\",Saturday,5,\\"boris@greene-family.zzz\\",\\"-\\",Europe,GB,\\"{", + " \\"\\"coordinates\\"\\": [", + " -0.1,", + " 51.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"-\\",\\"Elitelligence, Spritechnologies\\",\\"Elitelligence, Spritechnologies\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591133,\\"sold_product_591133_19496, sold_product_591133_20143\\",\\"sold_product_591133_19496, sold_product_591133_20143\\",\\"24.984, 10.992\\",\\"24.984, 10.992\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Spritechnologies\\",\\"Elitelligence, Spritechnologies\\",\\"12.742, 5.711\\",\\"24.984, 10.992\\",\\"19,496, 20,143\\",\\"Tracksuit bottoms - mottled grey, Sports shirt - black\\",\\"Tracksuit bottoms - mottled grey, Sports shirt - black\\",\\"1, 1\\",\\"ZO0529905299, ZO0617006170\\",\\"0, 0\\",\\"24.984, 10.992\\",\\"24.984, 10.992\\",\\"0, 0\\",\\"ZO0529905299, ZO0617006170\\",\\"35.969\\",\\"35.969\\",2,2,order,boris", + "6gMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Jackson,Jackson,\\"Jackson Conner\\",\\"Jackson Conner\\",MALE,13,Conner,Conner,\\"(empty)\\",Saturday,5,\\"jackson@conner-family.zzz\\",\\"Los Angeles\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -118.2,", + " 34.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",California,\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591175,\\"sold_product_591175_23703, sold_product_591175_11555\\",\\"sold_product_591175_23703, sold_product_591175_11555\\",\\"28.984, 65\\",\\"28.984, 65\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"13.922, 31.844\\",\\"28.984, 65\\",\\"23,703, 11,555\\",\\"Sweatshirt - grey multicolor, Short coat - dark blue\\",\\"Sweatshirt - grey multicolor, Short coat - dark blue\\",\\"1, 1\\",\\"ZO0299402994, ZO0433504335\\",\\"0, 0\\",\\"28.984, 65\\",\\"28.984, 65\\",\\"0, 0\\",\\"ZO0299402994, ZO0433504335\\",94,94,2,2,order,jackson", + "7QMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Yuri,Yuri,\\"Yuri Cross\\",\\"Yuri Cross\\",MALE,21,Cross,Cross,\\"(empty)\\",Saturday,5,\\"yuri@cross-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591297,\\"sold_product_591297_21234, sold_product_591297_17466\\",\\"sold_product_591297_21234, sold_product_591297_17466\\",\\"75, 28.984\\",\\"75, 28.984\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Low Tide Media\\",\\"Oceanavigations, Low Tide Media\\",\\"36.75, 13.344\\",\\"75, 28.984\\",\\"21,234, 17,466\\",\\"Lace-up boots - brown, Jumper - multicoloured\\",\\"Lace-up boots - brown, Jumper - multicoloured\\",\\"1, 1\\",\\"ZO0257502575, ZO0451704517\\",\\"0, 0\\",\\"75, 28.984\\",\\"75, 28.984\\",\\"0, 0\\",\\"ZO0257502575, ZO0451704517\\",104,104,2,2,order,yuri", + "7gMtOW0BH63Xcmy44WNv,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Irwin,Irwin,\\"Irwin Ramsey\\",\\"Irwin Ramsey\\",MALE,14,Ramsey,Ramsey,\\"(empty)\\",Saturday,5,\\"irwin@ramsey-family.zzz\\",Bogotu00e1,\\"South America\\",CO,\\"{", +] `; -exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: discover:searchFieldsFromSource 1`] = ` -"\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user -3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,\\"(empty)\\",Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{ - \\"\\"coordinates\\"\\": [ - 54.4, - 24.5 - ], - \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.375, 33, 10.344, 6.109\\",\\"80, 60, 21.984, 11.992\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",174,174,4,4,order,sultan -" +exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: discover:searchFieldsFromSource 2`] = ` +Array [ + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Dubai,\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale\\",\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale\\",\\"Jun 12, 2019 @ 00:00:00.000\\",731056,\\"sold_product_731056_22440, sold_product_731056_13969, sold_product_731056_20215, sold_product_731056_23401\\",\\"sold_product_731056_22440, sold_product_731056_13969, sold_product_731056_20215, sold_product_731056_23401\\",\\"33, 20.984, 75, 13.992\\",\\"33, 20.984, 75, 13.992\\",\\"Women's Clothing, Women's Accessories, Women's Shoes, Women's Accessories\\",\\"Women's Clothing, Women's Accessories, Women's Shoes, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale, Pyramidustries\\",\\"Tigress Enterprises MAMA, Pyramidustries, Angeldale, Pyramidustries\\",\\"15.18, 10.289, 39, 6.719\\",\\"33, 20.984, 75, 13.992\\",\\"22,440, 13,969, 20,215, 23,401\\",\\"Jersey dress - fan/black, Across body bag - Blue Violety, Boots - black, Hat - nude\\",\\"Jersey dress - fan/black, Across body bag - Blue Violety, Boots - black, Hat - nude\\",\\"1, 1, 1, 1\\",\\"ZO0230102301, ZO0210602106, ZO0679006790, ZO0187301873\\",\\"0, 0, 0, 0\\",\\"33, 20.984, 75, 13.992\\",\\"33, 20.984, 75, 13.992\\",\\"0, 0, 0, 0\\",\\"ZO0230102301, ZO0210602106, ZO0679006790, ZO0187301873\\",143,143,4,4,order,rabbia", + "4AMtOW0BH63Xcmy453H9,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Yahya,Yahya,\\"Yahya Pope\\",\\"Yahya Pope\\",MALE,23,Pope,Pope,\\"(empty)\\",Thursday,3,\\"yahya@pope-family.zzz\\",Marrakesh,Africa,MA,\\"{", + " \\"\\"coordinates\\"\\": [", + " -8,", + " 31.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Marrakech-Tensift-Al Haouz\\",Elitelligence,Elitelligence,\\"Jun 12, 2019 @ 00:00:00.000\\",551556,\\"sold_product_551556_1583, sold_product_551556_11991\\",\\"sold_product_551556_1583, sold_product_551556_11991\\",\\"33, 7.988\\",\\"33, 7.988\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Elitelligence\\",\\"Elitelligence, Elitelligence\\",\\"17.813, 3.76\\",\\"33, 7.988\\",\\"1,583, 11,991\\",\\"High-top trainers - black, Basic T-shirt - black\\",\\"High-top trainers - black, Basic T-shirt - black\\",\\"1, 1\\",\\"ZO0512405124, ZO0551405514\\",\\"0, 0\\",\\"33, 7.988\\",\\"33, 7.988\\",\\"0, 0\\",\\"ZO0512405124, ZO0551405514\\",\\"40.969\\",\\"40.969\\",2,2,order,yahya", + "4QMtOW0BH63Xcmy453H9,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,George,George,\\"George Morrison\\",\\"George Morrison\\",MALE,32,Morrison,Morrison,\\"(empty)\\",Thursday,3,\\"george@morrison-family.zzz\\",Birmingham,Europe,GB,\\"{", + " \\"\\"coordinates\\"\\": [", + " -1.9,", + " 52.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Birmingham,\\"Spritechnologies, Elitelligence\\",\\"Spritechnologies, Elitelligence\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551609,\\"sold_product_551609_3728, sold_product_551609_23435\\",\\"sold_product_551609_3728, sold_product_551609_23435\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Spritechnologies, Elitelligence\\",\\"Spritechnologies, Elitelligence\\",\\"10.703, 10.289\\",\\"20.984, 20.984\\",\\"3,728, 23,435\\",\\"Base layer - khaki, Shirt - red\\",\\"Base layer - khaki, Shirt - red\\",\\"1, 1\\",\\"ZO0630606306, ZO0524705247\\",\\"0, 0\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"0, 0\\",\\"ZO0630606306, ZO0524705247\\",\\"41.969\\",\\"41.969\\",2,2,order,george", + "\\"_wMtOW0BH63Xcmy453H9\\",ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Yuri,Yuri,\\"Yuri Morris\\",\\"Yuri Morris\\",MALE,21,Morris,Morris,\\"(empty)\\",Thursday,3,\\"yuri@morris-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Angeldale, Low Tide Media\\",\\"Angeldale, Low Tide Media\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550473,\\"sold_product_550473_20161, sold_product_550473_6991\\",\\"sold_product_550473_20161, sold_product_550473_6991\\",\\"65, 38\\",\\"65, 38\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Angeldale, Low Tide Media\\",\\"Angeldale, Low Tide Media\\",\\"30.547, 20.125\\",\\"65, 38\\",\\"20,161, 6,991\\",\\"Lace-up boots - dark tan, Jumper - dark blue\\",\\"Lace-up boots - dark tan, Jumper - dark blue\\",\\"1, 1\\",\\"ZO0688006880, ZO0450504505\\",\\"0, 0\\",\\"65, 38\\",\\"65, 38\\",\\"0, 0\\",\\"ZO0688006880, ZO0450504505\\",103,103,2,2,order,yuri", + "AAMtOW0BH63Xcmy453L9,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",EUR,Brigitte,Brigitte,\\"Brigitte Long\\",\\"Brigitte Long\\",FEMALE,12,Long,Long,\\"(empty)\\",Thursday,3,\\"brigitte@long-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Pyramidustries,Pyramidustries,\\"Jun 12, 2019 @ 00:00:00.000\\",550542,\\"sold_product_550542_11839, sold_product_550542_21052\\",\\"sold_product_550542_11839, sold_product_550542_21052\\",\\"24.984, 9.992\\",\\"24.984, 9.992\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries\\",\\"Pyramidustries, Pyramidustries\\",\\"12.992, 4.898\\",\\"24.984, 9.992\\",\\"11,839, 21,052\\",\\"High heeled sandals - cognac, Snood - black/red/green/yellow\\",\\"High heeled sandals - cognac, Snood - black/red/green/yellow\\",\\"1, 1\\",\\"ZO0137301373, ZO0192601926\\",\\"0, 0\\",\\"24.984, 9.992\\",\\"24.984, 9.992\\",\\"0, 0\\",\\"ZO0137301373, ZO0192601926\\",\\"34.969\\",\\"34.969\\",2,2,order,brigitte", + "AQMtOW0BH63Xcmy453L9,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",EUR,rania,rania,\\"rania Barnes\\",\\"rania Barnes\\",FEMALE,24,Barnes,Barnes,\\"(empty)\\",Thursday,3,\\"rania@barnes-family.zzz\\",Cairo,Africa,EG,\\"{", + " \\"\\"coordinates\\"\\": [", + " 31.3,", + " 30.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Cairo Governorate\\",\\"Pyramidustries, Pyramidustries active\\",\\"Pyramidustries, Pyramidustries active\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550580,\\"sold_product_550580_20312, sold_product_550580_10155\\",\\"sold_product_550580_20312, sold_product_550580_10155\\",\\"50, 16.984\\",\\"50, 16.984\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries active\\",\\"Pyramidustries, Pyramidustries active\\",\\"24, 8.656\\",\\"50, 16.984\\",\\"20,312, 10,155\\",\\"Lace-up boots - cognac, Sports shirt - black\\",\\"Lace-up boots - cognac, Sports shirt - black\\",\\"1, 1\\",\\"ZO0144801448, ZO0219602196\\",\\"0, 0\\",\\"50, 16.984\\",\\"50, 16.984\\",\\"0, 0\\",\\"ZO0144801448, ZO0219602196\\",67,67,2,2,order,rani", + "BwMtOW0BH63Xcmy453L9,ecommerce,\\"-\\",\\"-\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",EUR,\\"Abdulraheem Al\\",\\"Abdulraheem Al\\",\\"Abdulraheem Al Tyler\\",\\"Abdulraheem Al Tyler\\",MALE,33,Tyler,Tyler,\\"(empty)\\",Thursday,3,\\"abdulraheem al@tyler-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{", + " \\"\\"coordinates\\"\\": [", + " 54.4,", + " 24.5", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Abu Dhabi\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551324,\\"sold_product_551324_14742, sold_product_551324_19089\\",\\"sold_product_551324_14742, sold_product_551324_19089\\",\\"33, 12.992\\",\\"33, 12.992\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"15.18, 7.012\\",\\"33, 12.992\\",\\"14,742, 19,089\\",\\"Laptop bag - brown, Vest - white/dark blue\\",\\"Laptop bag - brown, Vest - white/dark blue\\",\\"1, 1\\",\\"ZO0316803168, ZO0566905669\\",\\"0, 0\\",\\"33, 12.992\\",\\"33, 12.992\\",\\"0, 0\\",\\"ZO0316803168, ZO0566905669\\",\\"45.969\\",\\"45.969\\",2,2,order,abdulraheem", + "mwMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",EUR,Marwan,Marwan,\\"Marwan James\\",\\"Marwan James\\",MALE,51,James,James,\\"(empty)\\",Thursday,3,\\"marwan@james-family.zzz\\",Marrakesh,Africa,MA,\\"{", + " \\"\\"coordinates\\"\\": [", + " -8,", + " 31.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Marrakech-Tensift-Al Haouz\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551355,\\"sold_product_551355_21057, sold_product_551355_23405\\",\\"sold_product_551355_21057, sold_product_551355_23405\\",\\"13.992, 11.992\\",\\"13.992, 11.992\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"6.859, 5.762\\",\\"13.992, 11.992\\",\\"21,057, 23,405\\",\\"Scarf - navy/grey, Basic T-shirt - blue\\",\\"Scarf - navy/grey, Basic T-shirt - blue\\",\\"1, 1\\",\\"ZO0313403134, ZO0561205612\\",\\"0, 0\\",\\"13.992, 11.992\\",\\"13.992, 11.992\\",\\"0, 0\\",\\"ZO0313403134, ZO0561205612\\",\\"25.984\\",\\"25.984\\",2,2,order,marwan", + "twMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",EUR,Sonya,Sonya,\\"Sonya Mccormick\\",\\"Sonya Mccormick\\",FEMALE,28,Mccormick,Mccormick,\\"(empty)\\",Thursday,3,\\"sonya@mccormick-family.zzz\\",Bogotu00e1,\\"South America\\",CO,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74.1,", + " 4.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Bogota D.C.\\",Pyramidustries,Pyramidustries,\\"Jun 12, 2019 @ 00:00:00.000\\",550957,\\"sold_product_550957_22980, sold_product_550957_19828\\",\\"sold_product_550957_22980, sold_product_550957_19828\\",\\"24.984, 16.984\\",\\"24.984, 16.984\\",\\"Women's Shoes, Women's Accessories\\",\\"Women's Shoes, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries\\",\\"Pyramidustries, Pyramidustries\\",\\"11.5, 9.172\\",\\"24.984, 16.984\\",\\"22,980, 19,828\\",\\"Classic heels - petrol, Watch - nude\\",\\"Classic heels - petrol, Watch - nude\\",\\"1, 1\\",\\"ZO0133101331, ZO0189401894\\",\\"0, 0\\",\\"24.984, 16.984\\",\\"24.984, 16.984\\",\\"0, 0\\",\\"ZO0133101331, ZO0189401894\\",\\"41.969\\",\\"41.969\\",2,2,order,sonya", + "7AMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",EUR,Kamal,Kamal,\\"Kamal Hansen\\",\\"Kamal Hansen\\",MALE,39,Hansen,Hansen,\\"(empty)\\",Thursday,3,\\"kamal@hansen-family.zzz\\",Istanbul,Asia,TR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 29,", + " 41", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",Istanbul,\\"Low Tide Media, Spritechnologies\\",\\"Low Tide Media, Spritechnologies\\",\\"Jun 12, 2019 @ 00:00:00.000\\",551154,\\"sold_product_551154_13181, sold_product_551154_23660\\",\\"sold_product_551154_13181, sold_product_551154_23660\\",\\"42, 11.992\\",\\"42, 11.992\\",\\"Men's Accessories, Men's Clothing\\",\\"Men's Accessories, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Spritechnologies\\",\\"Low Tide Media, Spritechnologies\\",\\"21, 6.109\\",\\"42, 11.992\\",\\"13,181, 23,660\\",\\"Briefcase - navy, Sports shirt - Seashell\\",\\"Briefcase - navy, Sports shirt - Seashell\\",\\"1, 1\\",\\"ZO0466704667, ZO0617306173\\",\\"0, 0\\",\\"42, 11.992\\",\\"42, 11.992\\",\\"0, 0\\",\\"ZO0466704667, ZO0617306173\\",\\"53.969\\",\\"53.969\\",2,2,order,kamal", + "7QMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing, Women's Accessories\\",\\"Women's Clothing, Women's Accessories\\",EUR,Elyssa,Elyssa,\\"Elyssa Graves\\",\\"Elyssa Graves\\",FEMALE,27,Graves,Graves,\\"(empty)\\",Thursday,3,\\"elyssa@graves-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",Pyramidustries,Pyramidustries,\\"Jun 12, 2019 @ 00:00:00.000\\",551204,\\"sold_product_551204_16805, sold_product_551204_12896\\",\\"sold_product_551204_16805, sold_product_551204_12896\\",\\"13.992, 20.984\\",\\"13.992, 20.984\\",\\"Women's Clothing, Women's Accessories\\",\\"Women's Clothing, Women's Accessories\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Pyramidustries, Pyramidustries\\",\\"Pyramidustries, Pyramidustries\\",\\"7.129, 9.656\\",\\"13.992, 20.984\\",\\"16,805, 12,896\\",\\"Bustier - white, Across body bag - cognac\\",\\"Bustier - white, Across body bag - cognac\\",\\"1, 1\\",\\"ZO0212602126, ZO0200702007\\",\\"0, 0\\",\\"13.992, 20.984\\",\\"13.992, 20.984\\",\\"0, 0\\",\\"ZO0212602126, ZO0200702007\\",\\"34.969\\",\\"34.969\\",2,2,order,elyssa", + "7gMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",EUR,Pia,Pia,\\"Pia Rose\\",\\"Pia Rose\\",FEMALE,45,Rose,Rose,\\"(empty)\\",Thursday,3,\\"pia@rose-family.zzz\\",Cannes,Europe,FR,\\"{", + " \\"\\"coordinates\\"\\": [", + " 7,", + " 43.6", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"Alpes-Maritimes\\",\\"Oceanavigations, Primemaster\\",\\"Oceanavigations, Primemaster\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550466,\\"sold_product_550466_19198, sold_product_550466_16409\\",\\"sold_product_550466_19198, sold_product_550466_16409\\",\\"50, 100\\",\\"50, 100\\",\\"Women's Clothing, Women's Shoes\\",\\"Women's Clothing, Women's Shoes\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Primemaster\\",\\"Oceanavigations, Primemaster\\",\\"24, 52\\",\\"50, 100\\",\\"19,198, 16,409\\",\\"Summer dress - grey, Boots - passion\\",\\"Summer dress - grey, Boots - passion\\",\\"1, 1\\",\\"ZO0260702607, ZO0363203632\\",\\"0, 0\\",\\"50, 100\\",\\"50, 100\\",\\"0, 0\\",\\"ZO0260702607, ZO0363203632\\",150,150,2,2,order,pia", + "7wMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Wagdi,Wagdi,\\"Wagdi Boone\\",\\"Wagdi Boone\\",MALE,15,Boone,Boone,\\"(empty)\\",Thursday,3,\\"wagdi@boone-family.zzz\\",\\"-\\",Asia,SA,\\"{", + " \\"\\"coordinates\\"\\": [", + " 45,", + " 25", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"-\\",Elitelligence,Elitelligence,\\"Jun 12, 2019 @ 00:00:00.000\\",550503,\\"sold_product_550503_13211, sold_product_550503_24369\\",\\"sold_product_550503_13211, sold_product_550503_24369\\",\\"34, 11.992\\",\\"34, 11.992\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Elitelligence\\",\\"Elitelligence, Elitelligence\\",\\"15.641, 6.109\\",\\"34, 11.992\\",\\"13,211, 24,369\\",\\"Tracksuit top - black, Print T-shirt - khaki\\",\\"Tracksuit top - black, Print T-shirt - khaki\\",\\"1, 1\\",\\"ZO0587505875, ZO0566405664\\",\\"0, 0\\",\\"34, 11.992\\",\\"34, 11.992\\",\\"0, 0\\",\\"ZO0587505875, ZO0566405664\\",\\"45.969\\",\\"45.969\\",2,2,order,wagdi", + "8AMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Women's Accessories, Women's Shoes\\",\\"Women's Accessories, Women's Shoes\\",EUR,Elyssa,Elyssa,\\"Elyssa Hale\\",\\"Elyssa Hale\\",FEMALE,27,Hale,Hale,\\"(empty)\\",Thursday,3,\\"elyssa@hale-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -74,", + " 40.8", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",\\"New York\\",\\"Angeldale, Oceanavigations\\",\\"Angeldale, Oceanavigations\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550538,\\"sold_product_550538_15047, sold_product_550538_18189\\",\\"sold_product_550538_15047, sold_product_550538_18189\\",\\"75, 60\\",\\"75, 60\\",\\"Women's Accessories, Women's Shoes\\",\\"Women's Accessories, Women's Shoes\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Angeldale, Oceanavigations\\",\\"Angeldale, Oceanavigations\\",\\"34.5, 28.797\\",\\"75, 60\\",\\"15,047, 18,189\\",\\"Handbag - black, Ankle boots - grey\\",\\"Handbag - black, Ankle boots - grey\\",\\"1, 1\\",\\"ZO0699406994, ZO0246202462\\",\\"0, 0\\",\\"75, 60\\",\\"75, 60\\",\\"0, 0\\",\\"ZO0699406994, ZO0246202462\\",135,135,2,2,order,elyssa", + "8QMtOW0BH63Xcmy46HLV,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",EUR,Jackson,Jackson,\\"Jackson Love\\",\\"Jackson Love\\",MALE,13,Love,Love,\\"(empty)\\",Thursday,3,\\"jackson@love-family.zzz\\",\\"Los Angeles\\",\\"North America\\",US,\\"{", + " \\"\\"coordinates\\"\\": [", + " -118.2,", + " 34.1", + " ],", + " \\"\\"type\\"\\": \\"\\"Point\\"\\"", + "}\\",California,\\"Low Tide Media\\",\\"Low Tide Media\\",\\"Jun 12, 2019 @ 00:00:00.000\\",550568,\\"sold_product_550568_17210, sold_product_550568_12524\\",\\"sold_product_550568_17210, sold_product_550568_12524\\",\\"50, 24.984\\",\\"50, 24.984\\",\\"Men's Shoes, Men's Clothing\\",\\"Men's Shoes, Men's Clothing\\",\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Low Tide Media\\",\\"Low Tide Media, Low Tide Media\\",\\"25, 12.492\\",\\"50, 24.984\\",\\"17,210, 12,524\\",\\"Casual lace-ups - navy, Jumper - dark grey multicolor\\",\\"Casual lace-ups - navy, Jumper - dark grey multicolor\\",\\"1, 1\\",\\"ZO0388403884, ZO0447604476\\",\\"0, 0\\",\\"50, 24.984\\",\\"50, 24.984\\",\\"0, 0\\",\\"ZO0388403884, ZO0447604476\\",75,75,2,2,order,jackson", +] `; diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index 83ad18ed369ae..2010bfd56d2af 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -229,12 +229,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.share.clickShareTopNavButton(); }); - it(`doesn't show CSV reports`, async () => { - await PageObjects.share.clickShareTopNavButton(); - await testSubjects.missingOrFail('sharePanel-CSVReports'); - await PageObjects.share.clickShareTopNavButton(); - }); - it('allows loading a saved query via the saved query management component', async () => { await savedQueryManagementComponent.loadSavedQuery('OKJpgs'); const queryString = await queryBar.getQueryString(); @@ -322,7 +316,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('Permalinks shows create short-url button', async () => { - await PageObjects.share.clickShareTopNavButton(); + await PageObjects.share.openShareMenuItem('Permalinks'); await PageObjects.share.createShortUrlExistOrFail(); // close the menu await PageObjects.share.clickShareTopNavButton(); diff --git a/x-pack/test/functional/apps/discover/reporting.ts b/x-pack/test/functional/apps/discover/reporting.ts index cb05a59f07917..11a442d3500b5 100644 --- a/x-pack/test/functional/apps/discover/reporting.ts +++ b/x-pack/test/functional/apps/discover/reporting.ts @@ -92,7 +92,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(res.status).to.equal(200); expect(res.get('content-type')).to.equal('text/csv; charset=utf-8'); - expectSnapshot(res.text).toMatch(); + + const csvFile = res.text; + const lines = csvFile.trim().split('\n'); + + // verifies the beginning and end of the text + expectSnapshot(lines.slice(0, 100)).toMatch(); + expectSnapshot(lines.slice(-100)).toMatch(); + + expectSnapshot(csvFile.length).toMatchInline(`5093456`); + expectSnapshot(lines.length).toMatchInline(`32726`); }); it('generates a report from a new search with data: discover:searchFieldsFromSource', async () => { @@ -110,7 +119,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(res.status).to.equal(200); expect(res.get('content-type')).to.equal('text/csv; charset=utf-8'); - expectSnapshot(res.text).toMatch(); + + const csvFile = res.text; + const lines = csvFile.trim().split('\n'); + + // verifies the beginning and end of the text + expectSnapshot(lines.slice(0, 100)).toMatch(); + expectSnapshot(lines.slice(-100)).toMatch(); + + expectSnapshot(csvFile.length).toMatchInline(`5093456`); + expectSnapshot(lines.length).toMatchInline(`32726`); + await setFieldsFromSource(false); }); @@ -167,8 +186,40 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await setupPage(); await PageObjects.discover.loadSavedSearch('Ecommerce Data'); - const { text } = await getReport(); - expectSnapshot(text).toMatch(); + const { text: csvFile } = await getReport(); + const lines = csvFile.trim().split('\n'); + expectSnapshot(csvFile.length).toMatchInline(`782100`); + expectSnapshot(lines.length).toMatchInline(`4676`); + + // match the beginning and the end of the csv file contents + expectSnapshot(lines.slice(0, 10)).toMatchInline(` + Array [ + "\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,45,591503,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0006400064, ZO0150601506\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,12,591709,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0638206382, ZO0038800388\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,52,590937,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0297602976, ZO0565605656\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,29,590976,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0561405614, ZO0281602816\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,41,591598,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0276702767, ZO0291702917\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",EUR,44,590927,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0046600466, ZO0050800508\\"", + ] + `); + expectSnapshot(lines.slice(-10)).toMatchInline(` + Array [ + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Clothing\\",EUR,24,550580,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0144801448, ZO0219602196\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Accessories, Men's Clothing\\",EUR,33,551324,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0316803168, ZO0566905669\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Accessories, Men's Clothing\\",EUR,51,551355,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0313403134, ZO0561205612\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Shoes, Women's Accessories\\",EUR,28,550957,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0133101331, ZO0189401894\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Accessories, Men's Clothing\\",EUR,39,551154,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0466704667, ZO0617306173\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Accessories\\",EUR,27,551204,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0212602126, ZO0200702007\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,45,550466,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0260702607, ZO0363203632\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing\\",EUR,15,550503,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0587505875, ZO0566405664\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Women's Accessories, Women's Shoes\\",EUR,27,550538,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0699406994, ZO0246202462\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,13,550568,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0388403884, ZO0447604476\\"", + ] + `); }); it('generates a report with filtered data', async () => { @@ -176,11 +227,44 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.loadSavedSearch('Ecommerce Data'); // filter and re-save - await filterBar.addFilter('currency', 'is', 'EUR'); + await filterBar.addFilter('category', 'is', `Men's Shoes`); await PageObjects.discover.saveSearch(`Ecommerce Data: EUR Filtered`); // renamed the search - const { text } = await getReport(); - expectSnapshot(text).toMatch(); + const { text: csvFile } = await getReport(); + const lines = csvFile.trim().split('\n'); + expectSnapshot(csvFile.length).toMatchInline(`165557`); + expectSnapshot(lines.length).toMatchInline(`945`); + + // match the beginning and the end of the csv file contents + expectSnapshot(lines.slice(0, 10)).toMatchInline(` + Array [ + "\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,4,591148,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0290302903, ZO0513705137\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,14,591562,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0544305443, ZO0108001080\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,30,591411,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0693506935, ZO0532405324\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,38,722629,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0424204242, ZO0403504035, ZO0506705067, ZO0395603956\\"", + ] + `); + expectSnapshot(lines.slice(-10)).toMatchInline(` + Array [ + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,37,550425,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0256602566, ZO0516305163\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,52,719265,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0619506195, ZO0297802978, ZO0125001250, ZO0693306933\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,34,550990,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0404404044, ZO0570605706\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,7,550663,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0399703997, ZO0560905609\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,34,551697,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0387903879, ZO0693206932\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,9,550784,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0683206832, ZO0687706877\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,15,550412,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0508905089, ZO0681206812\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,23,551556,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0512405124, ZO0551405514\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,550473,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0688006880, ZO0450504505\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,13,550568,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0388403884, ZO0447604476\\"", + ] + `); + await PageObjects.discover.saveSearch(`Ecommerce Data`); // rename the search back for the next test }); @@ -191,8 +275,41 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await setFieldsFromSource(true); await browser.refresh(); - const { text } = await getReport(); - expectSnapshot(text).toMatch(); + const { text: csvFile } = await getReport(); + const lines = csvFile.trim().split('\n'); + expectSnapshot(csvFile.length).toMatchInline(`165753`); + expectSnapshot(lines.length).toMatchInline(`945`); + + // match the beginning and the end of the csv file contents + expectSnapshot(lines.slice(0, 10)).toMatchInline(` + Array [ + "\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,19,716724,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,41,591636,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0385003850, ZO0408604086\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,30,591539,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0505605056, ZO0513605136\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,48,590970,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0455604556, ZO0680806808\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,591297,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0257502575, ZO0451704517\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,4,591148,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0290302903, ZO0513705137\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,14,591562,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0544305443, ZO0108001080\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,30,591411,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0693506935, ZO0532405324\\"", + "\\"Jul 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,38,722629,5,\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"ZO0424204242, ZO0403504035, ZO0506705067, ZO0395603956\\"", + ] + `); + expectSnapshot(lines.slice(-10)).toMatchInline(` + Array [ + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,37,550425,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0256602566, ZO0516305163\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,52,719265,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0619506195, ZO0297802978, ZO0125001250, ZO0693306933\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,34,550990,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0404404044, ZO0570605706\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,7,550663,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0399703997, ZO0560905609\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,34,551697,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0387903879, ZO0693206932\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,9,550784,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0683206832, ZO0687706877\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes\\",EUR,15,550412,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0508905089, ZO0681206812\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,23,551556,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0512405124, ZO0551405514\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,21,550473,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0688006880, ZO0450504505\\"", + "\\"Jun 12, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,13,550568,3,\\"Dec 1, 2016 @ 00:00:00.000, Dec 1, 2016 @ 00:00:00.000\\",\\"ZO0388403884, ZO0447604476\\"", + ] + `); + await setFieldsFromSource(false); }); }); diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts index cc121dccfb13e..913a5034bacc5 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts @@ -16,7 +16,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const appsMenu = getService('appsMenu'); const globalNav = getService('globalNav'); - describe('security', () => { + // FLAKY https://github.com/elastic/kibana/issues/109564 + describe.skip('security', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); // ensure we're logged out so we can login as the appropriate users diff --git a/x-pack/test/functional/apps/lens/lens_reporting.ts b/x-pack/test/functional/apps/lens/lens_reporting.ts index fb4c501baaf80..bd0c003ff3f45 100644 --- a/x-pack/test/functional/apps/lens/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/lens_reporting.ts @@ -18,18 +18,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('lens reporting', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/reporting'); - await security.role.create('test_reporting_user', { - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: [ - { - spaces: ['*'], - base: [], - feature: { dashboard: ['minimal_read', 'generate_report'] }, - }, - ], - }); await security.testUser.setRoles( - ['test_logstash_reader', 'global_dashboard_read', 'test_reporting_user'], + [ + 'test_logstash_reader', + 'global_dashboard_read', + 'reporting_user', // NOTE: the built-in role granting full reporting access is deprecated. See xpack.reporting.roles.enabled + ], false ); }); diff --git a/x-pack/test/functional/apps/management/feature_controls/management_security.ts b/x-pack/test/functional/apps/management/feature_controls/management_security.ts index edca9b64d4fca..b47afc4cda6e4 100644 --- a/x-pack/test/functional/apps/management/feature_controls/management_security.ts +++ b/x-pack/test/functional/apps/management/feature_controls/management_security.ts @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(sections).to.have.length(2); expect(sections[0]).to.eql({ sectionId: 'insightsAndAlerting', - sectionLinks: ['triggersActions', 'reporting'], + sectionLinks: ['triggersActions'], }); expect(sections[1]).to.eql({ sectionId: 'kibana', diff --git a/x-pack/test/functional/apps/maps/mapbox_styles.js b/x-pack/test/functional/apps/maps/mapbox_styles.js index a0a858c303722..58c69950590cf 100644 --- a/x-pack/test/functional/apps/maps/mapbox_styles.js +++ b/x-pack/test/functional/apps/maps/mapbox_styles.js @@ -6,7 +6,10 @@ */ import expect from '@kbn/expect'; -import { KBN_IS_TILE_COMPLETE, KBN_METADATA_FEATURE } from '../../../../plugins/maps/common'; +import { + KBN_IS_TILE_COMPLETE, + KBN_METADATA_FEATURE, +} from '../../../../plugins/maps/common/constants'; export default function ({ getPageObjects, getService }) { const PageObjects = getPageObjects(['maps']); @@ -219,6 +222,7 @@ export default function ({ getPageObjects, getService }) { const layer = mapboxStyle.layers.find((mbLayer) => { return mbLayer.id === 'n1t6f_toomanyfeatures'; }); + expect(layer).to.eql({ id: 'n1t6f_toomanyfeatures', type: 'fill', diff --git a/x-pack/test/functional/apps/reporting_management/report_listing.ts b/x-pack/test/functional/apps/reporting_management/report_listing.ts index dee09cf678992..1291fb686d8b1 100644 --- a/x-pack/test/functional/apps/reporting_management/report_listing.ts +++ b/x-pack/test/functional/apps/reporting_management/report_listing.ts @@ -20,17 +20,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Listing of Reports', function () { before(async () => { - await security.role.create('test_reporting_user', { - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: [ - { - spaces: ['*'], - base: [], - feature: { canvas: ['minimal_read', 'generate_report'] }, - }, - ], - }); - await security.testUser.setRoles(['kibana_admin', 'test_reporting_user']); + await security.testUser.setRoles([ + 'kibana_admin', // to access stack management + 'reporting_user', // NOTE: the built-in role granting full reporting access is deprecated. See xpack.reporting.roles.enabled + ]); await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); }); diff --git a/x-pack/test/functional/apps/saved_objects_management/exports/_7.13_import_saved_objects.ndjson b/x-pack/test/functional/apps/saved_objects_management/exports/_7.13_import_saved_objects.ndjson index 5c80b7f0624fe..21be31349af0f 100644 --- a/x-pack/test/functional/apps/saved_objects_management/exports/_7.13_import_saved_objects.ndjson +++ b/x-pack/test/functional/apps/saved_objects_management/exports/_7.13_import_saved_objects.ndjson @@ -70,7 +70,6 @@ {"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"darkTheme\":false,\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"1\",\"w\":24,\"x\":0,\"y\":0},\"panelIndex\":\"1\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"2\",\"w\":24,\"x\":24,\"y\":0},\"panelIndex\":\"2\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_2\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"3\",\"w\":24,\"x\":0,\"y\":15},\"panelIndex\":\"3\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_3\"},{\"version\":\"7.3.0\",\"type\":\"search\",\"gridData\":{\"h\":15,\"i\":\"4\",\"w\":24,\"x\":24,\"y\":15},\"panelIndex\":\"4\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_4\"},{\"version\":\"7.3.0\",\"type\":\"search\",\"gridData\":{\"h\":15,\"i\":\"5\",\"w\":24,\"x\":0,\"y\":30},\"panelIndex\":\"5\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_5\"},{\"version\":\"7.3.0\",\"type\":\"search\",\"gridData\":{\"h\":15,\"i\":\"6\",\"w\":24,\"x\":24,\"y\":30},\"panelIndex\":\"6\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_6\"}]","timeRestore":false,"title":"shakespeare_dashboard","version":1},"coreMigrationVersion":"7.13.2","id":"73398a90-619e-11eb-aebf-c306684b328d","migrationVersion":{"dashboard":"7.13.1"},"references":[{"id":"185283c0-619e-11eb-aebf-c306684b328d","name":"1:panel_1","type":"visualization"},{"id":"33736660-619e-11eb-aebf-c306684b328d","name":"2:panel_2","type":"visualization"},{"id":"622ac7f0-619e-11eb-aebf-c306684b328d","name":"3:panel_3","type":"visualization"},{"id":"712ebbe0-619d-11eb-aebf-c306684b328d","name":"4:panel_4","type":"search"},{"id":"ddacc820-619d-11eb-aebf-c306684b328d","name":"5:panel_5","type":"search"},{"id":"f852d570-619d-11eb-aebf-c306684b328d","name":"6:panel_6","type":"search"}],"sort":[1623693556928,649],"type":"dashboard","updated_at":"2021-06-14T17:59:16.928Z","version":"WzE3MDEsNF0="} {"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"lucene\",\"query\":\"\"},\"filter\":[{\"meta\":{\"negate\":false,\"disabled\":false,\"alias\":null,\"type\":\"phrase\",\"key\":\"geo.srcdest\",\"value\":\"IN:US\",\"params\":{\"query\":\"IN:US\",\"type\":\"phrase\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"match\":{\"geo.srcdest\":{\"query\":\"IN:US\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}]}"},"optionsJSON":"{\"darkTheme\":false,\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"1\",\"w\":24,\"x\":0,\"y\":0},\"panelIndex\":\"1\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"2\",\"w\":24,\"x\":24,\"y\":0},\"panelIndex\":\"2\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_2\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"3\",\"w\":24,\"x\":0,\"y\":15},\"panelIndex\":\"3\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_3\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"4\",\"w\":24,\"x\":24,\"y\":15},\"panelIndex\":\"4\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_4\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"5\",\"w\":24,\"x\":0,\"y\":30},\"panelIndex\":\"5\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_5\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"6\",\"w\":24,\"x\":24,\"y\":30},\"panelIndex\":\"6\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_6\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"7\",\"w\":24,\"x\":0,\"y\":45},\"panelIndex\":\"7\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_7\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"8\",\"w\":24,\"x\":24,\"y\":45},\"panelIndex\":\"8\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_8\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"9\",\"w\":24,\"x\":0,\"y\":60},\"panelIndex\":\"9\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_9\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"10\",\"w\":24,\"x\":24,\"y\":60},\"panelIndex\":\"10\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_10\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"11\",\"w\":24,\"x\":0,\"y\":75},\"panelIndex\":\"11\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_11\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"12\",\"w\":24,\"x\":24,\"y\":75},\"panelIndex\":\"12\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_12\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"13\",\"w\":24,\"x\":0,\"y\":90},\"panelIndex\":\"13\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_13\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"14\",\"w\":24,\"x\":24,\"y\":90},\"panelIndex\":\"14\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_14\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"15\",\"w\":24,\"x\":0,\"y\":105},\"panelIndex\":\"15\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_15\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"16\",\"w\":24,\"x\":24,\"y\":105},\"panelIndex\":\"16\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_16\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"17\",\"w\":24,\"x\":0,\"y\":120},\"panelIndex\":\"17\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_17\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"18\",\"w\":24,\"x\":24,\"y\":120},\"panelIndex\":\"18\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_18\"},{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"h\":15,\"i\":\"19\",\"w\":24,\"x\":0,\"y\":135},\"panelIndex\":\"19\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_19\"},{\"version\":\"7.3.0\",\"type\":\"search\",\"gridData\":{\"h\":15,\"i\":\"20\",\"w\":24,\"x\":24,\"y\":135},\"panelIndex\":\"20\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_20\"}]","timeRestore":false,"title":"logstash_dashboardwithfilters","version":1},"coreMigrationVersion":"7.13.2","id":"79794f20-6249-11eb-aebf-c306684b328d","migrationVersion":{"dashboard":"7.13.1"},"references":[{"id":"56b34100-619d-11eb-aebf-c306684b328d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index","type":"index-pattern"},{"id":"36b91810-6239-11eb-aebf-c306684b328d","name":"1:panel_1","type":"visualization"},{"id":"0a274320-61cc-11eb-aebf-c306684b328d","name":"2:panel_2","type":"visualization"},{"id":"e4aef350-623d-11eb-aebf-c306684b328d","name":"3:panel_3","type":"visualization"},{"id":"f92e5630-623e-11eb-aebf-c306684b328d","name":"4:panel_4","type":"visualization"},{"id":"9853d4d0-623d-11eb-aebf-c306684b328d","name":"5:panel_5","type":"visualization"},{"id":"6ecb33b0-623d-11eb-aebf-c306684b328d","name":"6:panel_6","type":"visualization"},{"id":"b8e35c80-623c-11eb-aebf-c306684b328d","name":"7:panel_7","type":"visualization"},{"id":"f1bc75d0-6239-11eb-aebf-c306684b328d","name":"8:panel_8","type":"visualization"},{"id":"0d8a8860-623a-11eb-aebf-c306684b328d","name":"9:panel_9","type":"visualization"},{"id":"d79fe3d0-6239-11eb-aebf-c306684b328d","name":"10:panel_10","type":"visualization"},{"id":"318375a0-6240-11eb-aebf-c306684b328d","name":"11:panel_11","type":"visualization"},{"id":"e461eb20-6245-11eb-aebf-c306684b328d","name":"12:panel_12","type":"visualization"},{"id":"25bdc750-6242-11eb-aebf-c306684b328d","name":"13:panel_13","type":"visualization"},{"id":"71dd7bc0-6248-11eb-aebf-c306684b328d","name":"14:panel_14","type":"visualization"},{"id":"6aea48a0-6240-11eb-aebf-c306684b328d","name":"15:panel_15","type":"visualization"},{"id":"32b681f0-6241-11eb-aebf-c306684b328d","name":"16:panel_16","type":"visualization"},{"id":"ccca99e0-6244-11eb-aebf-c306684b328d","name":"17:panel_17","type":"visualization"},{"id":"a4d7be80-6245-11eb-aebf-c306684b328d","name":"18:panel_18","type":"visualization"},{"id":"c94d8440-6248-11eb-aebf-c306684b328d","name":"19:panel_19","type":"visualization"},{"id":"db6226f0-61c0-11eb-aebf-c306684b328d","name":"20:panel_20","type":"search"}],"sort":[1623693556928,671],"type":"dashboard","updated_at":"2021-06-14T17:59:16.928Z","version":"WzE3MDIsNF0="} {"attributes":{"description":"","state":{"datasourceStates":{"indexpattern":{"layers":{"037b7937-790b-4d2d-94a5-7f5837a6ef05":{"columnOrder":["b3d46616-75e0-419e-97ea-91148961ef94","025a0fb3-dc44-4f5c-b517-2d71d3f26f14","c476db14-0cc1-40ec-863e-d2779256a407"],"columns":{"025a0fb3-dc44-4f5c-b517-2d71d3f26f14":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"@timestamp"},"b3d46616-75e0-419e-97ea-91148961ef94":{"dataType":"string","isBucketed":true,"label":"Top values of geo.srcdest","operationType":"terms","params":{"missingBucket":false,"orderBy":{"columnId":"c476db14-0cc1-40ec-863e-d2779256a407","type":"column"},"orderDirection":"desc","otherBucket":true,"size":3},"scale":"ordinal","sourceField":"geo.srcdest"},"c476db14-0cc1-40ec-863e-d2779256a407":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"}},"incompleteColumns":{}}}}},"filters":[],"query":{"language":"lucene","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["c476db14-0cc1-40ec-863e-d2779256a407"],"layerId":"037b7937-790b-4d2d-94a5-7f5837a6ef05","position":"top","seriesType":"bar_stacked","showGridlines":false,"splitAccessor":"b3d46616-75e0-419e-97ea-91148961ef94","xAccessor":"025a0fb3-dc44-4f5c-b517-2d71d3f26f14"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"lens_verticalstacked","visualizationType":"lnsXY"},"coreMigrationVersion":"7.13.2","id":"8dc19b50-be32-11eb-9520-1b4c3ca6a781","migrationVersion":{"lens":"7.13.1"},"references":[{"id":"56b34100-619d-11eb-aebf-c306684b328d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"56b34100-619d-11eb-aebf-c306684b328d","name":"indexpattern-datasource-layer-037b7937-790b-4d2d-94a5-7f5837a6ef05","type":"index-pattern"},{"id":"e6994960-bd9e-11eb-9520-1b4c3ca6a781","name":"tag-ref-e6994960-bd9e-11eb-9520-1b4c3ca6a781","type":"tag"}],"sort":[1623693556928,675],"type":"lens","updated_at":"2021-06-14T17:59:16.928Z","version":"WzE3MDMsNF0="} -{"attributes":{"description":"","hits":0,"timelion_chart_height":275,"timelion_columns":2,"timelion_interval":"auto","timelion_rows":2,"timelion_sheet":[".es(index=logstash-*, \"sum:bytes\")"],"title":"logstash_timelionsheet","version":1},"coreMigrationVersion":"7.13.2","id":"a8961990-be5c-11eb-9520-1b4c3ca6a781","references":[],"sort":[1623693556928,676],"type":"timelion-sheet","updated_at":"2021-06-14T17:59:16.928Z","version":"WzE3MDQsNF0="} {"attributes":{"columns":[],"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"geo.dest\",\"params\":{\"query\":\"US\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"match_phrase\":{\"geo.dest\":\"US\"}},\"$state\":{\"store\":\"appState\"}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"title":"drilldown_logstash","version":1},"coreMigrationVersion":"7.13.2","id":"b3288100-ca2c-11eb-bf5e-3de94e83d4f0","migrationVersion":{"search":"7.9.3"},"references":[{"id":"43fcac20-ca27-11eb-bf5e-3de94e83d4f0","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"43fcac20-ca27-11eb-bf5e-3de94e83d4f0","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index","type":"index-pattern"}],"sort":[1623415891791,216],"type":"search","updated_at":"2021-06-11T12:51:31.791Z","version":"WzE0NjMsNF0="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{}"},"title":"logstash_timelion_panel","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"logstash_timelion_panel\",\"type\":\"timelion\",\"aggs\":[],\"params\":{\"expression\":\".es(index=logstash-*, \\\"sum:bytes\\\")\",\"interval\":\"auto\"}}"},"coreMigrationVersion":"7.13.2","id":"b3a44cd0-be5c-11eb-9520-1b4c3ca6a781","migrationVersion":{"visualization":"7.13.1"},"references":[],"sort":[1623693556928,677],"type":"visualization","updated_at":"2021-06-14T17:59:16.928Z","version":"WzE3MDUsNF0="} {"attributes":{"color":"#9170B8","description":"","name":"alltogether"},"coreMigrationVersion":"7.13.2","id":"be808cb0-be32-11eb-9520-1b4c3ca6a781","references":[],"sort":[1623693556928,678],"type":"tag","updated_at":"2021-06-14T17:59:16.928Z","version":"WzE3MDYsNF0="} diff --git a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts index 427e42b7b7a65..790909164b33d 100644 --- a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts +++ b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts @@ -48,14 +48,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.savedObjects.clickImportDone(); const importedSavedObjects = await testSubjects.getVisibleText('exportAllObjects'); // verifying the count of saved objects after importing .ndjson - await expect(importedSavedObjects).to.be('Export 88 objects'); + await expect(importedSavedObjects).to.be('Export 87 objects'); }); it('should be able to import alerts and actions saved objects from 7.14 into 8.0.0', async function () { await retry.tryForTime(10000, async () => { const existingSavedObjects = await testSubjects.getVisibleText('exportAllObjects'); // Kibana always has 1 advanced setting as a saved object - await expect(existingSavedObjects).to.be('Export 88 objects'); + await expect(existingSavedObjects).to.be('Export 87 objects'); }); await PageObjects.savedObjects.importFile( path.join(__dirname, 'exports', '_7.14_import_alerts_actions.ndjson') @@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.savedObjects.clickImportDone(); const importedSavedObjects = await testSubjects.getVisibleText('exportAllObjects'); // verifying the count of saved objects after importing .ndjson - await expect(importedSavedObjects).to.be('Export 111 objects'); + await expect(importedSavedObjects).to.be('Export 110 objects'); }); }); } diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts deleted file mode 100644 index 8a1021abb4330..0000000000000 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts +++ /dev/null @@ -1,196 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const security = getService('security'); - const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects([ - 'common', - 'error', - 'header', - 'security', - 'spaceSelector', - 'timelion', - ]); - const appsMenu = getService('appsMenu'); - const globalNav = getService('globalNav'); - - describe('feature controls security', () => { - before(async () => { - await kibanaServer.importExport.load( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' - ); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - }); - - after(async () => { - await kibanaServer.importExport.unload( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' - ); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/110396 - describe.skip('global timelion all privileges', () => { - before(async () => { - await security.role.create('global_timelion_all_role', { - elasticsearch: { - indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], - }, - kibana: [ - { - feature: { - timelion: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create('global_timelion_all_user', { - password: 'global_timelion_all_user-password', - roles: ['global_timelion_all_role'], - full_name: 'test user', - }); - - await PageObjects.security.forceLogout(); - - await PageObjects.security.login( - 'global_timelion_all_user', - 'global_timelion_all_user-password', - { - expectSpaceSelector: false, - } - ); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await security.role.delete('global_timelion_all_role'); - await security.user.delete('global_timelion_all_user'); - }); - - it('shows timelion navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Timelion']); - }); - - it(`allows a timelion sheet to be created`, async () => { - await PageObjects.common.navigateToApp('timelion'); - await PageObjects.timelion.saveTimelionSheet(); - }); - - it(`doesn't show read-only badge`, async () => { - await globalNav.badgeMissingOrFail(); - }); - }); - - describe('global timelion read-only privileges', () => { - before(async () => { - await security.role.create('global_timelion_read_role', { - elasticsearch: { - indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], - }, - kibana: [ - { - feature: { - timelion: ['read'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create('global_timelion_read_user', { - password: 'global_timelion_read_user-password', - roles: ['global_timelion_read_role'], - full_name: 'test user', - }); - - await PageObjects.security.login( - 'global_timelion_read_user', - 'global_timelion_read_user-password', - { - expectSpaceSelector: false, - } - ); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await security.role.delete('global_timelion_read_role'); - await security.user.delete('global_timelion_read_user'); - }); - - it('shows timelion navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Timelion']); - }); - - it(`does not allow a timelion sheet to be created`, async () => { - await PageObjects.common.navigateToApp('timelion'); - await PageObjects.timelion.expectMissingWriteControls(); - }); - - it(`shows read-only badge`, async () => { - await globalNav.badgeExistsOrFail('Read only'); - }); - }); - - describe('no timelion privileges', () => { - before(async () => { - await security.role.create('no_timelion_privileges_role', { - elasticsearch: { - indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], - }, - kibana: [ - { - feature: { - discover: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create('no_timelion_privileges_user', { - password: 'no_timelion_privileges_user-password', - roles: ['no_timelion_privileges_role'], - full_name: 'test user', - }); - - await PageObjects.security.forceLogout(); - - await PageObjects.security.login( - 'no_timelion_privileges_user', - 'no_timelion_privileges_user-password', - { - expectSpaceSelector: false, - } - ); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await security.role.delete('no_timelion_privileges_role'); - await security.user.delete('no_timelion_privileges_user'); - }); - - it(`returns a 403`, async () => { - await PageObjects.common.navigateToActualUrl('timelion', '', { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - }); - PageObjects.error.expectForbidden(); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts deleted file mode 100644 index a1dea695fce86..0000000000000 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts +++ /dev/null @@ -1,158 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const spacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'timelion', 'security', 'spaceSelector']); - const appsMenu = getService('appsMenu'); - const kibanaServer = getService('kibanaServer'); - - describe('timelion', () => { - before(async () => { - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - }); - - describe('space with no features disabled', () => { - before(async () => { - // we need to load the following in every situation as deleting - // a space deletes all of the associated saved objects - // await esArchiver.load('x-pack/test/functional/es_archives/timelion/feature_controls'); - await kibanaServer.importExport.load( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' - ); - - await spacesService.create({ - id: 'custom_space', - name: 'custom_space', - disabledFeatures: [], - }); - - await kibanaServer.importExport.load( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json', - { space: 'custom_space' } - ); - }); - - after(async () => { - await spacesService.delete('custom_space'); - await kibanaServer.importExport.unload( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' - ); - }); - - it('shows timelion navlink', async () => { - await PageObjects.common.navigateToApp('home', { - basePath: '/s/custom_space', - }); - - const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.contain('Timelion'); - }); - - it(`allows a timelion sheet to be created`, async () => { - await PageObjects.common.navigateToApp('timelion', { - basePath: '/s/custom_space', - }); - - await PageObjects.timelion.saveTimelionSheet(); - }); - }); - - describe('space with Timelion disabled', () => { - before(async () => { - // we need to load the following in every situation as deleting - // a space deletes all of the associated saved objects - // await esArchiver.load('x-pack/test/functional/es_archives/timelion/feature_controls'); - - await kibanaServer.importExport.load( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' - ); - - await spacesService.create({ - id: 'custom_space', - name: 'custom_space', - disabledFeatures: ['timelion'], - }); - - await kibanaServer.importExport.load( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json', - { space: 'custom_space' } - ); - }); - - after(async () => { - await spacesService.delete('custom_space'); - await kibanaServer.importExport.unload( - 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' - ); - }); - - it(`doesn't show timelion navlink`, async () => { - await PageObjects.common.navigateToApp('home', { - basePath: '/s/custom_space', - }); - const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).not.to.contain('Timelion'); - }); - - it(`create new timelion returns a 404`, async () => { - await PageObjects.common.navigateToActualUrl('timelion', 'i-exist', { - basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - }); - - const messageText = await PageObjects.common.getJsonBodyText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); - }); - - it(`edit timelion sheet which doesn't exist returns a 404`, async () => { - await PageObjects.common.navigateToActualUrl('timelion', 'i-dont-exist', { - basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - }); - - const messageText = await PageObjects.common.getJsonBodyText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); - }); - - it(`edit timelion sheet which exists returns a 404`, async () => { - await PageObjects.common.navigateToActualUrl('timelion', 'i-exist', { - basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - }); - - const messageText = await PageObjects.common.getJsonBodyText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/timelion/index.ts b/x-pack/test/functional/apps/timelion/index.ts deleted file mode 100644 index 009f080adddec..0000000000000 --- a/x-pack/test/functional/apps/timelion/index.ts +++ /dev/null @@ -1,17 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function timelion({ loadTestFile }: FtrProviderContext) { - describe('Timelion', function visualizeTestSuite() { - this.tags(['ciGroup4', 'skipFirefox']); - - loadTestFile(require.resolve('./feature_controls/timelion_security')); - loadTestFile(require.resolve('./feature_controls/timelion_spaces')); - }); -} diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 0de3df6a04ed8..f8e8ce76bace1 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -40,7 +40,6 @@ export default async function ({ readConfigFile }) { resolve(__dirname, './apps/rollup_job'), resolve(__dirname, './apps/maps'), resolve(__dirname, './apps/status_page'), - resolve(__dirname, './apps/timelion'), resolve(__dirname, './apps/upgrade_assistant'), resolve(__dirname, './apps/visualize'), resolve(__dirname, './apps/uptime'), @@ -83,14 +82,10 @@ export default async function ({ readConfigFile }) { '--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d', '--xpack.maps.showMapsInspectorAdapter=true', '--xpack.maps.preserveDrawingBuffer=true', - '--xpack.reporting.roles.enabled=false', // use the non-deprecated access control model for Reporting - '--xpack.reporting.queue.pollInterval=3000', // make it explicitly the default - '--xpack.reporting.csv.maxSizeBytes=2850', // small-ish limit for cutting off a 1999 byte report '--usageCollection.maximumWaitTimeForAllCollectorsInS=1', '--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions '--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', '--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true', - '--timelion.ui.enabled=true', '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects '--xpack.observability.unsafe.cases.enabled=true', '--xpack.observability.unsafe.alertingExperience.enabled=true', // NOTE: Can be removed once enabled by default @@ -240,8 +235,8 @@ export default async function ({ readConfigFile }) { kibana: [ { feature: { - canvas: ['minimal_all'], - visualize: ['minimal_all'], + canvas: ['all'], + visualize: ['all'], }, spaces: ['*'], }, diff --git a/x-pack/test/functional/es_archives/action_task_params/mappings.json b/x-pack/test/functional/es_archives/action_task_params/mappings.json index d0eb35fa3b157..2bb6be179d890 100644 --- a/x-pack/test/functional/es_archives/action_task_params/mappings.json +++ b/x-pack/test/functional/es_archives/action_task_params/mappings.json @@ -67,7 +67,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2312,47 +2311,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/actions/mappings.json b/x-pack/test/functional/es_archives/actions/mappings.json index 8289174ffd57d..eeb9d09d3b0a2 100644 --- a/x-pack/test/functional/es_archives/actions/mappings.json +++ b/x-pack/test/functional/es_archives/actions/mappings.json @@ -67,7 +67,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2311,47 +2310,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/alerts/mappings.json b/x-pack/test/functional/es_archives/alerts/mappings.json index 287d9a79a68cf..ecaf138a0cdc4 100644 --- a/x-pack/test/functional/es_archives/alerts/mappings.json +++ b/x-pack/test/functional/es_archives/alerts/mappings.json @@ -45,7 +45,6 @@ "search": "181661168bbadd1eff5902361e2a0d5c", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "todo": "082a2cc96a590268344d5cd74c159ac4", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", diff --git a/x-pack/test/functional/es_archives/alerts_legacy/mappings.json b/x-pack/test/functional/es_archives/alerts_legacy/mappings.json index 8c33155636a8b..69edf30c1ffd0 100644 --- a/x-pack/test/functional/es_archives/alerts_legacy/mappings.json +++ b/x-pack/test/functional/es_archives/alerts_legacy/mappings.json @@ -66,7 +66,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2312,47 +2311,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/banners/multispace/mappings.json b/x-pack/test/functional/es_archives/banners/multispace/mappings.json index 9f3201d73abc1..f813fca64c328 100644 --- a/x-pack/test/functional/es_archives/banners/multispace/mappings.json +++ b/x-pack/test/functional/es_archives/banners/multispace/mappings.json @@ -180,48 +180,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/canvas/default/mappings.json b/x-pack/test/functional/es_archives/canvas/default/mappings.json index 3bde3969e5ded..61481a0f949fa 100644 --- a/x-pack/test/functional/es_archives/canvas/default/mappings.json +++ b/x-pack/test/functional/es_archives/canvas/default/mappings.json @@ -245,47 +245,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/canvas/reports/mappings.json b/x-pack/test/functional/es_archives/canvas/reports/mappings.json index 66f40d746b794..047a52aaa1f98 100644 --- a/x-pack/test/functional/es_archives/canvas/reports/mappings.json +++ b/x-pack/test/functional/es_archives/canvas/reports/mappings.json @@ -70,7 +70,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2175,47 +2174,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/cases/migrations/7.10.0/mappings.json b/x-pack/test/functional/es_archives/cases/migrations/7.10.0/mappings.json index 7101af08400a2..c7e1a180e6c54 100644 --- a/x-pack/test/functional/es_archives/cases/migrations/7.10.0/mappings.json +++ b/x-pack/test/functional/es_archives/cases/migrations/7.10.0/mappings.json @@ -67,7 +67,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2305,47 +2304,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/cases/migrations/7.11.1/mappings.json b/x-pack/test/functional/es_archives/cases/migrations/7.11.1/mappings.json index 5140a81b3ed46..bc719ab40aebd 100644 --- a/x-pack/test/functional/es_archives/cases/migrations/7.11.1/mappings.json +++ b/x-pack/test/functional/es_archives/cases/migrations/7.11.1/mappings.json @@ -78,7 +78,6 @@ "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", "tag": "83d55da58f6530f7055415717ec06474", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2502,47 +2501,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/cases/migrations/7.13.2/mappings.json b/x-pack/test/functional/es_archives/cases/migrations/7.13.2/mappings.json index e79ebf2b8fc10..88f3e5d78cb16 100644 --- a/x-pack/test/functional/es_archives/cases/migrations/7.13.2/mappings.json +++ b/x-pack/test/functional/es_archives/cases/migrations/7.13.2/mappings.json @@ -84,7 +84,6 @@ "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", "tag": "83d55da58f6530f7055415717ec06474", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2643,47 +2642,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json b/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json index 5001bb053e01b..1766723cc436f 100644 --- a/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json b/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json index a842c20b6965e..8382a490ac230 100644 --- a/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json b/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json index 29c56a751c038..a4392cd88b356 100644 --- a/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json @@ -386,47 +386,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json b/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json index 0cd1a29f92241..51dfbb6d066fc 100644 --- a/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json @@ -358,47 +358,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json b/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json index a842c20b6965e..8382a490ac230 100644 --- a/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json @@ -137,48 +137,6 @@ } } }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json index 9c27bc1458c7f..a3eab7787c3eb 100644 --- a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json +++ b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json @@ -2356,47 +2356,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/empty_kibana/mappings.json b/x-pack/test/functional/es_archives/empty_kibana/mappings.json index 499c994780285..07dc66dd8ce94 100644 --- a/x-pack/test/functional/es_archives/empty_kibana/mappings.json +++ b/x-pack/test/functional/es_archives/empty_kibana/mappings.json @@ -177,48 +177,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/mappings.json index 9d7da7b5bbd87..4dcb8f905b197 100644 --- a/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/mappings.json @@ -66,7 +66,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2336,47 +2335,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json index 7aadc43a3ad94..d6d91281151aa 100644 --- a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json @@ -67,7 +67,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2341,47 +2340,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json index 7aadc43a3ad94..d6d91281151aa 100644 --- a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json @@ -67,7 +67,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2341,47 +2340,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json index 7aadc43a3ad94..d6d91281151aa 100644 --- a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json @@ -67,7 +67,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2341,47 +2340,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/mappings.json index 9d7da7b5bbd87..4dcb8f905b197 100644 --- a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/mappings.json @@ -66,7 +66,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2336,47 +2335,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/mappings.json index 9d7da7b5bbd87..4dcb8f905b197 100644 --- a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/mappings.json @@ -66,7 +66,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2336,47 +2335,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/mappings.json index 9d7da7b5bbd87..4dcb8f905b197 100644 --- a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/mappings.json @@ -66,7 +66,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2336,47 +2335,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/event_log_multiple_indicies/mappings.json b/x-pack/test/functional/es_archives/event_log_multiple_indicies/mappings.json index 15382fb2524fe..b28c558612770 100644 --- a/x-pack/test/functional/es_archives/event_log_multiple_indicies/mappings.json +++ b/x-pack/test/functional/es_archives/event_log_multiple_indicies/mappings.json @@ -71,7 +71,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", diff --git a/x-pack/test/functional/es_archives/fleet/agents/mappings.json b/x-pack/test/functional/es_archives/fleet/agents/mappings.json index e9a3a965c0523..8e4c7a912b75a 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/mappings.json +++ b/x-pack/test/functional/es_archives/fleet/agents/mappings.json @@ -65,7 +65,6 @@ "maps-telemetry": "bfd39d88aadadb4be597ea984d433dbe", "namespace": "2f4316de49999235636386fe51dc06c1", "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "config": "ae24d22d5986d04124cc6568f771066f", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215" @@ -2846,47 +2845,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/hybrid/kibana/mappings.json b/x-pack/test/functional/es_archives/hybrid/kibana/mappings.json index a352155899e79..af4ee5ed1e46f 100644 --- a/x-pack/test/functional/es_archives/hybrid/kibana/mappings.json +++ b/x-pack/test/functional/es_archives/hybrid/kibana/mappings.json @@ -35,7 +35,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "25de8c2deec044392922989cfcf24c54", "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", @@ -890,47 +889,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json b/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json index 63cc283f96d32..0d41e0ce86c14 100644 --- a/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json +++ b/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json @@ -143,47 +143,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/kibana_scripted_fields_on_logstash/mappings.json b/x-pack/test/functional/es_archives/kibana_scripted_fields_on_logstash/mappings.json index c0ce939234a9d..7479c525f6a07 100644 --- a/x-pack/test/functional/es_archives/kibana_scripted_fields_on_logstash/mappings.json +++ b/x-pack/test/functional/es_archives/kibana_scripted_fields_on_logstash/mappings.json @@ -50,7 +50,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2522,47 +2521,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/lens/basic/mappings.json b/x-pack/test/functional/es_archives/lens/basic/mappings.json index 50230a5eff0c2..5ff0a0e4661c3 100644 --- a/x-pack/test/functional/es_archives/lens/basic/mappings.json +++ b/x-pack/test/functional/es_archives/lens/basic/mappings.json @@ -36,7 +36,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", @@ -1113,47 +1112,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/lens/reporting/mappings.json b/x-pack/test/functional/es_archives/lens/reporting/mappings.json index a71efbd3e59ec..907c5ee4fe70a 100644 --- a/x-pack/test/functional/es_archives/lens/reporting/mappings.json +++ b/x-pack/test/functional/es_archives/lens/reporting/mappings.json @@ -36,7 +36,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", @@ -1122,47 +1121,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/lens/rollup/config/mappings.json b/x-pack/test/functional/es_archives/lens/rollup/config/mappings.json index 50230a5eff0c2..5ff0a0e4661c3 100644 --- a/x-pack/test/functional/es_archives/lens/rollup/config/mappings.json +++ b/x-pack/test/functional/es_archives/lens/rollup/config/mappings.json @@ -36,7 +36,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", @@ -1113,47 +1112,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/lists/mappings.json b/x-pack/test/functional/es_archives/lists/mappings.json index 2c6e948cedf6c..134ed28bac2e7 100644 --- a/x-pack/test/functional/es_archives/lists/mappings.json +++ b/x-pack/test/functional/es_archives/lists/mappings.json @@ -71,7 +71,6 @@ "namespace": "2f4316de49999235636386fe51dc06c1", "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", "ingest-package-policies": "48e8bd97e488008e21c0b5a2367b83ad", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "config": "c63748b75f39d0c54de12d12c1ccbc20", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", @@ -2265,47 +2264,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/logstash/empty/mappings.json b/x-pack/test/functional/es_archives/logstash/empty/mappings.json index 98fb761389a24..6a3f685752258 100644 --- a/x-pack/test/functional/es_archives/logstash/empty/mappings.json +++ b/x-pack/test/functional/es_archives/logstash/empty/mappings.json @@ -209,48 +209,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json b/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json index 56a2e144ff89c..b221ce613d72d 100644 --- a/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json +++ b/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json @@ -206,48 +206,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/data.json.gz b/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/data.json.gz index e1e8437948dea..9c15082c70f02 100644 Binary files a/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/data.json.gz and b/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/observability/alerts/data.json.gz b/x-pack/test/functional/es_archives/observability/alerts/data.json.gz index 45da368188284..bcccaeda999c0 100644 Binary files a/x-pack/test/functional/es_archives/observability/alerts/data.json.gz and b/x-pack/test/functional/es_archives/observability/alerts/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/observability/alerts/mappings.json b/x-pack/test/functional/es_archives/observability/alerts/mappings.json index 88d12b7d797bb..63750ddafe329 100644 --- a/x-pack/test/functional/es_archives/observability/alerts/mappings.json +++ b/x-pack/test/functional/es_archives/observability/alerts/mappings.json @@ -65,8 +65,12 @@ } } }, - "id": { - "type": "keyword" + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } }, "reason": { "type": "keyword" @@ -325,8 +329,12 @@ } } }, - "id": { - "type": "keyword" + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } }, "reason": { "type": "keyword" @@ -561,8 +569,12 @@ } } }, - "id": { - "type": "keyword" + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } }, "reason": { "type": "keyword" diff --git a/x-pack/test/functional/es_archives/packaging/mappings.json b/x-pack/test/functional/es_archives/packaging/mappings.json index 182d281c9a3de..0ec1e12567460 100644 --- a/x-pack/test/functional/es_archives/packaging/mappings.json +++ b/x-pack/test/functional/es_archives/packaging/mappings.json @@ -74,7 +74,6 @@ "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "tag": "83d55da58f6530f7055415717ec06474", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2283,47 +2282,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json b/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json index aa097f9bb214e..5bffb9540e983 100644 --- a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json @@ -61,7 +61,6 @@ "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -1971,47 +1970,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/mappings.json b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/mappings.json index 9d6c0ecf9898d..254184cbef584 100644 --- a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/mappings.json @@ -66,7 +66,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2388,47 +2387,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json b/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json index 8580f216a06f6..1fdde9d9d208b 100644 --- a/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json @@ -2307,47 +2307,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/reporting/logs/mappings.json b/x-pack/test/functional/es_archives/reporting/logs/mappings.json index ffb8c85e8fd8b..2e1873e43ffcc 100644 --- a/x-pack/test/functional/es_archives/reporting/logs/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/logs/mappings.json @@ -195,48 +195,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json b/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json index 97b9599bc86cc..f950877aea332 100644 --- a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json @@ -41,7 +41,6 @@ "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -1836,47 +1835,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/functional/es_archives/reporting/nanos/mappings.json b/x-pack/test/functional/es_archives/reporting/nanos/mappings.json index 86451ee4303af..216b89e4bfbcf 100644 --- a/x-pack/test/functional/es_archives/reporting/nanos/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/nanos/mappings.json @@ -34,7 +34,6 @@ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "space": "25de8c2deec044392922989cfcf24c54", "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", @@ -881,47 +880,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/reporting/sales/mappings.json b/x-pack/test/functional/es_archives/reporting/sales/mappings.json index dbb8d396eb496..317b185046ce1 100644 --- a/x-pack/test/functional/es_archives/reporting/sales/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/sales/mappings.json @@ -195,48 +195,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json b/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json index 05ca4d8e8307e..bb863dc24c585 100644 --- a/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json +++ b/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json @@ -358,47 +358,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json b/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json index d860c19ceb64c..092f8a326d9df 100644 --- a/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json @@ -178,47 +178,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json b/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json index 23a63e372a855..5bda0101c653a 100644 --- a/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json @@ -358,47 +358,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json b/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json index 9f3201d73abc1..f813fca64c328 100644 --- a/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json @@ -180,48 +180,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json b/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json index 499c994780285..07dc66dd8ce94 100644 --- a/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json @@ -177,48 +177,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/spaces/selector/mappings.json b/x-pack/test/functional/es_archives/spaces/selector/mappings.json index 499c994780285..07dc66dd8ce94 100644 --- a/x-pack/test/functional/es_archives/spaces/selector/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/selector/mappings.json @@ -177,48 +177,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json b/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json index 6ec81326d1ca4..f35681efb089d 100644 --- a/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json +++ b/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json @@ -75,7 +75,6 @@ "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "tag": "83d55da58f6530f7055415717ec06474", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", diff --git a/x-pack/test/functional/es_archives/timelion/feature_controls/data.json b/x-pack/test/functional/es_archives/timelion/feature_controls/data.json index 03fc0d57e9278..59da22294aa5a 100644 --- a/x-pack/test/functional/es_archives/timelion/feature_controls/data.json +++ b/x-pack/test/functional/es_archives/timelion/feature_controls/data.json @@ -29,53 +29,4 @@ } } } -} - -{ - "type": "doc", - "value": { - "index": ".kibana", - "id": "timelion-sheet:i-exist", - "source": { - "timelion-sheet": { - "title": "i-exist", - "hits": 0, - "description": "", - "timelion_sheet": [ - ".es(*)" - ], - "timelion_interval": "auto", - "timelion_chart_height": 275, - "timelion_columns": 2, - "timelion_rows": 2, - "version": 1 - }, - "type": "timelion-sheet" - } - } -} - -{ - "type": "doc", - "value": { - "index": ".kibana", - "id": "custom_space:timelion-sheet:i-exist", - "source": { - "namespace": "custom_space", - "timelion-sheet": { - "title": "i-exist", - "hits": 0, - "description": "", - "timelion_sheet": [ - ".es(*).label('custom space sheet')" - ], - "timelion_interval": "auto", - "timelion_chart_height": 275, - "timelion_columns": 2, - "timelion_rows": 2, - "version": 1 - }, - "type": "timelion-sheet" - } - } } \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json b/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json index 23a63e372a855..5bda0101c653a 100644 --- a/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json +++ b/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json @@ -358,47 +358,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/es_archives/visualize/default/mappings.json b/x-pack/test/functional/es_archives/visualize/default/mappings.json index be3b287032911..00dddbcafdd1c 100644 --- a/x-pack/test/functional/es_archives/visualize/default/mappings.json +++ b/x-pack/test/functional/es_archives/visualize/default/mappings.json @@ -79,7 +79,6 @@ "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", "tag": "83d55da58f6530f7055415717ec06474", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "type": "2f4316de49999235636386fe51dc06c1", "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -2423,47 +2422,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json b/x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json deleted file mode 100644 index 323dbb67d54b8..0000000000000 --- a/x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "attributes": { - "buildNum": 9007199254740991, - "defaultIndex": "logstash-*" - }, - "coreMigrationVersion": "7.14.0", - "id": "7.0.0", - "migrationVersion": { - "config": "7.13.0" - }, - "references": [], - "type": "config", - "updated_at": "2019-01-22T19:32:02.235Z", - "version": "WzQsMl0=" -} - -{ - "attributes": { - "description": "", - "hits": 0, - "timelion_chart_height": 275, - "timelion_columns": 2, - "timelion_interval": "auto", - "timelion_rows": 2, - "timelion_sheet": [ - ".es(*)" - ], - "title": "i-exist", - "version": 1 - }, - "coreMigrationVersion": "7.14.0", - "id": "i-exist", - "references": [], - "type": "timelion-sheet", - "version": "WzYsMl0=" -} - -{ - "attributes": { - "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kilobytes\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['bytes'].value / 1000\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"machine os raw\",\"type\":\"string\",\"count\":0,\"scripted\":true,\"script\":\"doc['machine.os.raw'].value\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", - "timeFieldName": "@timestamp", - "title": "logstash-*" - }, - "coreMigrationVersion": "7.14.0", - "id": "logstash-*", - "migrationVersion": { - "index-pattern": "7.11.0" - }, - "references": [], - "type": "index-pattern", - "version": "WzUsMl0=" -} \ No newline at end of file diff --git a/x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json b/x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json deleted file mode 100644 index f27149f9d7eb6..0000000000000 --- a/x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "attributes": { - "description": "", - "hits": 0, - "timelion_chart_height": 275, - "timelion_columns": 2, - "timelion_interval": "auto", - "timelion_rows": 2, - "timelion_sheet": [ - ".es(*).label('custom space sheet')" - ], - "title": "i-exist", - "version": 1 - }, - "coreMigrationVersion": "7.14.0", - "id": "i-exist", - "references": [], - "type": "timelion-sheet", - "version": "WzcsMl0=" -} \ No newline at end of file diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index 9af9aaa45be5c..65621c4959d37 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -241,6 +241,11 @@ export function MachineLearningCommonUIProvider({ channelTolerance = 10, valueTolerance = 10 ) { + if (process.env.TEST_CLOUD) { + log.warning('Not running color assertions in cloud'); + return; + } + await retry.tryForTime(30 * 1000, async () => { await testSubjects.existOrFail(dataTestSubj); diff --git a/x-pack/test/functional/services/observability/alerts.ts b/x-pack/test/functional/services/observability/alerts.ts new file mode 100644 index 0000000000000..ba7f952b30c64 --- /dev/null +++ b/x-pack/test/functional/services/observability/alerts.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import querystring from 'querystring'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; + +// Based on the x-pack/test/functional/es_archives/observability/alerts archive. +const DATE_WITH_DATA = { + rangeFrom: '2021-09-01T13:36:22.109Z', + rangeTo: '2021-09-03T13:36:22.109Z', +}; + +const ALERTS_FLYOUT_SELECTOR = 'alertsFlyout'; + +export function ObservabilityAlertsProvider({ getPageObjects, getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const flyoutService = getService('flyout'); + const pageObjects = getPageObjects(['common']); + const retry = getService('retry'); + + const navigateToTimeWithData = async () => { + return await pageObjects.common.navigateToUrlWithBrowserHistory( + 'observability', + '/alerts', + `?${querystring.stringify(DATE_WITH_DATA)}` + ); + }; + + const getTableCells = async () => { + // NOTE: This isn't ideal, but EuiDataGrid doesn't really have the concept of "rows" + return await testSubjects.findAll('dataGridRowCell'); + }; + + const getTableOrFail = async () => { + return await testSubjects.existOrFail('events-viewer-panel'); + }; + + const getNoDataStateOrFail = async () => { + return await testSubjects.existOrFail('tGridEmptyState'); + }; + + // Query Bar + const getQueryBar = async () => { + return await testSubjects.find('queryInput'); + }; + + const getQuerySubmitButton = async () => { + return await testSubjects.find('querySubmitButton'); + }; + + const clearQueryBar = async () => { + return await (await getQueryBar()).clearValueWithKeyboard({ charByChar: true }); + }; + + const typeInQueryBar = async (query: string) => { + return await (await getQueryBar()).type(query); + }; + + const submitQuery = async (query: string) => { + await typeInQueryBar(query); + return await (await getQuerySubmitButton()).click(); + }; + + // Flyout + const getOpenFlyoutButton = async () => { + return await testSubjects.find('openFlyoutButton'); + }; + + const openAlertsFlyout = async () => { + await (await getOpenFlyoutButton()).click(); + await retry.waitFor( + 'flyout open', + async () => await testSubjects.exists(ALERTS_FLYOUT_SELECTOR, { timeout: 2500 }) + ); + }; + + const getAlertsFlyout = async () => { + return await testSubjects.find(ALERTS_FLYOUT_SELECTOR); + }; + + const getAlertsFlyoutOrFail = async () => { + return await testSubjects.existOrFail(ALERTS_FLYOUT_SELECTOR); + }; + + const getAlertsFlyoutTitle = async () => { + return await testSubjects.find('alertsFlyoutTitle'); + }; + + const closeAlertsFlyout = async () => { + return await flyoutService.close(ALERTS_FLYOUT_SELECTOR); + }; + + const getAlertsFlyoutViewInAppButtonOrFail = async () => { + return await testSubjects.existOrFail('alertsFlyoutViewInAppButton'); + }; + + const getAlertsFlyoutDescriptionListTitles = async (): Promise => { + const flyout = await getAlertsFlyout(); + return await testSubjects.findAllDescendant('alertsFlyoutDescriptionListTitle', flyout); + }; + + const getAlertsFlyoutDescriptionListDescriptions = async (): Promise => { + const flyout = await getAlertsFlyout(); + return await testSubjects.findAllDescendant('alertsFlyoutDescriptionListDescription', flyout); + }; + + return { + clearQueryBar, + typeInQueryBar, + submitQuery, + getTableCells, + getTableOrFail, + getNoDataStateOrFail, + openAlertsFlyout, + getAlertsFlyout, + getAlertsFlyoutTitle, + closeAlertsFlyout, + navigateToTimeWithData, + getAlertsFlyoutOrFail, + getAlertsFlyoutViewInAppButtonOrFail, + getAlertsFlyoutDescriptionListTitles, + getAlertsFlyoutDescriptionListDescriptions, + }; +} diff --git a/x-pack/test/functional/services/observability/index.ts b/x-pack/test/functional/services/observability/index.ts index 14f931d93b56f..0d167ae5d516e 100644 --- a/x-pack/test/functional/services/observability/index.ts +++ b/x-pack/test/functional/services/observability/index.ts @@ -7,11 +7,14 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { ObservabilityUsersProvider } from './users'; +import { ObservabilityAlertsProvider } from './alerts'; export function ObservabilityProvider(context: FtrProviderContext) { const users = ObservabilityUsersProvider(context); + const alerts = ObservabilityAlertsProvider(context); return { users, + alerts, }; } diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index 607f2ee120ed1..39809d4805064 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -22,6 +22,7 @@ export type HistogramCharts = Array<{ export function TransformWizardProvider({ getService, getPageObjects }: FtrProviderContext) { const aceEditor = getService('aceEditor'); const canvasElement = getService('canvasElement'); + const log = getService('log'); const testSubjects = getService('testSubjects'); const comboBox = getService('comboBox'); const retry = getService('retry'); @@ -106,7 +107,6 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi // To determine row and column of a cell, we're utilizing the screen reader // help text, which enumerates the rows and columns 1-based. const cells = $.findTestSubjects('dataGridRowCell') - .find('.euiDataGridRowCell__truncate') .toArray() .map((cell) => { const cellText = $(cell).text(); @@ -241,6 +241,11 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi }, async assertIndexPreviewHistogramCharts(expectedHistogramCharts: HistogramCharts) { + if (process.env.TEST_CLOUD) { + log.warning('Not running color assertions in cloud'); + return; + } + // For each chart, get the content of each header cell and assert // the legend text and column id and if the chart should be present or not. await retry.tryForTime(5000, async () => { diff --git a/x-pack/test/observability_functional/apps/observability/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/alerts/index.ts index ae60eff1859ba..2009f46f432d4 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/index.ts @@ -6,32 +6,29 @@ */ import expect from '@kbn/expect'; -import querystring from 'querystring'; import { FtrProviderContext } from '../../../ftr_provider_context'; -// Based on the x-pack/test/functional/es_archives/observability/alerts archive. -const DATE_WITH_DATA = { - rangeFrom: '2021-08-31T13:36:22.109Z', - rangeTo: '2021-09-01T13:36:22.109Z', -}; +async function asyncForEach(array: T[], callback: (item: T, index: number) => void) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index); + } +} export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); - // FLAKY: https://github.com/elastic/kibana/issues/110920 + // Failing: See https://github.com/elastic/kibana/issues/111907 describe.skip('Observability alerts', function () { this.tags('includeFirefox'); const pageObjects = getPageObjects(['common']); const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const observability = getService('observability'); before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); - await pageObjects.common.navigateToUrlWithBrowserHistory( - 'observability', - '/alerts', - `?${querystring.stringify(DATE_WITH_DATA)}` - ); + await observability.alerts.navigateToTimeWithData(); }); after(async () => { @@ -40,13 +37,123 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Alerts table', () => { it('Renders the table', async () => { - await testSubjects.existOrFail('events-viewer-panel'); + await observability.alerts.getTableOrFail(); }); it('Renders the correct number of cells', async () => { // NOTE: This isn't ideal, but EuiDataGrid doesn't really have the concept of "rows" - const cells = await testSubjects.findAll('dataGridRowCell'); - expect(cells.length).to.be(54); + const cells = await observability.alerts.getTableCells(); + expect(cells.length).to.be(72); + }); + + describe('Filtering', () => { + afterEach(async () => { + await observability.alerts.clearQueryBar(); + }); + + after(async () => { + // NOTE: We do this as the query bar takes the place of the datepicker when it is in focus, so we'll reset + // back to default. + await observability.alerts.submitQuery(''); + }); + + it('Autocompletion works', async () => { + await observability.alerts.typeInQueryBar('kibana.alert.s'); + await testSubjects.existOrFail('autocompleteSuggestion-field-kibana.alert.start-'); + await testSubjects.existOrFail('autocompleteSuggestion-field-kibana.alert.status-'); + }); + + it('Applies filters correctly', async () => { + await observability.alerts.submitQuery('kibana.alert.status: recovered'); + await retry.try(async () => { + const cells = await observability.alerts.getTableCells(); + expect(cells.length).to.be(24); + }); + }); + + it('Displays a no data state when filters produce zero results', async () => { + await observability.alerts.submitQuery('kibana.alert.consumer: uptime'); + await observability.alerts.getNoDataStateOrFail(); + }); + }); + + describe('Date selection', () => { + after(async () => { + await observability.alerts.navigateToTimeWithData(); + }); + + it('Correctly applies date picker selections', async () => { + await retry.try(async () => { + await (await testSubjects.find('superDatePickerToggleQuickMenuButton')).click(); + // We shouldn't expect any data for the last 15 minutes + await (await testSubjects.find('superDatePickerCommonlyUsed_Last_15 minutes')).click(); + }); + await observability.alerts.getNoDataStateOrFail(); + await pageObjects.common.waitUntilUrlIncludes('rangeFrom=now-15m&rangeTo=now'); + }); + }); + + describe('Flyout', () => { + it('Can be opened', async () => { + await observability.alerts.openAlertsFlyout(); + await observability.alerts.getAlertsFlyoutOrFail(); + }); + + it('Can be closed', async () => { + await observability.alerts.closeAlertsFlyout(); + await testSubjects.missingOrFail('alertsFlyout'); + }); + + describe('When open', async () => { + before(async () => { + await observability.alerts.openAlertsFlyout(); + }); + + after(async () => { + await observability.alerts.closeAlertsFlyout(); + }); + + it('Displays the correct title', async () => { + const titleText = await ( + await observability.alerts.getAlertsFlyoutTitle() + ).getVisibleText(); + expect(titleText).to.contain('Log threshold'); + }); + + it('Displays the correct content', async () => { + const flyoutTitles = await observability.alerts.getAlertsFlyoutDescriptionListTitles(); + const flyoutDescriptions = await observability.alerts.getAlertsFlyoutDescriptionListDescriptions(); + + const expectedTitles = [ + 'Status', + 'Last updated', + 'Duration', + 'Expected value', + 'Actual value', + 'Rule type', + ]; + const expectedDescriptions = [ + 'Active', + 'Sep 2, 2021 @ 12:54:09.674', + '15 minutes', + '100.25', + '1957', + 'Log threshold', + ]; + + await asyncForEach(flyoutTitles, async (title, index) => { + expect(await title.getVisibleText()).to.be(expectedTitles[index]); + }); + + await asyncForEach(flyoutDescriptions, async (description, index) => { + expect(await description.getVisibleText()).to.be(expectedDescriptions[index]); + }); + }); + + it('Displays a View in App button', async () => { + await observability.alerts.getAlertsFlyoutViewInAppButtonOrFail(); + }); + }); }); }); }); diff --git a/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json b/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json index 29c56a751c038..a4392cd88b356 100644 --- a/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json +++ b/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json @@ -386,47 +386,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts index 0d1839c7a138f..b5f75ed7d501c 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts @@ -36,6 +36,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.reporting.capture.networkPolicy.rules=${JSON.stringify(testPolicyRules)}`, `--xpack.reporting.capture.maxAttempts=1`, `--xpack.reporting.csv.maxSizeBytes=6000`, + '--xpack.reporting.roles.enabled=false', // Reporting access control is implemented by sub-feature application privileges ], }, }; diff --git a/x-pack/test/reporting_functional/reporting_and_security.config.ts b/x-pack/test/reporting_functional/reporting_and_security.config.ts index 536695b4c6dc9..3037aeacde033 100644 --- a/x-pack/test/reporting_functional/reporting_and_security.config.ts +++ b/x-pack/test/reporting_functional/reporting_and_security.config.ts @@ -25,6 +25,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.get('kbnTestServer.serverArgs'), `--xpack.reporting.capture.maxAttempts=1`, `--xpack.reporting.csv.maxSizeBytes=6000`, + '--xpack.reporting.roles.enabled=false', // Reporting access control is implemented by sub-feature application privileges ], }, services: { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json index 47f23dc57e361..6caac5e31e1f6 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json @@ -22,7 +22,6 @@ "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", @@ -406,47 +405,6 @@ } } }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "tsvb-validation-telemetry": { "properties": { "failedRequests": { diff --git a/x-pack/test/security_api_integration/fixtures/saml/idp_metadata_never_login.xml b/x-pack/test/security_api_integration/fixtures/saml/idp_metadata_never_login.xml new file mode 100644 index 0000000000000..44b2ede5060ff --- /dev/null +++ b/x-pack/test/security_api_integration/fixtures/saml/idp_metadata_never_login.xml @@ -0,0 +1,41 @@ + + + + + + + + MIIDOTCCAiGgAwIBAgIVANNWkg9lzNiLqNkMFhFKHcXyaZmqMA0GCSqGSIb3DQEB +CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu +ZXJhdGVkIENBMCAXDTE5MTIyNzE3MDM0MloYDzIwNjkxMjE0MTcwMzQyWjARMQ8w +DQYDVQQDEwZraWJhbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQ +wYYbQtbRBKJ4uNZc2+IgRU+7NNL21ZebQlEIMgK7jAqOMrsW2b5DATz41Fd+GQFU +FUYYjwo+PQj6sJHshOJo/gNb32HrydvMI7YPvevkszkuEGCfXxQ3Dw2RTACLgD0Q +OCkwHvn3TMf0loloV/ePGWaZDYZaXi3a5DdWi/HFFoJysgF0JV2f6XyKhJkGaEfJ +s9pWX269zH/XQvGNx4BEimJpYB8h4JnDYPFIiQdqj+sl2b+kS1hH9kL5gBAMXjFU +vcNnX+PmyTjyJrGo75k0ku+spBf1bMwuQt3uSmM+TQIXkvFDmS0DOVESrpA5EC1T +BUGRz6o/I88Xx4Mud771AgMBAAGjYzBhMB0GA1UdDgQWBBQLB1Eo23M3Ss8MsFaz +V+Twcb3PmDAfBgNVHSMEGDAWgBQa7SYOe8NGcF00EbwPHA91YCsHSTAUBgNVHREE +DTALgglsb2NhbGhvc3QwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEAnEl/ +z5IElIjvkK4AgMPrNcRlvIGDt2orEik7b6Jsq6/RiJQ7cSsYTZf7xbqyxNsUOTxv ++frj47MEN448H2nRvUxH29YR3XygV5aEwADSAhwaQWn0QfWTCZbJTmSoNEDtDOzX +TGDlAoCD9s9Xz9S1JpxY4H+WWRZrBSDM6SC1c6CzuEeZRuScNAjYD5mh2v6fOlSy +b8xJWSg0AFlJPCa3ZsA2SKbNqI0uNfJTnkXRm88Z2NHcgtlADbOLKauWfCrpgsCk +cZgo6yAYkOM148h/8wGla1eX+iE1R72NUABGydu8MSQKvc0emWJkGsC1/KqPlf/O +eOUsdwn1yDKHRxDHyA== + + + + + + + + + + diff --git a/x-pack/test/security_api_integration/fixtures/saml/saml_provider/server/init_routes.ts b/x-pack/test/security_api_integration/fixtures/saml/saml_provider/server/init_routes.ts index 8d6529ad6213d..6d2a8c7b4b529 100644 --- a/x-pack/test/security_api_integration/fixtures/saml/saml_provider/server/init_routes.ts +++ b/x-pack/test/security_api_integration/fixtures/saml/saml_provider/server/init_routes.ts @@ -55,4 +55,25 @@ export function initRoutes(core: CoreSetup) { return response.redirected({ headers: { location: '/logout?SAMLResponse=something' } }); } ); + + let attemptsCounter = 0; + core.http.resources.register( + { + path: '/saml_provider/never_login', + validate: false, + options: { authRequired: false }, + }, + async (context, request, response) => { + return response.renderHtml({ + body: ` + + Kibana SAML Login + + + Attempt #${++attemptsCounter} + + `, + }); + } + ); } diff --git a/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts b/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts index 47d942db947f5..db9715f7e48d5 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts @@ -56,8 +56,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -65,7 +63,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -79,7 +76,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": false, @@ -91,8 +87,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -100,7 +94,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -114,7 +107,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": false, @@ -126,8 +118,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -135,7 +125,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -149,7 +138,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": false, @@ -180,8 +168,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -189,7 +175,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -203,7 +188,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": false, @@ -215,8 +199,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -224,7 +206,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -238,7 +219,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": false, @@ -250,8 +230,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -259,7 +237,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -273,7 +250,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": false, @@ -322,8 +298,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": true, "showWriteControls": false, @@ -331,7 +305,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": true, @@ -345,7 +318,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": true, @@ -359,8 +331,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -368,7 +338,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -382,7 +351,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": false, @@ -396,8 +364,6 @@ export default function ({ getService }: FtrProviderContext) { "dashboard": Object { "createNew": false, "createShortUrl": false, - "downloadCsv": false, - "generateScreenshot": false, "saveQuery": false, "show": false, "showWriteControls": false, @@ -405,7 +371,6 @@ export default function ({ getService }: FtrProviderContext) { }, "discover": Object { "createShortUrl": false, - "generateCsv": false, "save": false, "saveQuery": false, "show": false, @@ -419,7 +384,6 @@ export default function ({ getService }: FtrProviderContext) { "visualize": Object { "createShortUrl": false, "delete": false, - "generateScreenshot": false, "save": false, "saveQuery": false, "show": true, diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 4c6db9ef258bb..0836897bb3f00 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -117,6 +117,30 @@ export default function ({ getService }: FtrProviderContext) { ); }); + it('preserves auth_provider_hint when accessing Kibana with intermediate authentication cookie', async () => { + const handshakeResponse = await supertest + .post('/internal/security/login') + .ca(CA_CERT) + .set('kbn-xsrf', 'xxx') + .send({ providerType: 'saml', providerName: 'saml1', currentURL: 'https://kibana.com/' }) + .expect(200); + + // The cookie that includes some state of the in-progress authentication, that doesn't allow + // to fully authenticate user yet. + const intermediateAuthCookie = parseCookie(handshakeResponse.headers['set-cookie'][0])!; + + // When user tries to access any other page in Kibana. + const response = await supertest + .get('/abc/xyz/handshake?one=two three&auth_provider_hint=saml1') + .ca(CA_CERT) + .set('Cookie', intermediateAuthCookie.cookieString()) + .expect(302); + expect(response.headers['set-cookie']).to.be(undefined); + expect(response.headers.location).to.be( + '/login?next=%2Fabc%2Fxyz%2Fhandshake%3Fone%3Dtwo%2520three%26auth_provider_hint%3Dsaml1&auth_provider_hint=saml1' + ); + }); + describe('SAML', () => { function createSAMLResponse(options = {}) { return getSAMLResponse({ diff --git a/x-pack/test/security_functional/login_selector.config.ts b/x-pack/test/security_functional/login_selector.config.ts index e30775af7e784..aa145e2ec6216 100644 --- a/x-pack/test/security_functional/login_selector.config.ts +++ b/x-pack/test/security_functional/login_selector.config.ts @@ -25,6 +25,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { __dirname, '../security_api_integration/fixtures/saml/saml_provider/metadata.xml' ); + const idpNeverLoginPath = resolve( + __dirname, + '../security_api_integration/fixtures/saml/idp_metadata_never_login.xml' + ); const samlIdPPlugin = resolve( __dirname, '../security_api_integration/fixtures/saml/saml_provider' @@ -53,6 +57,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `xpack.security.authc.realms.saml.saml1.sp.logout=http://localhost:${kibanaPort}/logout`, `xpack.security.authc.realms.saml.saml1.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, 'xpack.security.authc.realms.saml.saml1.attributes.principal=urn:oid:0.0.7', + 'xpack.security.authc.realms.saml.saml_never.order=2', + `xpack.security.authc.realms.saml.saml_never.idp.metadata.path=${idpNeverLoginPath}`, + 'xpack.security.authc.realms.saml.saml_never.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.saml_never.sp.entity_id=http://localhost:${kibanaPort}`, + `xpack.security.authc.realms.saml.saml_never.sp.logout=http://localhost:${kibanaPort}/logout`, + `xpack.security.authc.realms.saml.saml_never.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, + 'xpack.security.authc.realms.saml.saml_never.attributes.principal=urn:oid:0.0.7', ], }, @@ -80,6 +91,12 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { description: 'Do-not-log-in-with-THIS-SAML', icon: 'logoAWS', }, + saml_never: { + order: 4, + realm: 'saml_never', + description: 'Never-log-in-with-SAML', + icon: 'logoKibana', + }, }, anonymous: { anonymous1: { diff --git a/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts b/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts index b94b93d920da4..b19af792d8af6 100644 --- a/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts +++ b/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const security = getService('security'); const deployment = getService('deployment'); + const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['security', 'common']); describe('Authentication provider hint', function () { @@ -88,6 +89,44 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); + it('re-initiates SSO handshake even with unauthenticated session', async () => { + // 1. Try to authenticate with SAML that never completes SAML handshake. In this case we end + // up with the cookie pointing to the intermediate unauthenticated session. + await PageObjects.common.navigateToUrlWithBrowserHistory( + 'management', + '/security/users', + '?auth_provider_hint=saml_never', + { ensureCurrentUrl: false, shouldLoginIfPrompted: false } + ); + await testSubjects.stringExistsInCodeBlockOrFail('idp-page', 'Attempt #1'); + + // 2. Now navigate to the same URL again and make sure we're still automatically redirected to IDP. + await PageObjects.common.navigateToUrlWithBrowserHistory( + 'management', + '/security/users', + '?auth_provider_hint=saml_never', + { ensureCurrentUrl: false, shouldLoginIfPrompted: false } + ); + await testSubjects.stringExistsInCodeBlockOrFail('idp-page', 'Attempt #2'); + + // 3. Finally try another SSO provider. + await PageObjects.common.navigateToUrlWithBrowserHistory( + 'management', + '/security/users', + '?auth_provider_hint=saml1', + { ensureCurrentUrl: false, shouldLoginIfPrompted: false } + ); + + await PageObjects.common.waitUntilUrlIncludes('/app/management/security/users'); + + const currentURL = parse(await browser.getCurrentUrl()); + expect(currentURL.pathname).to.eql('/app/management/security/users'); + expect((await PageObjects.security.getCurrentUser())?.authentication_provider).to.eql({ + type: 'saml', + name: 'saml1', + }); + }); + it('can login anonymously preserving original URL', async () => { await PageObjects.common.navigateToUrlWithBrowserHistory( 'management', diff --git a/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json b/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json index 499c994780285..07dc66dd8ce94 100644 --- a/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json @@ -177,48 +177,6 @@ "spaceId": { "type": "keyword" }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "type": { "type": "keyword" }, diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 06398fdcd9658..a793582cb7295 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -306,7 +306,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, }, }, @@ -322,7 +322,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, }, }, @@ -348,11 +348,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, memory_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, ransomware: { enabled: true, @@ -530,7 +530,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, }, }, @@ -546,7 +546,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, }, }, @@ -572,11 +572,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, memory_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, ransomware: { enabled: true, @@ -751,7 +751,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, }, }, @@ -767,7 +767,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, }, }, @@ -793,11 +793,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, memory_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, behavior_protection: { enabled: true, - message: 'Elastic Security {action} {filename}', + message: 'Elastic Security {action} {rule}', }, ransomware: { enabled: true, diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts index 95fd914d32b07..684df902bb499 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts @@ -37,14 +37,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { SHA256 ); await testSubjects.click('addTrustedAppFlyout-createButton'); - expect(await testSubjects.getVisibleText('conditionValue')).to.equal(SHA256.toLowerCase()); + expect( + await testSubjects.getVisibleText('trustedAppCard-criteriaConditions-condition') + ).to.equal( + 'AND process.hash.*IS a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476' + ); await pageObjects.common.closeToast(); // Remove it - await testSubjects.click('trustedAppDeleteButton'); + await pageObjects.trustedApps.clickCardActionMenu(); + await testSubjects.click('deleteTrustedAppAction'); await testSubjects.click('trustedAppDeletionConfirm'); await testSubjects.waitForDeleted('trustedAppDeletionConfirm'); - expect(await testSubjects.existOrFail('trustedAppEmptyState')); + // We only expect one trusted app to have been visible + await testSubjects.missingOrFail('trustedAppCard'); }); }); }; diff --git a/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts b/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts index e32fa5e738f89..1678358acc11e 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts @@ -35,5 +35,13 @@ export function TrustedAppsPageProvider({ getService, getPageObjects }: FtrProvi await this.ensureIsOnTrustedAppsListPage(); return testSubjects.find('backToOrigin'); }, + + /** + * Clicks on the actions menu icon in the (only one) truated app card to show the popup with list of actions + */ + async clickCardActionMenu() { + await this.ensureIsOnTrustedAppsListPage(); + await testSubjects.click('trustedAppCard-header-actions-button'); + }, }; } diff --git a/x-pack/test/security_solution_endpoint/services/endpoint.ts b/x-pack/test/security_solution_endpoint/services/endpoint.ts index ae9e714315289..4b5b8d0717889 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint.ts @@ -189,7 +189,9 @@ export class EndpointTestResources extends FtrService { * installs (or upgrades) the Endpoint Fleet package * (NOTE: ensure that fleet is setup first before calling this function) */ - async installOrUpgradeEndpointFleetPackage(): Promise { + async installOrUpgradeEndpointFleetPackage(): ReturnType< + typeof installOrUpgradeEndpointFleetPackage + > { return installOrUpgradeEndpointFleetPackage(this.kbnClient); } } diff --git a/yarn.lock b/yarn.lock index bdf256424c5eb..0cb3876c0d2ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1487,10 +1487,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@37.3.1": - version "37.3.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-37.3.1.tgz#077066c3fb614d6d95b16af7185a3e940e84c38d" - integrity sha512-65yUfDnfVWwDJyUX7S/yNiYBwLL6hvARProoHPzSdp3hTWLgMKyhXFVdwqMgjTeyXt/69Haiza+fAzzW174w/w== +"@elastic/eui@37.6.0": + version "37.6.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-37.6.0.tgz#dc4fb2431e223047fa52b9035627c05a22197887" + integrity sha512-CeaKhwZCbTaq2h0cn3s9t0kGr+P+khVtYNa72zAVsH1Vhb6Ox0Z7cnvmn49qyjb3GnOHSYtQlbyrAcxqstfwbw== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" @@ -4913,11 +4913,6 @@ "@types/node" "*" "@types/responselike" "*" -"@types/caseless@*": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" - integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== - "@types/chance@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df" @@ -5827,11 +5822,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/proper-lockfile@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/proper-lockfile/-/proper-lockfile-3.0.1.tgz#dd770a2abce3adbcce3bd1ed892ce2f5f17fbc86" - integrity sha512-ODOjqxmaNs0Zkij+BJovsNJRSX7BJrr681o8ZnNTNIcTermvVFzLpz/XFtfg3vNrlPVTJY1l4e9h2LvHoxC1lg== - "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" @@ -6029,16 +6019,6 @@ dependencies: "@types/prismjs" "*" -"@types/request@^2.48.2": - version "2.48.2" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.2.tgz#936374cbe1179d7ed529fc02543deb4597450fed" - integrity sha512-gP+PSFXAXMrd5PcD7SqHeUjdGshAI8vKQ3+AvpQr3ht9iQea+59LOKvKITcQI+Lg+1EIkDP6AFSBUJPWG8GDyA== - dependencies: - "@types/caseless" "*" - "@types/node" "*" - "@types/tough-cookie" "*" - form-data "^2.5.0" - "@types/resize-observer-browser@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.5.tgz#36d897708172ac2380cd486da7a3daf1161c1e23" @@ -6169,10 +6149,10 @@ dependencies: "@types/node" "*" -"@types/tar@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.3.tgz#e2cce0b8ff4f285293243f5971bd7199176ac489" - integrity sha512-Z7AVMMlkI8NTWF0qGhC4QIX0zkV/+y0J8x7b/RsHrN0310+YNjoJd8UrApCiGBCWtKjxS9QhNqLi2UJNToh5hA== +"@types/tar@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.5.tgz#5f953f183e36a15c6ce3f336568f6051b7b183f3" + integrity sha512-cgwPhNEabHaZcYIy5xeMtux2EmYBitfqEceBUi2t5+ETy4dW6kswt6WX4+HqLeiiKOo42EXbGiDmVJ2x+vi37Q== dependencies: "@types/minipass" "*" "@types/node" "*" @@ -6926,6 +6906,14 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.5.5, ajv@^6.9.1: version "6.12.4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234" @@ -6975,13 +6963,6 @@ angular-aria@^1.8.0: resolved "https://registry.yarnpkg.com/angular-aria/-/angular-aria-1.8.0.tgz#97aec9b1e8bafd07d5fab30f98d8ec832e18e25d" integrity sha512-eCQI6EwgY6bYHdzIUfDABHnZjoZ3bNYpCsnceQF4bLfbq1QtZ7raRPNca45sj6C9Pfjde6PNcEDvuLozFPYnrQ== -angular-elastic@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/angular-elastic/-/angular-elastic-2.5.1.tgz#e938ab1bd8c76415b8ca6514b15fe3593a5df535" - integrity sha1-6TirG9jHZBW4ymUUsV/jWTpd9TU= - dependencies: - angular ">=1.0.6" - angular-mocks@^1.7.9: version "1.7.9" resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.7.9.tgz#0a3b7e28b9a493b4e3010ed2b0f69a68e9b4f79b" @@ -6992,11 +6973,6 @@ angular-recursion@^1.0.5: resolved "https://registry.yarnpkg.com/angular-recursion/-/angular-recursion-1.0.5.tgz#cd405428a0bf55faf52eaa7988c1fe69cd930543" integrity sha1-zUBUKKC/Vfr1Lqp5iMH+ac2TBUM= -angular-resource@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/angular-resource/-/angular-resource-1.8.0.tgz#578ef122e7cb7bcc6c0ad6c2451dc3d27fd570ba" - integrity sha512-9woUq3kDwoT7R6SjKX8vaJMhOplYBm9sqRAxKgDhDIdPyA8iBowqQIusf9+8Q+z/HlXb8ZXvKspJyKXrxmKdvg== - angular-route@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/angular-route/-/angular-route-1.8.0.tgz#cb8066c5d34284ffd6a15ac7be1b3d51c5ad7bb2" @@ -7007,12 +6983,7 @@ angular-sanitize@^1.8.0: resolved "https://registry.yarnpkg.com/angular-sanitize/-/angular-sanitize-1.8.0.tgz#9f80782d3afeec3bcc0bb92b3ca6f1f421cfbca6" integrity sha512-j5GiOPCvfcDWK5svEOVoPb11X3UDVy/mdHPRWuy14Iyw86xaq+Bb+x/em2sAOa5MQQeY5ciLXbF3RRp8iCKcNg== -angular-sortable-view@^0.0.17: - version "0.0.17" - resolved "https://registry.yarnpkg.com/angular-sortable-view/-/angular-sortable-view-0.0.17.tgz#99e2679951a86b6ee6ff27b099022943c683fb4f" - integrity sha512-2WkhM0Lt/wyMyrX/+7ve9ejSegBd7A4eRBNHEIJz8XMBIOjt+3oM1WpcAm+qNThkmNmmQaDeaYv0TQZw/WDMBw== - -angular@>=1.0.6, angular@^1.8.0: +angular@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.0.tgz#b1ec179887869215cab6dfd0df2e42caa65b1b51" integrity sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg== @@ -7527,6 +7498,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= + assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" @@ -7724,11 +7700,6 @@ attr-accept@^2.0.0: resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.1.tgz#89b48de019ed4342f1865626b4389c666b3ed231" integrity sha512-GpefLMsbH5ojNgfTW+OBin2xKzuHfyeNA+qCktzZojBhbA/lPZdCFMWdwk5ajb989Ok7ZT+EADqvW3TAFNMjhA== -autobind-decorator@^1.3.4: - version "1.4.3" - resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-1.4.3.tgz#4c96ffa77b10622ede24f110f5dbbf56691417d1" - integrity sha1-TJb/p3sQYi7eJPEQ9du/VmkUF9E= - autoprefixer@^9.7.2, autoprefixer@^9.7.4: version "9.8.5" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.5.tgz#2c225de229ddafe1d1424c02791d0c3e10ccccaa" @@ -7762,11 +7733,21 @@ available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= +aws4@^1.2.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" @@ -8522,6 +8503,13 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= + dependencies: + hoek "2.x.x" + bottleneck@^2.15.3: version "2.18.0" resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.18.0.tgz#41fa63ae185b65435d789d1700334bc48222dacf" @@ -9395,11 +9383,6 @@ charenc@~0.0.1: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= -check-disk-space@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/check-disk-space/-/check-disk-space-2.1.0.tgz#2e77fe62f30d9676dc37a524ea2008f40c780295" - integrity sha512-f0nx9oJF/AVF8nhSYlF1EBvMNnO+CXyLwKhPvN1943iOMI9TWhQigLZm80jAf0wzQhwKkzA8XXjyvuVUeGGcVQ== - check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -9929,9 +9912,9 @@ combine-source-map@^0.8.0, combine-source-map@~0.8.0: lodash.memoize "~3.0.3" source-map "~0.5.3" -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.5, combined-stream@~1.0.6: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" @@ -10469,11 +10452,6 @@ create-react-context@0.3.0, create-react-context@^0.3.0: gud "^1.0.0" warning "^4.0.3" -create-react-context@^0.1.5: - version "0.1.6" - resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.1.6.tgz#0f425931d907741127acc6e31acb4f9015dd9fdc" - integrity sha512-eCnYYEUEc5i32LHwpE/W7NlddOB9oHwsPaWtWzYtflNkkwa3IfindIcoXdVWs12zCbwaMCavKNu84EXogVIWHw== - cronstrue@^1.51.0: version "1.51.0" resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-1.51.0.tgz#7a63153d61d940344049037628da38a60784c8e2" @@ -10511,6 +10489,13 @@ crypt@~0.0.1: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= + dependencies: + boom "2.x.x" + crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -11653,14 +11638,6 @@ defined@^1.0.0, defined@~1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= -del-cli@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/del-cli/-/del-cli-3.0.1.tgz#2d27ff260204b5104cadeda86f78f180a4ebe89a" - integrity sha512-BLHItGr82rUbHhjMu41d+vw9Md49i81jmZSV00HdTq4t+RTHywmEht/23mNFpUl2YeLYJZJyGz4rdlMAyOxNeg== - dependencies: - del "^5.1.0" - meow "^6.1.1" - del@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" @@ -13467,7 +13444,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@~3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -13831,14 +13808,6 @@ filesize@6.1.0: resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== -fill-keys@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" - integrity sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA= - dependencies: - is-object "~1.0.1" - merge-descriptors "~1.0.0" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -14044,20 +14013,6 @@ focus-lock@^0.7.0: resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a" integrity sha512-LI7v2mH02R55SekHYdv9pRHR9RajVNyIJ2N5IEkWbg7FT5ZmJ9Hw4mWxHeEUcd+dJo0QmzztHvDvWcc7prVFsw== -focus-trap-react@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-3.1.2.tgz#4dd021ccd028bbd3321147d132cdf7585d6d1394" - integrity sha512-MoQmONoy9gRPyrC5DGezkcOMGgx7MtIOAQDHe098UtL2sA2vmucJwEmQisb+8LRXNYFHxuw5zJ1oLFeKu4Mteg== - dependencies: - focus-trap "^2.0.1" - -focus-trap@^2.0.1: - version "2.4.5" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.5.tgz#91c9c9ffb907f8f4446d80202dda9c12c2853ddb" - integrity sha512-jkz7Dh6Pb4ox+z24GhVABDE7lFT19z7KVrpYGH5qqI6KK3Y2IcXhBx844W6ZXYahD+jOEUcGz49dLakXg2sjOQ== - dependencies: - tabbable "^1.0.3" - folktale@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/folktale/-/folktale-2.0.1.tgz#6dc26a65565aefdef9520223e022dddf5b8d8462" @@ -14155,12 +14110,7 @@ fork-ts-checker-webpack-plugin@4.1.6, fork-ts-checker-webpack-plugin@^4.1.4: tapable "^1.0.0" worker-rpc "^0.1.0" -form-data-to-object@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" - integrity sha1-96jmjd2RChEApl4lrGpIQUP/gWg= - -form-data@^2.3.1, form-data@^2.5.0: +form-data@^2.3.1: version "2.5.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.0.tgz#094ec359dc4b55e7d62e0db4acd76e89fe874d37" integrity sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA== @@ -14187,6 +14137,15 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -14211,14 +14170,6 @@ formidable@^1.2.0: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== -formsy-react@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" - integrity sha512-nNWe4Vbp6aDQ/zSxJ7gVQgD5+avFRVbydcjA2Om42flxpQFrKE54AAbuyEj3Jvv+2b9LVl+WLMAPalyvLjwNcQ== - dependencies: - form-data-to-object "^0.2.0" - prop-types "^15.5.10" - forwarded-parse@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/forwarded-parse/-/forwarded-parse-2.1.0.tgz#1ae9d7a4be3af884f74d936d856f7d8c6abd0439" @@ -14313,13 +14264,6 @@ fs-extra@^9.0.0, fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== - dependencies: - minipass "^2.2.1" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -14589,11 +14533,6 @@ gifwrap@^0.9.2: image-q "^1.1.1" omggif "^1.0.10" -github-markdown-css@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/github-markdown-css/-/github-markdown-css-2.10.0.tgz#0612fed22816b33b282f37ef8def7a4ecabfe993" - integrity sha512-RX5VUC54uX6Lvrm226M9kMzsNeOa81MnKyxb3J0G5KLjyoOySOZgwyKFkUpv6iUhooiUZdogk+OTwQPJ4WttYg== - github-slugger@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" @@ -15231,11 +15170,24 @@ handlebars@4.7.7, handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4= + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio= + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" @@ -15495,6 +15447,16 @@ hat@0.0.3: resolved "https://registry.yarnpkg.com/hat/-/hat-0.0.3.tgz#bb014a9e64b3788aed8005917413d4ff3d502d8a" integrity sha1-uwFKnmSzeIrtgAWRdBPU/z1QLYo= +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + hdr-histogram-js@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-1.2.0.tgz#1213c0b317f39b9c05bc4f208cb7931dbbc192ae" @@ -15554,6 +15516,11 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= + hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -15837,6 +15804,15 @@ http-proxy@^1.17.0, http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -16675,7 +16651,7 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-object@^1.0.1, is-object@~1.0.1: +is-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= @@ -17696,7 +17672,7 @@ jju@~1.4.0: resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= -joi@*, joi@^17.4.0: +joi@*, joi@^17.3.0, joi@^17.4.0: version "17.4.0" resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.0.tgz#b5c2277c8519e016316e49ababd41a1908d9ef20" integrity sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg== @@ -17707,17 +17683,6 @@ joi@*, joi@^17.4.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -joi@^17.3.0: - version "17.3.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2" - integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.0" - "@sideway/formula" "^3.0.0" - "@sideway/pinpoint" "^2.0.0" - jpeg-js@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.0.4.tgz#06aaf47efec7af0b1924a59cd695a6d2b5ed870e" @@ -19321,23 +19286,6 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467" - integrity sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "^4.0.2" - normalize-package-data "^2.5.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.13.1" - yargs-parser "^18.1.3" - meow@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/meow/-/meow-7.0.1.tgz#1ed4a0a50b3844b451369c48362eb0515f04c1dc" @@ -19392,7 +19340,7 @@ meow@^9.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-descriptors@1.0.1, merge-descriptors@~1.0.0: +merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= @@ -19482,6 +19430,11 @@ mime-db@1.44.0, mime-db@1.x.x, "mime-db@>= 1.40.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== +mime-db@1.45.0: + version "1.45.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" + integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== + mime-types@^2.0.1, mime-types@^2.1.12, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" @@ -19489,6 +19442,13 @@ mime-types@^2.0.1, mime-types@^2.1.12, mime-types@^2.1.26, mime-types@^2.1.27, m dependencies: mime-db "1.44.0" +mime-types@~2.1.7: + version "2.1.28" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" + integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== + dependencies: + mime-db "1.45.0" + mime@1.6.0, mime@^1.2.11, mime@^1.3.4, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -19625,14 +19585,6 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" -minipass@^2.2.1, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minipass@^3.0.0, minipass@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" @@ -19647,13 +19599,6 @@ minipass@^3.1.0, minipass@^3.1.3: dependencies: yallist "^4.0.0" -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -19662,14 +19607,6 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -minizlib@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" - integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -19850,11 +19787,6 @@ module-details-from-path@^1.0.3: resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" integrity sha1-EUyUlnPiqKNenTV4hSeqN7Z52is= -module-not-found-error@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" - integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA= - moment-duration-format@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-2.3.2.tgz#5fa2b19b941b8d277122ff3f87a12895ec0d6212" @@ -20634,6 +20566,11 @@ nyc@^15.0.1: test-exclude "^6.0.0" yargs "^15.0.2" +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -22471,15 +22408,6 @@ propagate@^2.0.0: resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== -proper-lockfile@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-3.2.0.tgz#89ca420eea1d55d38ca552578851460067bcda66" - integrity sha512-iMghHHXv2bsxl6NchhEaFck8tvX3F9cknEEh1SUpguUOBjN7PAAW9BLzmbc1g/mCD1gY3EE2EABBHPJfFdHFmA== - dependencies: - graceful-fs "^4.1.11" - retry "^0.12.0" - signal-exit "^3.0.2" - property-information@^5.0.0, property-information@^5.0.1, property-information@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943" @@ -22534,15 +22462,6 @@ proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -proxyquire@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.8.0.tgz#02d514a5bed986f04cbb2093af16741535f79edc" - integrity sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw= - dependencies: - fill-keys "^1.0.2" - module-not-found-error "^1.0.0" - resolve "~1.1.7" - prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -22609,7 +22528,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.3.2: +punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -22691,6 +22610,11 @@ qs@^6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -23141,14 +23065,6 @@ react-input-autosize@^2.2.1, react-input-autosize@^2.2.2: dependencies: prop-types "^15.5.8" -react-input-range@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-input-range/-/react-input-range-1.3.0.tgz#f96d001631ab817417f1e26d8f9f9684b4827f59" - integrity sha1-+W0AFjGrgXQX8eJtj5+WhLSCf1k= - dependencies: - autobind-decorator "^1.3.4" - prop-types "^15.5.8" - react-inspector@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.0.1.tgz#8a30f3d488c4f40203624bbe24800f508ae05d3a" @@ -24358,7 +24274,35 @@ request-promise@^4.2.2: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.81.0, request@^2.44.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA= + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@^2.44.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -24518,7 +24462,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7, resolve@~1.1.7: +resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= @@ -25435,6 +25379,13 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^2.0.0" +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= + dependencies: + hoek "2.x.x" + sockjs-client@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" @@ -26181,6 +26132,11 @@ stringify-entities@^3.0.1: is-decimal "^1.0.2" is-hexadecimal "^1.0.0" +stringstream@~0.0.4: + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" + integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== + strip-ansi@*, strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -26601,7 +26557,7 @@ syntax-error@^1.1.1: dependencies: acorn-node "^1.2.0" -tabbable@1.1.3, tabbable@^1.0.3: +tabbable@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.3.tgz#0e4ee376f3631e42d7977a074dbd2b3827843081" integrity sha512-nOWwx35/JuDI4ONuF0ZTo6lYvI0fY0tZCH1ErzY2EXfu4az50ZyiUX8X073FLiZtmWUVlkRnuXsehjJgCw9tYg== @@ -26725,19 +26681,6 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@4.4.13: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - tar@6.1.9: version "6.1.9" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.9.tgz#5646ef51342ac55456b2466e44da810439978db1" @@ -26750,22 +26693,10 @@ tar@6.1.9: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39" - integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.0" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tar@^6.1.2: - version "6.1.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.10.tgz#8a320a74475fba54398fa136cd9883aa8ad11175" - integrity sha512-kvvfiVvjGMxeUNB6MyYv5z7vhfFRwbwCXJAeL0/lnbrttBVqcMOnpHUf0X42LrPMR8mMpgapkJMchFH4FSHzNA== +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -26841,7 +26772,7 @@ term-size@^2.1.0: resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== -terminal-link@^2.0.0, terminal-link@^2.1.1: +terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== @@ -27281,6 +27212,13 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" +tough-cookie@~2.3.0: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== + dependencies: + punycode "^1.4.1" + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -27640,11 +27578,6 @@ uglify-to-browserify@~1.0.0: resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= -ui-select@0.19.8: - version "0.19.8" - resolved "https://registry.yarnpkg.com/ui-select/-/ui-select-0.19.8.tgz#74860848a7fd8bc494d9856d2f62776ea98637c1" - integrity sha1-dIYISKf9i8SU2YVtL2J3bqmGN8E= - uid-safe@2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" @@ -28068,13 +28001,6 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -unstated@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/unstated/-/unstated-2.1.1.tgz#36b124dfb2e7a12d39d0bb9c46dfb6e51276e3a2" - integrity sha512-fORlTWMZxq7NuMJDxyIrrYIZKN7wEWYQ9SiaJfIRcSpsowr6Ph/JIfK2tgtXLW614JfPG/t5q9eEIhXRCf55xg== - dependencies: - create-react-context "^0.1.5" - untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -28253,6 +28179,11 @@ use@^2.0.0: isobject "^3.0.0" lazy-cache "^2.0.2" +usng.js@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/usng.js/-/usng.js-0.4.5.tgz#49301e131baa9f7f7ab36c0539472c1aa1ecdae7" + integrity sha512-JTJcFFDy/JqA5iiU8DqMOV5gJiL3ZdXH0hCKYaRMDbbredhFna5Ok4C1YDFU07mTGAgm+5FzHaaOzQnO/3BzWQ== + utif@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759" @@ -28339,7 +28270,7 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: +uuid@^3.0.0, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -29648,7 +29579,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==