diff --git a/.buildkite/scripts/bootstrap.sh b/.buildkite/scripts/bootstrap.sh index d4e8434fe6df8..c80f215052a26 100755 --- a/.buildkite/scripts/bootstrap.sh +++ b/.buildkite/scripts/bootstrap.sh @@ -26,6 +26,8 @@ fi ### upload ts-refs-cache artifacts as quickly as possible so they are available for download ### if [[ "${BUILD_TS_REFS_CACHE_CAPTURE:-}" == "true" ]]; then + echo "--- Build ts-refs-cache" + node scripts/build_ts_refs.js --ignore-type-failures echo "--- Upload ts-refs-cache" cd "$KIBANA_DIR/target/ts_refs_cache" gsutil cp "*.zip" 'gs://kibana-ci-ts-refs-cache/' diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a8619643d1b2e..b09edd73bbcbc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -56,8 +56,6 @@ /examples/state_containers_examples/ @elastic/kibana-app-services /examples/ui_action_examples/ @elastic/kibana-app-services /examples/ui_actions_explorer/ @elastic/kibana-app-services -/examples/url_generators_examples/ @elastic/kibana-app-services -/examples/url_generators_explorer/ @elastic/kibana-app-services /examples/field_formats_example/ @elastic/kibana-app-services /examples/partial_results_example/ @elastic/kibana-app-services /examples/search_examples/ @elastic/kibana-app-services @@ -344,8 +342,8 @@ /x-pack/plugins/triggers_actions_ui/ @elastic/response-ops /x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/response-ops /x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/response-ops -/docs/user/alerting/ @elastic/response-ops -/docs/management/connectors/ @elastic/response-ops +/docs/user/alerting/ @elastic/response-ops @elastic/mlr-docs +/docs/management/connectors/ @elastic/response-ops @elastic/mlr-docs #CC# /x-pack/plugins/stack_alerts @elastic/response-ops /x-pack/plugins/cases/ @elastic/response-ops /x-pack/test/cases_api_integration/ @elastic/response-ops diff --git a/.github/workflows/label-qa-fixed-in.yml b/.github/workflows/label-qa-fixed-in.yml index e1dafa061f623..bb203d7d17c43 100644 --- a/.github/workflows/label-qa-fixed-in.yml +++ b/.github/workflows/label-qa-fixed-in.yml @@ -37,7 +37,8 @@ jobs: } } prnumber: ${{ github.event.number }} - token: ${{ secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.FLEET_TECH_KIBANA_USER_TOKEN }} - uses: sergeysova/jq-action@v2 id: issues_to_label with: @@ -75,4 +76,5 @@ jobs: } issueid: ${{ matrix.issueNodeId }} labelids: ${{ needs.fetch_issues_to_label.outputs.label_ids }} - token: ${{ secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.FLEET_TECH_KIBANA_USER_TOKEN }} diff --git a/dev_docs/key_concepts/building_blocks.mdx b/dev_docs/key_concepts/building_blocks.mdx index 61e3a711775c3..787fc59c3bba3 100644 --- a/dev_docs/key_concepts/building_blocks.mdx +++ b/dev_docs/key_concepts/building_blocks.mdx @@ -124,7 +124,7 @@ sharing and space isolation, and tags. ## Advanced Settings - should be used if you need to add application-level configuration options. If you wanted to add a setting for listing a number of items per page in your TODO application, then `pageListing` would be a configuration option. **Github labels**: `Team:Core`, `Feature:uiSettings`, `Feature:Advanced Settings` diff --git a/dev_docs/tutorials/ci.mdx b/dev_docs/tutorials/ci.mdx new file mode 100644 index 0000000000000..32e5a8503a22c --- /dev/null +++ b/dev_docs/tutorials/ci.mdx @@ -0,0 +1,48 @@ +--- +id: kibDevTutorialCI +slug: /kibana-dev-docs/tutorials/ci +title: CI +summary: CI +date: 2022-02-03 +tags: ['kibana', 'onboarding', 'dev', 'ci'] +--- + +## CI + +Kibana uses BuildKite to run a series of checks against each pull requests and tracked branch. Results are posted in pull requests as comments and from the BuildKite UI + +### Comments + +Comments in pull requests can be used to trigger CI operations. + +#### `buildkite test this` + +Run test suites and checks. + +#### `@elasticmachine merge upstream` + +Merge in the most recent changes from upstream. + +#### `@elasticmachine run elasticsearch-ci/docs` + +Build documentation from the root `docs` folder. + +### Labels + +Labels can be added to a pull request to run conditional pipelines. + +#### `ci:deploy-cloud` + +Deploy a pull request to Elastic Cloud. Deployment information will be available as an annotation at the top of a build. Access credentials will be available in vault. + +#### `ci:build-all-platforms` + +Build Windows, macOS, and Linux archives. Artifacts will be available on the "Artifacts" tab of the "Build Kibana Distribution and Plugins" step. + +#### `ci:build-os-packages` + +Build Docker images, and Debian and RPM packages. Artifacts will be available on the "Artifacts" tab of the "Build Kibana Distribution and Plugins" step. + +#### `ci:all-cypress-suites` + +By default, Cypress test suites are only run when code changes are made in certain files, typically files with overlapping test coverage. Adding this label will cause all Cypress tests to run. diff --git a/docs/apm/correlations.asciidoc b/docs/apm/correlations.asciidoc index ea62f034fb89b..cb72a1b305fd7 100644 --- a/docs/apm/correlations.asciidoc +++ b/docs/apm/correlations.asciidoc @@ -60,8 +60,6 @@ filtered out, you can begin viewing sample traces to continue your investigation [[correlations-error-rate]] ==== Find failed transaction correlations -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 diff --git a/docs/apm/images/correlations-failed-transactions.png b/docs/apm/images/correlations-failed-transactions.png index 18fd3ff728544..19221e751ef69 100644 Binary files a/docs/apm/images/correlations-failed-transactions.png and b/docs/apm/images/correlations-failed-transactions.png differ diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index e7555a6c3e3d6..e8c5d6600c0bd 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -151,7 +151,7 @@ Learn more about a trace sample in the *Metadata* tab: * User - Requires additional configuration, but allows you to see which user experienced the current transaction. TIP: All of this data is stored in documents in Elasticsearch. -This means you can select "Actions - View sample document" to see the actual Elasticsearch document under the discover tab. +This means you can select "Actions - View transaction in Discover" to see the actual Elasticsearch document under the discover tab. *Trace sample logs* diff --git a/docs/concepts/data-views.asciidoc b/docs/concepts/data-views.asciidoc index 98bbba5392b15..807b829c7481a 100644 --- a/docs/concepts/data-views.asciidoc +++ b/docs/concepts/data-views.asciidoc @@ -32,16 +32,16 @@ uploaded a file, or added sample data, you get a data view for free, and can start exploring your data. If you loaded your own data, follow these steps to create a data view. -. Open the main menu, then click to *Stack Management > Data Views*. +. Open the main menu, then click *Stack Management > Data Views*. . Click *Create data view*. -[role="screenshot"] -image:management/index-patterns/images/create-data-view.png["Create data view"] - . Start typing in the *name* field, and {kib} looks for the names of indices, data streams, and aliases that match your input. + +[role="screenshot"] +image:management/index-patterns/images/create-data-view.png["Create data view"] ++ ** To match multiple sources, use a wildcard (*). For example, `filebeat-*` matches `filebeat-apache-a`, `filebeat-apache-b`, and so on. + diff --git a/docs/concepts/kuery.asciidoc b/docs/concepts/kuery.asciidoc index 53d445b932d62..947dd96315a36 100644 --- a/docs/concepts/kuery.asciidoc +++ b/docs/concepts/kuery.asciidoc @@ -144,6 +144,20 @@ but in some cases you might need to search on dates. Include the date range in q @timestamp < "2021" ------------------- +KQL supports date math expressions. + +[source,yaml] +------------------- +@timestamp < now-1d +------------------- + +[source,yaml] +------------------- +updated_at > 2022-02-17||+1M/d +------------------- + +Check the +{ref}/common-options.html#date-math[date math documentation] for more examples. [discrete] === Exist queries diff --git a/docs/developer/contributing/development-functional-tests.asciidoc b/docs/developer/contributing/development-functional-tests.asciidoc index 1c44daf60f973..af88754b316fa 100644 --- a/docs/developer/contributing/development-functional-tests.asciidoc +++ b/docs/developer/contributing/development-functional-tests.asciidoc @@ -105,7 +105,9 @@ There are also command line flags for `--bail` and `--grep`, which behave just l Logging can also be customized with `--quiet`, `--debug`, or `--verbose` flags. -Use the `--help` flag for more options. +There are also options like `--include` to run only the tests defined in a single file or set of files. + +Run `node scripts/functional_test_runner --help` to see all available options. [discrete] diff --git a/docs/discover/document-explorer.asciidoc b/docs/discover/document-explorer.asciidoc index 8d334b14b1387..99447a2478b3a 100644 --- a/docs/discover/document-explorer.asciidoc +++ b/docs/discover/document-explorer.asciidoc @@ -31,6 +31,18 @@ To resize a column, drag the right edge of the column header until the column is Column widths are stored with a saved search. When you visualize saved searches on dashboards, the saved search appears the same as in **Discover**. +[float] +[[document-explorer-row-height]] +=== Adjust row height + +To set the row height to 1 or more lines, or automatically +adjust the height to fit the contents, click the row height icon +image:images/row-height-icon.png[icon to open the Row height pop-up]. + +[role="screenshot"] +image::images/document-explorer-row-height.png[Row height settings for Document Explroer, width="75%"] + + [float] [[document-explorer-sort-data]] === Sort data @@ -43,7 +55,7 @@ The default sort is based on the time field, from new to old. [role="screenshot"] image::images/document-explorer-sort-data.png[Pop-up in Document Explorer for sorting columns, width="75%"] -. To add more fields to the sort, expand the dropdown menu. +. To add more fields to the sort, select from the dropdown menu. + By default, columns are sorted in the order they are added. For example, to sort by `order_date` then `geo.country_iso_code`, make sure `order_date` appears first. @@ -77,14 +89,22 @@ the documents that occurred before and after it. image:images/expand-icon-2.png[double arrow icon to open a flyout with the document details]. + [role="screenshot"] -image::images/document-explorer-expand.png[Multi field sort in Document Explorer] +image::images/document-explorer-expand.png[Expanded view in Document Explorer] -. Scan through the fields and their values. If you find a field of interest, +. Scan through the fields and their values, or search for a field by name. + +. When you find a field of interest, click -image:images/actions-icon.png[three dots icon in table column] in the *Actions* column for filters and other controls. -. To view documents that occurred before or after the event you are looking at, click <>. +image:images/actions-icon.png[three dots icon in table column] in the *Actions* column +to: +.. Filter the view of the data +.. Toggle the field in or out the document table +.. Pin the field so it stays at the top + . For direct access to a particular document, click <>. +. To view documents that occurred before or after the event you are looking at, click <>. + [float] [[document-explorer-full-screen]] === View documents in fullscreen diff --git a/docs/discover/field-statistics.asciidoc b/docs/discover/field-statistics.asciidoc index f20b5488ec003..750b25c714aa3 100644 --- a/docs/discover/field-statistics.asciidoc +++ b/docs/discover/field-statistics.asciidoc @@ -14,11 +14,9 @@ for the data and its cardinality? This example explores the fields in the <>, or you can use your own data. -. Open the main menu, click *Stack Managment > Advanced Settings*, search for *Show field statistics*, -and turn on the setting. +. Open the main menu, and click *Discover*. -. Open the main menu, click *Discover*, expand the {data-source} dropdown, -and select *kibana_sample_data_logs*. +. Expand the {data-source} dropdown, and select *kibana_sample_data_logs*. . If you don’t see any results, expand the time range, for example, to *Last 7 days*. diff --git a/docs/discover/images/add-field-to-data-view.png b/docs/discover/images/add-field-to-data-view.png index 6e30ee0116c86..e4352e814fa0b 100644 Binary files a/docs/discover/images/add-field-to-data-view.png and b/docs/discover/images/add-field-to-data-view.png differ diff --git a/docs/discover/images/customer.png b/docs/discover/images/customer.png index 8e4b7500ba058..55a04bf754d4e 100644 Binary files a/docs/discover/images/customer.png and b/docs/discover/images/customer.png differ diff --git a/docs/discover/images/discover-context-filters-inactive.png b/docs/discover/images/discover-context-filters-inactive.png deleted file mode 100644 index 26609392472fb..0000000000000 Binary files a/docs/discover/images/discover-context-filters-inactive.png and /dev/null differ diff --git a/docs/discover/images/discover-context.png b/docs/discover/images/discover-context.png index f73cba203f8f1..770c5fb921a5d 100644 Binary files a/docs/discover/images/discover-context.png and b/docs/discover/images/discover-context.png differ diff --git a/docs/discover/images/discover-from-visualize.png b/docs/discover/images/discover-from-visualize.png index 6c976f01bc9f4..1aa3e9b502d40 100644 Binary files a/docs/discover/images/discover-from-visualize.png and b/docs/discover/images/discover-from-visualize.png differ diff --git a/docs/discover/images/discover-search-for-relevance.png b/docs/discover/images/discover-search-for-relevance.png index 15945b3515530..81b8c323e4d49 100644 Binary files a/docs/discover/images/discover-search-for-relevance.png and b/docs/discover/images/discover-search-for-relevance.png differ diff --git a/docs/discover/images/discover-visualize.png b/docs/discover/images/discover-visualize.png index 5aca5a210139d..15f2eb4a7f60b 100644 Binary files a/docs/discover/images/discover-visualize.png and b/docs/discover/images/discover-visualize.png differ diff --git a/docs/discover/images/discover.png b/docs/discover/images/discover.png index ba4d1347e9064..83a91e03b033d 100644 Binary files a/docs/discover/images/discover.png and b/docs/discover/images/discover.png differ diff --git a/docs/discover/images/document-explorer-expand.png b/docs/discover/images/document-explorer-expand.png index 67dbd04b5f744..a1b989a0f5ae8 100644 Binary files a/docs/discover/images/document-explorer-expand.png and b/docs/discover/images/document-explorer-expand.png differ diff --git a/docs/discover/images/document-explorer-row-height.png b/docs/discover/images/document-explorer-row-height.png new file mode 100644 index 0000000000000..3706f195c84ad Binary files /dev/null and b/docs/discover/images/document-explorer-row-height.png differ diff --git a/docs/discover/images/document-explorer.png b/docs/discover/images/document-explorer.png index 23e822868ce05..75d2826869a1d 100644 Binary files a/docs/discover/images/document-explorer.png and b/docs/discover/images/document-explorer.png differ diff --git a/docs/discover/images/document-table-expanded.png b/docs/discover/images/document-table-expanded.png index 2e6d090f17677..9f0a05f74df75 100644 Binary files a/docs/discover/images/document-table-expanded.png and b/docs/discover/images/document-table-expanded.png differ diff --git a/docs/discover/images/document-table.png b/docs/discover/images/document-table.png index 277666b10efd2..1fc8a19d54cb7 100644 Binary files a/docs/discover/images/document-table.png and b/docs/discover/images/document-table.png differ diff --git a/docs/discover/images/hello-field.png b/docs/discover/images/hello-field.png index 3098abf0298c3..bd5999f86dc3e 100644 Binary files a/docs/discover/images/hello-field.png and b/docs/discover/images/hello-field.png differ diff --git a/docs/discover/images/row-height-icon.png b/docs/discover/images/row-height-icon.png new file mode 100644 index 0000000000000..4c30d7a81c099 Binary files /dev/null and b/docs/discover/images/row-height-icon.png differ diff --git a/docs/discover/save-search.asciidoc b/docs/discover/save-search.asciidoc index 33fbf3aba4989..89adf4eac0ba1 100644 --- a/docs/discover/save-search.asciidoc +++ b/docs/discover/save-search.asciidoc @@ -7,10 +7,10 @@ Saved searches are good for adding search results to a dashboard, and can also serve as a foundation for building visualizations. A saved search stores the query text, filters, and -current view of *Discover*—the columns selected in the document table, +current view of *Discover*, including the columns selected in the document table, the sort order, and the {data-source}. Saved searches are different from <>, which -are primarily used for storing query text and are available in any app with a query bar. +are for storing query text and are available in any app with a query bar. [role="xpack"] [[discover-read-only-access]] diff --git a/docs/discover/view-document.asciidoc b/docs/discover/view-document.asciidoc index 1168f2d0fa55f..c5b59d98dc5fd 100644 --- a/docs/discover/view-document.asciidoc +++ b/docs/discover/view-document.asciidoc @@ -33,16 +33,11 @@ your {data-source} must contain time-based events. . In the expanded view, click **View surrounding documents**. + Documents are displayed using the same set of columns as the *Discover* view from which -the context was opened. The anchor document is highlighted in blue. -+ -[role="screenshot"] -image::images/discover-context.png[Image showing context view feature, with anchor documents highlighted in blue] -+ -The filters you applied in *Discover* are carried over to the context view. Pinned +the context was opened. The filters you applied are also carried over. Pinned filters remain active, while normal filters are copied in a disabled state. + [role="screenshot"] -image::images/discover-context-filters-inactive.png[Filter in context view] +image::images/discover-context.png[Image showing context view feature, with anchor documents highlighted in blue] . To find the documents of interest, add filters. diff --git a/docs/management/connectors/action-types/slack.asciidoc b/docs/management/connectors/action-types/slack.asciidoc index 6dffebd9d9354..19abf9119b837 100644 --- a/docs/management/connectors/action-types/slack.asciidoc +++ b/docs/management/connectors/action-types/slack.asciidoc @@ -76,5 +76,3 @@ URL, set up an an **Incoming Webhook Integration** through the Slack console: image::images/slack-add-webhook-integration.png[] . Click *Add Incoming Webhook Integration*. . Copy the generated webhook URL so you can paste it into your Slack connector form. -+ -image::images/slack-copy-webhook-url.png[] diff --git a/docs/management/connectors/images/slack-copy-webhook-url.png b/docs/management/connectors/images/slack-copy-webhook-url.png deleted file mode 100644 index 805f5719980da..0000000000000 Binary files a/docs/management/connectors/images/slack-copy-webhook-url.png and /dev/null differ diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index dce53703ba8e1..b5e0af7e188a1 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -106,7 +106,7 @@ available, but there will likely be errors in the visualizations in the report. If capturing a report fails for any reason, {kib} will re-attempt other reporting job, as many times as this setting. Defaults to `3`. `xpack.reporting.capture.loadDelay`:: -Specify the {time-units}[amount of time] before taking a screenshot when visualizations are not evented. All visualizations that ship with {kib} are evented, so this setting should not have much effect. If you are seeing empty images instead of visualizations, try increasing this value. Defaults to `3s`. +deprecated:[8.0.0,This setting has no effect.] Specify the {time-units}[amount of time] before taking a screenshot when visualizations are not evented. All visualizations that ship with {kib} are evented, so this setting should not have much effect. If you are seeing empty images instead of visualizations, try increasing this value. Defaults to `3s`. *NOTE*: This setting exists for backwards compatibility, but is unused and therefore does not have an affect on reporting performance. [float] [[reporting-chromium-settings]] @@ -220,8 +220,7 @@ Enables a check that warns you when there's a potential formula included in the Escape formula values in cells with a `'`. See OWASP: https://www.owasp.org/index.php/CSV_Injection. Defaults to `true`. `xpack.reporting.csv.enablePanelActionDownload`:: -Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search. -NOTE: This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard is enabled when Reporting is enabled. +deprecated:[7.9.0,This setting has no effect.] Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search. *NOTE*: This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard is enabled when Reporting is enabled. `xpack.reporting.csv.useByteOrderMarkEncoding`:: Adds a byte order mark (`\ufeff`) at the beginning of the CSV file. Defaults to `false`. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 045239bd5317f..3d08bceaccc7b 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -8,7 +8,7 @@ What pages on your website contain a specific word or phrase? What events were logged most recently? What processes take longer than 500 milliseconds to respond? -With *Discover*, you can quickly gain insight to your data: search and filter your data, get information +With *Discover*, you can quickly search and filter your data, get information about the structure of the fields, and display your findings in a visualization. You can also customize and save your searches and place them on a dashboard. @@ -19,7 +19,7 @@ image::images/discover.png[A view of the Discover app] [float] === Explore and query your data -This tutorial shows you how to use *Discover* to quickly search large amounts of +This tutorial shows you how to use *Discover* to search large amounts of data and understand what’s going on at any given time. You’ll learn to: @@ -58,6 +58,10 @@ To view the ecommerce sample data, make sure the {data-source} is set to **kiban + [role="screenshot"] image::images/discover-data-view.png[How to set the {data-source} in Discover, width=50%] ++ +To create a data view for your own data, +click the ellipsis icon (…​), and then click *Create new data view*. +For details, refer to <> . Adjust the <> to view data for the *Last 7 days*. + @@ -73,8 +77,8 @@ click and drag the mouse over the chart. === Explore the fields in your data **Discover** includes a table that shows all the documents that match your search. -By default, the table includes columns for the time field and the document `_source`, -which can be overwhelming. You’ll modify this table to display only your fields of interest. +By default, the table includes columns for the time field and the document `_source`. +You’ll modify this table to display your fields of interest. . Scan through the list of **Available fields** until you find the `manufacturer` field. You can also search for the field by name. @@ -110,7 +114,7 @@ You can add a runtime field to your {data-source} from inside of **Discover**, and then use that field for analysis and visualizations, the same way you do with other fields. -. Click the ellipsis icon (...), and then click *Add field to data view*. +. Click the ellipsis icon (...), and then click *Add field*. + [role="screenshot"] image:images/add-field-to-data-view.png[Dropdown menu located next to {data-source} field with item for adding a field to a {data-source}, width=50%] @@ -168,17 +172,19 @@ you can use to build a structured query. Search the ecommerce data for documents where the country matches US: . Enter `g`, and then select *geoip.country_iso_code*. -. Select *equals some value* and *US*, and then click *Update*. +. Select *:* for equals some value and *US*, and then click *Update*. . For a more complex search, try: + -`geoip.country_iso_code : US and products.taxless_price >= 75` +```ts +geoip.country_iso_code : US and products.taxless_price >= 75 +``` [float] [[filter-in-discover]] === Filter your data Whereas the query defines the set of documents you are interested in, -filters enable you to zero in on different subsets of those documents. +filters enable you to zero in on subsets of those documents. You can filter results to include or exclude specific fields, filter for a value in a range, and more. @@ -224,7 +230,7 @@ You can bookmark this document and share the link. Save your search so you can repeat it later, generate a CSV report, or use it in visualizations, dashboards, and Canvas workpads. Saving a search saves the query text, filters, -and current view of *Discover*—the columns selected in the document table, the sort order, and the {data-source}. +and current view of *Discover*, including the columns selected in the document table, the sort order, and the {data-source}. . In the toolbar, click **Save**. @@ -287,4 +293,3 @@ include::{kib-repo-dir}/discover/search-sessions.asciidoc[] include::{kib-repo-dir}/discover/document-explorer.asciidoc[] include::{kib-repo-dir}/discover/field-statistics.asciidoc[] - diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 5df9961f28103..a946cfa0dd56b 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -46,6 +46,7 @@ "id": "kibDevTutorialBuildingDistributable", "label": "Building a Kibana distributable" }, + { "id": "kibDevTutorialCI" }, { "id": "kibDevTutorialServerEndpoint" }, { "id": "kibDevTutorialAdvancedSettings"} ] diff --git a/package.json b/package.json index d8e2226ba944c..6012dae100822 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "**/handlebars/uglify-js": "^3.14.3", "**/hoist-non-react-statics": "^3.3.2", "**/html-minifier/uglify-js": "^3.14.3", - "**/isomorphic-fetch/node-fetch": "^2.6.1", + "**/isomorphic-fetch/node-fetch": "^2.6.7", "**/istanbul-lib-coverage": "^3.2.0", "**/json-schema": "^0.4.0", "**/minimist": "^1.2.5", @@ -93,7 +93,8 @@ "**/trim": "1.0.1", "**/typescript": "4.5.3", "**/underscore": "^1.13.1", - "globby/fast-glob": "3.2.7" + "globby/fast-glob": "3.2.7", + "puppeteer/node-fetch": "^2.6.7" }, "dependencies": { "@babel/runtime": "^7.17.2", @@ -213,7 +214,6 @@ "color": "1.0.3", "commander": "^4.1.1", "compare-versions": "3.5.1", - "concat-stream": "1.6.2", "constate": "^1.3.2", "content-disposition": "0.5.3", "copy-to-clipboard": "^3.0.8", @@ -291,7 +291,7 @@ "lz-string": "^1.4.4", "mapbox-gl-draw-rectangle-mode": "1.0.4", "maplibre-gl": "1.15.2", - "markdown-it": "^10.0.0", + "markdown-it": "^12.3.2", "md5": "^2.1.0", "mdast-util-to-hast": "10.0.1", "memoize-one": "^6.0.0", @@ -305,7 +305,7 @@ "monaco-editor": "^0.22.3", "mustache": "^2.3.2", "nock": "12.0.3", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.7", "node-forge": "^1.2.1", "nodemailer": "^6.6.2", "normalize-path": "^3.0.0", @@ -630,7 +630,7 @@ "@types/lodash": "^4.14.159", "@types/lru-cache": "^5.1.0", "@types/lz-string": "^1.3.34", - "@types/markdown-it": "^0.0.7", + "@types/markdown-it": "^12.2.3", "@types/md5": "^2.2.0", "@types/mime": "^2.0.1", "@types/mime-types": "^2.1.0", @@ -643,7 +643,7 @@ "@types/ncp": "^2.0.1", "@types/nock": "^10.0.3", "@types/node": "16.10.2", - "@types/node-fetch": "^2.5.7", + "@types/node-fetch": "^2.6.0", "@types/node-forge": "^1.0.0", "@types/nodemailer": "^6.4.0", "@types/normalize-path": "^3.0.0", diff --git a/packages/kbn-monaco/src/painless/antlr/painless_lexer.g4 b/packages/kbn-monaco/src/painless/antlr/painless_lexer.g4 index 56a8fada59aa5..71a495d113b76 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_lexer.g4 +++ b/packages/kbn-monaco/src/painless/antlr/painless_lexer.g4 @@ -17,6 +17,7 @@ LBRACE: '['; RBRACE: ']'; LP: '('; RP: ')'; +DOLLAR: '$'; // We switch modes after a dot to ensure there are not conflicts // between shortcuts and decimal values. Without the mode switch // shortcuts such as id.0.0 will fail because 0.0 will be interpreted diff --git a/packages/kbn-monaco/src/painless/antlr/painless_lexer.interp b/packages/kbn-monaco/src/painless/antlr/painless_lexer.interp index df5a0d5244124..ab7a7e876df8e 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_lexer.interp +++ b/packages/kbn-monaco/src/painless/antlr/painless_lexer.interp @@ -8,6 +8,7 @@ null ']' '(' ')' +'$' '.' '?.' ',' @@ -96,6 +97,7 @@ LBRACE RBRACE LP RP +DOLLAR DOT NSDOT COMMA @@ -183,6 +185,7 @@ LBRACE RBRACE LP RP +DOLLAR DOT NSDOT COMMA @@ -270,4 +273,4 @@ DEFAULT_MODE AFTER_DOT atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 87, 634, 8, 1, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 4, 68, 9, 68, 4, 69, 9, 69, 4, 70, 9, 70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 4, 74, 9, 74, 4, 75, 9, 75, 4, 76, 9, 76, 4, 77, 9, 77, 4, 78, 9, 78, 4, 79, 9, 79, 4, 80, 9, 80, 4, 81, 9, 81, 4, 82, 9, 82, 4, 83, 9, 83, 4, 84, 9, 84, 4, 85, 9, 85, 4, 86, 9, 86, 3, 2, 6, 2, 176, 10, 2, 13, 2, 14, 2, 177, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 186, 10, 3, 12, 3, 14, 3, 189, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 196, 10, 3, 12, 3, 14, 3, 199, 11, 3, 3, 3, 3, 3, 5, 3, 203, 10, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 30, 3, 30, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 44, 3, 45, 3, 45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 3, 57, 3, 57, 3, 57, 3, 58, 3, 58, 3, 58, 3, 58, 3, 59, 3, 59, 3, 59, 3, 60, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 62, 3, 63, 3, 63, 3, 63, 3, 64, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 3, 67, 3, 67, 3, 67, 3, 68, 3, 68, 3, 68, 3, 69, 3, 69, 3, 69, 3, 70, 3, 70, 3, 70, 3, 70, 3, 71, 3, 71, 3, 71, 3, 71, 3, 72, 3, 72, 3, 72, 3, 72, 3, 72, 3, 73, 3, 73, 6, 73, 442, 10, 73, 13, 73, 14, 73, 443, 3, 73, 5, 73, 447, 10, 73, 3, 74, 3, 74, 3, 74, 6, 74, 452, 10, 74, 13, 74, 14, 74, 453, 3, 74, 5, 74, 457, 10, 74, 3, 75, 3, 75, 3, 75, 7, 75, 462, 10, 75, 12, 75, 14, 75, 465, 11, 75, 5, 75, 467, 10, 75, 3, 75, 5, 75, 470, 10, 75, 3, 76, 3, 76, 3, 76, 7, 76, 475, 10, 76, 12, 76, 14, 76, 478, 11, 76, 5, 76, 480, 10, 76, 3, 76, 3, 76, 6, 76, 484, 10, 76, 13, 76, 14, 76, 485, 5, 76, 488, 10, 76, 3, 76, 3, 76, 5, 76, 492, 10, 76, 3, 76, 6, 76, 495, 10, 76, 13, 76, 14, 76, 496, 5, 76, 499, 10, 76, 3, 76, 5, 76, 502, 10, 76, 3, 77, 3, 77, 3, 77, 3, 77, 3, 77, 3, 77, 7, 77, 510, 10, 77, 12, 77, 14, 77, 513, 11, 77, 3, 77, 3, 77, 3, 77, 3, 77, 3, 77, 3, 77, 3, 77, 7, 77, 522, 10, 77, 12, 77, 14, 77, 525, 11, 77, 3, 77, 5, 77, 528, 10, 77, 3, 78, 3, 78, 3, 78, 3, 78, 6, 78, 534, 10, 78, 13, 78, 14, 78, 535, 3, 78, 3, 78, 7, 78, 540, 10, 78, 12, 78, 14, 78, 543, 11, 78, 3, 78, 3, 78, 3, 79, 3, 79, 3, 79, 3, 79, 3, 79, 3, 80, 3, 80, 3, 80, 3, 80, 3, 80, 3, 80, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 5, 82, 601, 10, 82, 3, 83, 3, 83, 3, 83, 3, 83, 3, 84, 3, 84, 7, 84, 609, 10, 84, 12, 84, 14, 84, 612, 11, 84, 3, 85, 3, 85, 3, 85, 7, 85, 617, 10, 85, 12, 85, 14, 85, 620, 11, 85, 5, 85, 622, 10, 85, 3, 85, 3, 85, 3, 86, 3, 86, 7, 86, 628, 10, 86, 12, 86, 14, 86, 631, 11, 86, 3, 86, 3, 86, 7, 187, 197, 511, 523, 535, 2, 2, 87, 4, 2, 3, 6, 2, 4, 8, 2, 5, 10, 2, 6, 12, 2, 7, 14, 2, 8, 16, 2, 9, 18, 2, 10, 20, 2, 11, 22, 2, 12, 24, 2, 13, 26, 2, 14, 28, 2, 15, 30, 2, 16, 32, 2, 17, 34, 2, 18, 36, 2, 19, 38, 2, 20, 40, 2, 21, 42, 2, 22, 44, 2, 23, 46, 2, 24, 48, 2, 25, 50, 2, 26, 52, 2, 27, 54, 2, 28, 56, 2, 29, 58, 2, 30, 60, 2, 31, 62, 2, 32, 64, 2, 33, 66, 2, 34, 68, 2, 35, 70, 2, 36, 72, 2, 37, 74, 2, 38, 76, 2, 39, 78, 2, 40, 80, 2, 41, 82, 2, 42, 84, 2, 43, 86, 2, 44, 88, 2, 45, 90, 2, 46, 92, 2, 47, 94, 2, 48, 96, 2, 49, 98, 2, 50, 100, 2, 51, 102, 2, 52, 104, 2, 53, 106, 2, 54, 108, 2, 55, 110, 2, 56, 112, 2, 57, 114, 2, 58, 116, 2, 59, 118, 2, 60, 120, 2, 61, 122, 2, 62, 124, 2, 63, 126, 2, 64, 128, 2, 65, 130, 2, 66, 132, 2, 67, 134, 2, 68, 136, 2, 69, 138, 2, 70, 140, 2, 71, 142, 2, 72, 144, 2, 73, 146, 2, 74, 148, 2, 75, 150, 2, 76, 152, 2, 77, 154, 2, 78, 156, 2, 79, 158, 2, 80, 160, 2, 81, 162, 2, 82, 164, 2, 83, 166, 2, 84, 168, 2, 85, 170, 2, 86, 172, 2, 87, 4, 2, 3, 21, 5, 2, 11, 12, 15, 15, 34, 34, 4, 2, 12, 12, 15, 15, 3, 2, 50, 57, 4, 2, 78, 78, 110, 110, 4, 2, 90, 90, 122, 122, 5, 2, 50, 59, 67, 72, 99, 104, 3, 2, 51, 59, 3, 2, 50, 59, 8, 2, 70, 70, 72, 72, 78, 78, 102, 102, 104, 104, 110, 110, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 6, 2, 70, 70, 72, 72, 102, 102, 104, 104, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2, 12, 12, 4, 2, 12, 12, 49, 49, 9, 2, 87, 87, 101, 101, 107, 107, 110, 111, 117, 117, 119, 119, 122, 122, 5, 2, 67, 92, 97, 97, 99, 124, 6, 2, 50, 59, 67, 92, 97, 97, 99, 124, 2, 672, 2, 4, 3, 2, 2, 2, 2, 6, 3, 2, 2, 2, 2, 8, 3, 2, 2, 2, 2, 10, 3, 2, 2, 2, 2, 12, 3, 2, 2, 2, 2, 14, 3, 2, 2, 2, 2, 16, 3, 2, 2, 2, 2, 18, 3, 2, 2, 2, 2, 20, 3, 2, 2, 2, 2, 22, 3, 2, 2, 2, 2, 24, 3, 2, 2, 2, 2, 26, 3, 2, 2, 2, 2, 28, 3, 2, 2, 2, 2, 30, 3, 2, 2, 2, 2, 32, 3, 2, 2, 2, 2, 34, 3, 2, 2, 2, 2, 36, 3, 2, 2, 2, 2, 38, 3, 2, 2, 2, 2, 40, 3, 2, 2, 2, 2, 42, 3, 2, 2, 2, 2, 44, 3, 2, 2, 2, 2, 46, 3, 2, 2, 2, 2, 48, 3, 2, 2, 2, 2, 50, 3, 2, 2, 2, 2, 52, 3, 2, 2, 2, 2, 54, 3, 2, 2, 2, 2, 56, 3, 2, 2, 2, 2, 58, 3, 2, 2, 2, 2, 60, 3, 2, 2, 2, 2, 62, 3, 2, 2, 2, 2, 64, 3, 2, 2, 2, 2, 66, 3, 2, 2, 2, 2, 68, 3, 2, 2, 2, 2, 70, 3, 2, 2, 2, 2, 72, 3, 2, 2, 2, 2, 74, 3, 2, 2, 2, 2, 76, 3, 2, 2, 2, 2, 78, 3, 2, 2, 2, 2, 80, 3, 2, 2, 2, 2, 82, 3, 2, 2, 2, 2, 84, 3, 2, 2, 2, 2, 86, 3, 2, 2, 2, 2, 88, 3, 2, 2, 2, 2, 90, 3, 2, 2, 2, 2, 92, 3, 2, 2, 2, 2, 94, 3, 2, 2, 2, 2, 96, 3, 2, 2, 2, 2, 98, 3, 2, 2, 2, 2, 100, 3, 2, 2, 2, 2, 102, 3, 2, 2, 2, 2, 104, 3, 2, 2, 2, 2, 106, 3, 2, 2, 2, 2, 108, 3, 2, 2, 2, 2, 110, 3, 2, 2, 2, 2, 112, 3, 2, 2, 2, 2, 114, 3, 2, 2, 2, 2, 116, 3, 2, 2, 2, 2, 118, 3, 2, 2, 2, 2, 120, 3, 2, 2, 2, 2, 122, 3, 2, 2, 2, 2, 124, 3, 2, 2, 2, 2, 126, 3, 2, 2, 2, 2, 128, 3, 2, 2, 2, 2, 130, 3, 2, 2, 2, 2, 132, 3, 2, 2, 2, 2, 134, 3, 2, 2, 2, 2, 136, 3, 2, 2, 2, 2, 138, 3, 2, 2, 2, 2, 140, 3, 2, 2, 2, 2, 142, 3, 2, 2, 2, 2, 144, 3, 2, 2, 2, 2, 146, 3, 2, 2, 2, 2, 148, 3, 2, 2, 2, 2, 150, 3, 2, 2, 2, 2, 152, 3, 2, 2, 2, 2, 154, 3, 2, 2, 2, 2, 156, 3, 2, 2, 2, 2, 158, 3, 2, 2, 2, 2, 160, 3, 2, 2, 2, 2, 162, 3, 2, 2, 2, 2, 164, 3, 2, 2, 2, 2, 166, 3, 2, 2, 2, 2, 168, 3, 2, 2, 2, 3, 170, 3, 2, 2, 2, 3, 172, 3, 2, 2, 2, 4, 175, 3, 2, 2, 2, 6, 202, 3, 2, 2, 2, 8, 206, 3, 2, 2, 2, 10, 208, 3, 2, 2, 2, 12, 210, 3, 2, 2, 2, 14, 212, 3, 2, 2, 2, 16, 214, 3, 2, 2, 2, 18, 216, 3, 2, 2, 2, 20, 218, 3, 2, 2, 2, 22, 222, 3, 2, 2, 2, 24, 227, 3, 2, 2, 2, 26, 229, 3, 2, 2, 2, 28, 231, 3, 2, 2, 2, 30, 234, 3, 2, 2, 2, 32, 237, 3, 2, 2, 2, 34, 242, 3, 2, 2, 2, 36, 248, 3, 2, 2, 2, 38, 251, 3, 2, 2, 2, 40, 255, 3, 2, 2, 2, 42, 264, 3, 2, 2, 2, 44, 270, 3, 2, 2, 2, 46, 277, 3, 2, 2, 2, 48, 281, 3, 2, 2, 2, 50, 285, 3, 2, 2, 2, 52, 291, 3, 2, 2, 2, 54, 297, 3, 2, 2, 2, 56, 302, 3, 2, 2, 2, 58, 313, 3, 2, 2, 2, 60, 315, 3, 2, 2, 2, 62, 317, 3, 2, 2, 2, 64, 319, 3, 2, 2, 2, 66, 322, 3, 2, 2, 2, 68, 324, 3, 2, 2, 2, 70, 326, 3, 2, 2, 2, 72, 328, 3, 2, 2, 2, 74, 331, 3, 2, 2, 2, 76, 334, 3, 2, 2, 2, 78, 338, 3, 2, 2, 2, 80, 340, 3, 2, 2, 2, 82, 343, 3, 2, 2, 2, 84, 345, 3, 2, 2, 2, 86, 348, 3, 2, 2, 2, 88, 351, 3, 2, 2, 2, 90, 355, 3, 2, 2, 2, 92, 358, 3, 2, 2, 2, 94, 362, 3, 2, 2, 2, 96, 364, 3, 2, 2, 2, 98, 366, 3, 2, 2, 2, 100, 368, 3, 2, 2, 2, 102, 371, 3, 2, 2, 2, 104, 374, 3, 2, 2, 2, 106, 376, 3, 2, 2, 2, 108, 378, 3, 2, 2, 2, 110, 381, 3, 2, 2, 2, 112, 384, 3, 2, 2, 2, 114, 387, 3, 2, 2, 2, 116, 390, 3, 2, 2, 2, 118, 394, 3, 2, 2, 2, 120, 397, 3, 2, 2, 2, 122, 400, 3, 2, 2, 2, 124, 402, 3, 2, 2, 2, 126, 405, 3, 2, 2, 2, 128, 408, 3, 2, 2, 2, 130, 411, 3, 2, 2, 2, 132, 414, 3, 2, 2, 2, 134, 417, 3, 2, 2, 2, 136, 420, 3, 2, 2, 2, 138, 423, 3, 2, 2, 2, 140, 426, 3, 2, 2, 2, 142, 430, 3, 2, 2, 2, 144, 434, 3, 2, 2, 2, 146, 439, 3, 2, 2, 2, 148, 448, 3, 2, 2, 2, 150, 466, 3, 2, 2, 2, 152, 479, 3, 2, 2, 2, 154, 527, 3, 2, 2, 2, 156, 529, 3, 2, 2, 2, 158, 546, 3, 2, 2, 2, 160, 551, 3, 2, 2, 2, 162, 557, 3, 2, 2, 2, 164, 600, 3, 2, 2, 2, 166, 602, 3, 2, 2, 2, 168, 606, 3, 2, 2, 2, 170, 621, 3, 2, 2, 2, 172, 625, 3, 2, 2, 2, 174, 176, 9, 2, 2, 2, 175, 174, 3, 2, 2, 2, 176, 177, 3, 2, 2, 2, 177, 175, 3, 2, 2, 2, 177, 178, 3, 2, 2, 2, 178, 179, 3, 2, 2, 2, 179, 180, 8, 2, 2, 2, 180, 5, 3, 2, 2, 2, 181, 182, 7, 49, 2, 2, 182, 183, 7, 49, 2, 2, 183, 187, 3, 2, 2, 2, 184, 186, 11, 2, 2, 2, 185, 184, 3, 2, 2, 2, 186, 189, 3, 2, 2, 2, 187, 188, 3, 2, 2, 2, 187, 185, 3, 2, 2, 2, 188, 190, 3, 2, 2, 2, 189, 187, 3, 2, 2, 2, 190, 203, 9, 3, 2, 2, 191, 192, 7, 49, 2, 2, 192, 193, 7, 44, 2, 2, 193, 197, 3, 2, 2, 2, 194, 196, 11, 2, 2, 2, 195, 194, 3, 2, 2, 2, 196, 199, 3, 2, 2, 2, 197, 198, 3, 2, 2, 2, 197, 195, 3, 2, 2, 2, 198, 200, 3, 2, 2, 2, 199, 197, 3, 2, 2, 2, 200, 201, 7, 44, 2, 2, 201, 203, 7, 49, 2, 2, 202, 181, 3, 2, 2, 2, 202, 191, 3, 2, 2, 2, 203, 204, 3, 2, 2, 2, 204, 205, 8, 3, 2, 2, 205, 7, 3, 2, 2, 2, 206, 207, 7, 125, 2, 2, 207, 9, 3, 2, 2, 2, 208, 209, 7, 127, 2, 2, 209, 11, 3, 2, 2, 2, 210, 211, 7, 93, 2, 2, 211, 13, 3, 2, 2, 2, 212, 213, 7, 95, 2, 2, 213, 15, 3, 2, 2, 2, 214, 215, 7, 42, 2, 2, 215, 17, 3, 2, 2, 2, 216, 217, 7, 43, 2, 2, 217, 19, 3, 2, 2, 2, 218, 219, 7, 48, 2, 2, 219, 220, 3, 2, 2, 2, 220, 221, 8, 10, 3, 2, 221, 21, 3, 2, 2, 2, 222, 223, 7, 65, 2, 2, 223, 224, 7, 48, 2, 2, 224, 225, 3, 2, 2, 2, 225, 226, 8, 11, 3, 2, 226, 23, 3, 2, 2, 2, 227, 228, 7, 46, 2, 2, 228, 25, 3, 2, 2, 2, 229, 230, 7, 61, 2, 2, 230, 27, 3, 2, 2, 2, 231, 232, 7, 107, 2, 2, 232, 233, 7, 104, 2, 2, 233, 29, 3, 2, 2, 2, 234, 235, 7, 107, 2, 2, 235, 236, 7, 112, 2, 2, 236, 31, 3, 2, 2, 2, 237, 238, 7, 103, 2, 2, 238, 239, 7, 110, 2, 2, 239, 240, 7, 117, 2, 2, 240, 241, 7, 103, 2, 2, 241, 33, 3, 2, 2, 2, 242, 243, 7, 121, 2, 2, 243, 244, 7, 106, 2, 2, 244, 245, 7, 107, 2, 2, 245, 246, 7, 110, 2, 2, 246, 247, 7, 103, 2, 2, 247, 35, 3, 2, 2, 2, 248, 249, 7, 102, 2, 2, 249, 250, 7, 113, 2, 2, 250, 37, 3, 2, 2, 2, 251, 252, 7, 104, 2, 2, 252, 253, 7, 113, 2, 2, 253, 254, 7, 116, 2, 2, 254, 39, 3, 2, 2, 2, 255, 256, 7, 101, 2, 2, 256, 257, 7, 113, 2, 2, 257, 258, 7, 112, 2, 2, 258, 259, 7, 118, 2, 2, 259, 260, 7, 107, 2, 2, 260, 261, 7, 112, 2, 2, 261, 262, 7, 119, 2, 2, 262, 263, 7, 103, 2, 2, 263, 41, 3, 2, 2, 2, 264, 265, 7, 100, 2, 2, 265, 266, 7, 116, 2, 2, 266, 267, 7, 103, 2, 2, 267, 268, 7, 99, 2, 2, 268, 269, 7, 109, 2, 2, 269, 43, 3, 2, 2, 2, 270, 271, 7, 116, 2, 2, 271, 272, 7, 103, 2, 2, 272, 273, 7, 118, 2, 2, 273, 274, 7, 119, 2, 2, 274, 275, 7, 116, 2, 2, 275, 276, 7, 112, 2, 2, 276, 45, 3, 2, 2, 2, 277, 278, 7, 112, 2, 2, 278, 279, 7, 103, 2, 2, 279, 280, 7, 121, 2, 2, 280, 47, 3, 2, 2, 2, 281, 282, 7, 118, 2, 2, 282, 283, 7, 116, 2, 2, 283, 284, 7, 123, 2, 2, 284, 49, 3, 2, 2, 2, 285, 286, 7, 101, 2, 2, 286, 287, 7, 99, 2, 2, 287, 288, 7, 118, 2, 2, 288, 289, 7, 101, 2, 2, 289, 290, 7, 106, 2, 2, 290, 51, 3, 2, 2, 2, 291, 292, 7, 118, 2, 2, 292, 293, 7, 106, 2, 2, 293, 294, 7, 116, 2, 2, 294, 295, 7, 113, 2, 2, 295, 296, 7, 121, 2, 2, 296, 53, 3, 2, 2, 2, 297, 298, 7, 118, 2, 2, 298, 299, 7, 106, 2, 2, 299, 300, 7, 107, 2, 2, 300, 301, 7, 117, 2, 2, 301, 55, 3, 2, 2, 2, 302, 303, 7, 107, 2, 2, 303, 304, 7, 112, 2, 2, 304, 305, 7, 117, 2, 2, 305, 306, 7, 118, 2, 2, 306, 307, 7, 99, 2, 2, 307, 308, 7, 112, 2, 2, 308, 309, 7, 101, 2, 2, 309, 310, 7, 103, 2, 2, 310, 311, 7, 113, 2, 2, 311, 312, 7, 104, 2, 2, 312, 57, 3, 2, 2, 2, 313, 314, 7, 35, 2, 2, 314, 59, 3, 2, 2, 2, 315, 316, 7, 128, 2, 2, 316, 61, 3, 2, 2, 2, 317, 318, 7, 44, 2, 2, 318, 63, 3, 2, 2, 2, 319, 320, 7, 49, 2, 2, 320, 321, 6, 32, 2, 2, 321, 65, 3, 2, 2, 2, 322, 323, 7, 39, 2, 2, 323, 67, 3, 2, 2, 2, 324, 325, 7, 45, 2, 2, 325, 69, 3, 2, 2, 2, 326, 327, 7, 47, 2, 2, 327, 71, 3, 2, 2, 2, 328, 329, 7, 62, 2, 2, 329, 330, 7, 62, 2, 2, 330, 73, 3, 2, 2, 2, 331, 332, 7, 64, 2, 2, 332, 333, 7, 64, 2, 2, 333, 75, 3, 2, 2, 2, 334, 335, 7, 64, 2, 2, 335, 336, 7, 64, 2, 2, 336, 337, 7, 64, 2, 2, 337, 77, 3, 2, 2, 2, 338, 339, 7, 62, 2, 2, 339, 79, 3, 2, 2, 2, 340, 341, 7, 62, 2, 2, 341, 342, 7, 63, 2, 2, 342, 81, 3, 2, 2, 2, 343, 344, 7, 64, 2, 2, 344, 83, 3, 2, 2, 2, 345, 346, 7, 64, 2, 2, 346, 347, 7, 63, 2, 2, 347, 85, 3, 2, 2, 2, 348, 349, 7, 63, 2, 2, 349, 350, 7, 63, 2, 2, 350, 87, 3, 2, 2, 2, 351, 352, 7, 63, 2, 2, 352, 353, 7, 63, 2, 2, 353, 354, 7, 63, 2, 2, 354, 89, 3, 2, 2, 2, 355, 356, 7, 35, 2, 2, 356, 357, 7, 63, 2, 2, 357, 91, 3, 2, 2, 2, 358, 359, 7, 35, 2, 2, 359, 360, 7, 63, 2, 2, 360, 361, 7, 63, 2, 2, 361, 93, 3, 2, 2, 2, 362, 363, 7, 40, 2, 2, 363, 95, 3, 2, 2, 2, 364, 365, 7, 96, 2, 2, 365, 97, 3, 2, 2, 2, 366, 367, 7, 126, 2, 2, 367, 99, 3, 2, 2, 2, 368, 369, 7, 40, 2, 2, 369, 370, 7, 40, 2, 2, 370, 101, 3, 2, 2, 2, 371, 372, 7, 126, 2, 2, 372, 373, 7, 126, 2, 2, 373, 103, 3, 2, 2, 2, 374, 375, 7, 65, 2, 2, 375, 105, 3, 2, 2, 2, 376, 377, 7, 60, 2, 2, 377, 107, 3, 2, 2, 2, 378, 379, 7, 65, 2, 2, 379, 380, 7, 60, 2, 2, 380, 109, 3, 2, 2, 2, 381, 382, 7, 60, 2, 2, 382, 383, 7, 60, 2, 2, 383, 111, 3, 2, 2, 2, 384, 385, 7, 47, 2, 2, 385, 386, 7, 64, 2, 2, 386, 113, 3, 2, 2, 2, 387, 388, 7, 63, 2, 2, 388, 389, 7, 128, 2, 2, 389, 115, 3, 2, 2, 2, 390, 391, 7, 63, 2, 2, 391, 392, 7, 63, 2, 2, 392, 393, 7, 128, 2, 2, 393, 117, 3, 2, 2, 2, 394, 395, 7, 45, 2, 2, 395, 396, 7, 45, 2, 2, 396, 119, 3, 2, 2, 2, 397, 398, 7, 47, 2, 2, 398, 399, 7, 47, 2, 2, 399, 121, 3, 2, 2, 2, 400, 401, 7, 63, 2, 2, 401, 123, 3, 2, 2, 2, 402, 403, 7, 45, 2, 2, 403, 404, 7, 63, 2, 2, 404, 125, 3, 2, 2, 2, 405, 406, 7, 47, 2, 2, 406, 407, 7, 63, 2, 2, 407, 127, 3, 2, 2, 2, 408, 409, 7, 44, 2, 2, 409, 410, 7, 63, 2, 2, 410, 129, 3, 2, 2, 2, 411, 412, 7, 49, 2, 2, 412, 413, 7, 63, 2, 2, 413, 131, 3, 2, 2, 2, 414, 415, 7, 39, 2, 2, 415, 416, 7, 63, 2, 2, 416, 133, 3, 2, 2, 2, 417, 418, 7, 40, 2, 2, 418, 419, 7, 63, 2, 2, 419, 135, 3, 2, 2, 2, 420, 421, 7, 96, 2, 2, 421, 422, 7, 63, 2, 2, 422, 137, 3, 2, 2, 2, 423, 424, 7, 126, 2, 2, 424, 425, 7, 63, 2, 2, 425, 139, 3, 2, 2, 2, 426, 427, 7, 62, 2, 2, 427, 428, 7, 62, 2, 2, 428, 429, 7, 63, 2, 2, 429, 141, 3, 2, 2, 2, 430, 431, 7, 64, 2, 2, 431, 432, 7, 64, 2, 2, 432, 433, 7, 63, 2, 2, 433, 143, 3, 2, 2, 2, 434, 435, 7, 64, 2, 2, 435, 436, 7, 64, 2, 2, 436, 437, 7, 64, 2, 2, 437, 438, 7, 63, 2, 2, 438, 145, 3, 2, 2, 2, 439, 441, 7, 50, 2, 2, 440, 442, 9, 4, 2, 2, 441, 440, 3, 2, 2, 2, 442, 443, 3, 2, 2, 2, 443, 441, 3, 2, 2, 2, 443, 444, 3, 2, 2, 2, 444, 446, 3, 2, 2, 2, 445, 447, 9, 5, 2, 2, 446, 445, 3, 2, 2, 2, 446, 447, 3, 2, 2, 2, 447, 147, 3, 2, 2, 2, 448, 449, 7, 50, 2, 2, 449, 451, 9, 6, 2, 2, 450, 452, 9, 7, 2, 2, 451, 450, 3, 2, 2, 2, 452, 453, 3, 2, 2, 2, 453, 451, 3, 2, 2, 2, 453, 454, 3, 2, 2, 2, 454, 456, 3, 2, 2, 2, 455, 457, 9, 5, 2, 2, 456, 455, 3, 2, 2, 2, 456, 457, 3, 2, 2, 2, 457, 149, 3, 2, 2, 2, 458, 467, 7, 50, 2, 2, 459, 463, 9, 8, 2, 2, 460, 462, 9, 9, 2, 2, 461, 460, 3, 2, 2, 2, 462, 465, 3, 2, 2, 2, 463, 461, 3, 2, 2, 2, 463, 464, 3, 2, 2, 2, 464, 467, 3, 2, 2, 2, 465, 463, 3, 2, 2, 2, 466, 458, 3, 2, 2, 2, 466, 459, 3, 2, 2, 2, 467, 469, 3, 2, 2, 2, 468, 470, 9, 10, 2, 2, 469, 468, 3, 2, 2, 2, 469, 470, 3, 2, 2, 2, 470, 151, 3, 2, 2, 2, 471, 480, 7, 50, 2, 2, 472, 476, 9, 8, 2, 2, 473, 475, 9, 9, 2, 2, 474, 473, 3, 2, 2, 2, 475, 478, 3, 2, 2, 2, 476, 474, 3, 2, 2, 2, 476, 477, 3, 2, 2, 2, 477, 480, 3, 2, 2, 2, 478, 476, 3, 2, 2, 2, 479, 471, 3, 2, 2, 2, 479, 472, 3, 2, 2, 2, 480, 487, 3, 2, 2, 2, 481, 483, 5, 20, 10, 2, 482, 484, 9, 9, 2, 2, 483, 482, 3, 2, 2, 2, 484, 485, 3, 2, 2, 2, 485, 483, 3, 2, 2, 2, 485, 486, 3, 2, 2, 2, 486, 488, 3, 2, 2, 2, 487, 481, 3, 2, 2, 2, 487, 488, 3, 2, 2, 2, 488, 498, 3, 2, 2, 2, 489, 491, 9, 11, 2, 2, 490, 492, 9, 12, 2, 2, 491, 490, 3, 2, 2, 2, 491, 492, 3, 2, 2, 2, 492, 494, 3, 2, 2, 2, 493, 495, 9, 9, 2, 2, 494, 493, 3, 2, 2, 2, 495, 496, 3, 2, 2, 2, 496, 494, 3, 2, 2, 2, 496, 497, 3, 2, 2, 2, 497, 499, 3, 2, 2, 2, 498, 489, 3, 2, 2, 2, 498, 499, 3, 2, 2, 2, 499, 501, 3, 2, 2, 2, 500, 502, 9, 13, 2, 2, 501, 500, 3, 2, 2, 2, 501, 502, 3, 2, 2, 2, 502, 153, 3, 2, 2, 2, 503, 511, 7, 36, 2, 2, 504, 505, 7, 94, 2, 2, 505, 510, 7, 36, 2, 2, 506, 507, 7, 94, 2, 2, 507, 510, 7, 94, 2, 2, 508, 510, 10, 14, 2, 2, 509, 504, 3, 2, 2, 2, 509, 506, 3, 2, 2, 2, 509, 508, 3, 2, 2, 2, 510, 513, 3, 2, 2, 2, 511, 512, 3, 2, 2, 2, 511, 509, 3, 2, 2, 2, 512, 514, 3, 2, 2, 2, 513, 511, 3, 2, 2, 2, 514, 528, 7, 36, 2, 2, 515, 523, 7, 41, 2, 2, 516, 517, 7, 94, 2, 2, 517, 522, 7, 41, 2, 2, 518, 519, 7, 94, 2, 2, 519, 522, 7, 94, 2, 2, 520, 522, 10, 15, 2, 2, 521, 516, 3, 2, 2, 2, 521, 518, 3, 2, 2, 2, 521, 520, 3, 2, 2, 2, 522, 525, 3, 2, 2, 2, 523, 524, 3, 2, 2, 2, 523, 521, 3, 2, 2, 2, 524, 526, 3, 2, 2, 2, 525, 523, 3, 2, 2, 2, 526, 528, 7, 41, 2, 2, 527, 503, 3, 2, 2, 2, 527, 515, 3, 2, 2, 2, 528, 155, 3, 2, 2, 2, 529, 533, 7, 49, 2, 2, 530, 531, 7, 94, 2, 2, 531, 534, 10, 16, 2, 2, 532, 534, 10, 17, 2, 2, 533, 530, 3, 2, 2, 2, 533, 532, 3, 2, 2, 2, 534, 535, 3, 2, 2, 2, 535, 536, 3, 2, 2, 2, 535, 533, 3, 2, 2, 2, 536, 537, 3, 2, 2, 2, 537, 541, 7, 49, 2, 2, 538, 540, 9, 18, 2, 2, 539, 538, 3, 2, 2, 2, 540, 543, 3, 2, 2, 2, 541, 539, 3, 2, 2, 2, 541, 542, 3, 2, 2, 2, 542, 544, 3, 2, 2, 2, 543, 541, 3, 2, 2, 2, 544, 545, 6, 78, 3, 2, 545, 157, 3, 2, 2, 2, 546, 547, 7, 118, 2, 2, 547, 548, 7, 116, 2, 2, 548, 549, 7, 119, 2, 2, 549, 550, 7, 103, 2, 2, 550, 159, 3, 2, 2, 2, 551, 552, 7, 104, 2, 2, 552, 553, 7, 99, 2, 2, 553, 554, 7, 110, 2, 2, 554, 555, 7, 117, 2, 2, 555, 556, 7, 103, 2, 2, 556, 161, 3, 2, 2, 2, 557, 558, 7, 112, 2, 2, 558, 559, 7, 119, 2, 2, 559, 560, 7, 110, 2, 2, 560, 561, 7, 110, 2, 2, 561, 163, 3, 2, 2, 2, 562, 563, 7, 100, 2, 2, 563, 564, 7, 113, 2, 2, 564, 565, 7, 113, 2, 2, 565, 566, 7, 110, 2, 2, 566, 567, 7, 103, 2, 2, 567, 568, 7, 99, 2, 2, 568, 601, 7, 112, 2, 2, 569, 570, 7, 100, 2, 2, 570, 571, 7, 123, 2, 2, 571, 572, 7, 118, 2, 2, 572, 601, 7, 103, 2, 2, 573, 574, 7, 117, 2, 2, 574, 575, 7, 106, 2, 2, 575, 576, 7, 113, 2, 2, 576, 577, 7, 116, 2, 2, 577, 601, 7, 118, 2, 2, 578, 579, 7, 101, 2, 2, 579, 580, 7, 106, 2, 2, 580, 581, 7, 99, 2, 2, 581, 601, 7, 116, 2, 2, 582, 583, 7, 107, 2, 2, 583, 584, 7, 112, 2, 2, 584, 601, 7, 118, 2, 2, 585, 586, 7, 110, 2, 2, 586, 587, 7, 113, 2, 2, 587, 588, 7, 112, 2, 2, 588, 601, 7, 105, 2, 2, 589, 590, 7, 104, 2, 2, 590, 591, 7, 110, 2, 2, 591, 592, 7, 113, 2, 2, 592, 593, 7, 99, 2, 2, 593, 601, 7, 118, 2, 2, 594, 595, 7, 102, 2, 2, 595, 596, 7, 113, 2, 2, 596, 597, 7, 119, 2, 2, 597, 598, 7, 100, 2, 2, 598, 599, 7, 110, 2, 2, 599, 601, 7, 103, 2, 2, 600, 562, 3, 2, 2, 2, 600, 569, 3, 2, 2, 2, 600, 573, 3, 2, 2, 2, 600, 578, 3, 2, 2, 2, 600, 582, 3, 2, 2, 2, 600, 585, 3, 2, 2, 2, 600, 589, 3, 2, 2, 2, 600, 594, 3, 2, 2, 2, 601, 165, 3, 2, 2, 2, 602, 603, 7, 102, 2, 2, 603, 604, 7, 103, 2, 2, 604, 605, 7, 104, 2, 2, 605, 167, 3, 2, 2, 2, 606, 610, 9, 19, 2, 2, 607, 609, 9, 20, 2, 2, 608, 607, 3, 2, 2, 2, 609, 612, 3, 2, 2, 2, 610, 608, 3, 2, 2, 2, 610, 611, 3, 2, 2, 2, 611, 169, 3, 2, 2, 2, 612, 610, 3, 2, 2, 2, 613, 622, 7, 50, 2, 2, 614, 618, 9, 8, 2, 2, 615, 617, 9, 9, 2, 2, 616, 615, 3, 2, 2, 2, 617, 620, 3, 2, 2, 2, 618, 616, 3, 2, 2, 2, 618, 619, 3, 2, 2, 2, 619, 622, 3, 2, 2, 2, 620, 618, 3, 2, 2, 2, 621, 613, 3, 2, 2, 2, 621, 614, 3, 2, 2, 2, 622, 623, 3, 2, 2, 2, 623, 624, 8, 85, 4, 2, 624, 171, 3, 2, 2, 2, 625, 629, 9, 19, 2, 2, 626, 628, 9, 20, 2, 2, 627, 626, 3, 2, 2, 2, 628, 631, 3, 2, 2, 2, 629, 627, 3, 2, 2, 2, 629, 630, 3, 2, 2, 2, 630, 632, 3, 2, 2, 2, 631, 629, 3, 2, 2, 2, 632, 633, 8, 86, 4, 2, 633, 173, 3, 2, 2, 2, 36, 2, 3, 177, 187, 197, 202, 443, 446, 453, 456, 463, 466, 469, 476, 479, 485, 487, 491, 496, 498, 501, 509, 511, 521, 523, 527, 533, 535, 541, 600, 610, 618, 621, 629, 5, 8, 2, 2, 4, 3, 2, 4, 2, 2] \ No newline at end of file +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 88, 638, 8, 1, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 4, 68, 9, 68, 4, 69, 9, 69, 4, 70, 9, 70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 4, 74, 9, 74, 4, 75, 9, 75, 4, 76, 9, 76, 4, 77, 9, 77, 4, 78, 9, 78, 4, 79, 9, 79, 4, 80, 9, 80, 4, 81, 9, 81, 4, 82, 9, 82, 4, 83, 9, 83, 4, 84, 9, 84, 4, 85, 9, 85, 4, 86, 9, 86, 4, 87, 9, 87, 3, 2, 6, 2, 178, 10, 2, 13, 2, 14, 2, 179, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 188, 10, 3, 12, 3, 14, 3, 191, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 198, 10, 3, 12, 3, 14, 3, 201, 11, 3, 3, 3, 3, 3, 5, 3, 205, 10, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 31, 3, 31, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 35, 3, 35, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 45, 3, 45, 3, 45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 3, 57, 3, 57, 3, 57, 3, 58, 3, 58, 3, 58, 3, 59, 3, 59, 3, 59, 3, 59, 3, 60, 3, 60, 3, 60, 3, 61, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 63, 3, 64, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 3, 67, 3, 67, 3, 67, 3, 68, 3, 68, 3, 68, 3, 69, 3, 69, 3, 69, 3, 70, 3, 70, 3, 70, 3, 71, 3, 71, 3, 71, 3, 71, 3, 72, 3, 72, 3, 72, 3, 72, 3, 73, 3, 73, 3, 73, 3, 73, 3, 73, 3, 74, 3, 74, 6, 74, 446, 10, 74, 13, 74, 14, 74, 447, 3, 74, 5, 74, 451, 10, 74, 3, 75, 3, 75, 3, 75, 6, 75, 456, 10, 75, 13, 75, 14, 75, 457, 3, 75, 5, 75, 461, 10, 75, 3, 76, 3, 76, 3, 76, 7, 76, 466, 10, 76, 12, 76, 14, 76, 469, 11, 76, 5, 76, 471, 10, 76, 3, 76, 5, 76, 474, 10, 76, 3, 77, 3, 77, 3, 77, 7, 77, 479, 10, 77, 12, 77, 14, 77, 482, 11, 77, 5, 77, 484, 10, 77, 3, 77, 3, 77, 6, 77, 488, 10, 77, 13, 77, 14, 77, 489, 5, 77, 492, 10, 77, 3, 77, 3, 77, 5, 77, 496, 10, 77, 3, 77, 6, 77, 499, 10, 77, 13, 77, 14, 77, 500, 5, 77, 503, 10, 77, 3, 77, 5, 77, 506, 10, 77, 3, 78, 3, 78, 3, 78, 3, 78, 3, 78, 3, 78, 7, 78, 514, 10, 78, 12, 78, 14, 78, 517, 11, 78, 3, 78, 3, 78, 3, 78, 3, 78, 3, 78, 3, 78, 3, 78, 7, 78, 526, 10, 78, 12, 78, 14, 78, 529, 11, 78, 3, 78, 5, 78, 532, 10, 78, 3, 79, 3, 79, 3, 79, 3, 79, 6, 79, 538, 10, 79, 13, 79, 14, 79, 539, 3, 79, 3, 79, 7, 79, 544, 10, 79, 12, 79, 14, 79, 547, 11, 79, 3, 79, 3, 79, 3, 80, 3, 80, 3, 80, 3, 80, 3, 80, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 81, 3, 82, 3, 82, 3, 82, 3, 82, 3, 82, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 3, 83, 5, 83, 605, 10, 83, 3, 84, 3, 84, 3, 84, 3, 84, 3, 85, 3, 85, 7, 85, 613, 10, 85, 12, 85, 14, 85, 616, 11, 85, 3, 86, 3, 86, 3, 86, 7, 86, 621, 10, 86, 12, 86, 14, 86, 624, 11, 86, 5, 86, 626, 10, 86, 3, 86, 3, 86, 3, 87, 3, 87, 7, 87, 632, 10, 87, 12, 87, 14, 87, 635, 11, 87, 3, 87, 3, 87, 7, 189, 199, 515, 527, 539, 2, 2, 88, 4, 2, 3, 6, 2, 4, 8, 2, 5, 10, 2, 6, 12, 2, 7, 14, 2, 8, 16, 2, 9, 18, 2, 10, 20, 2, 11, 22, 2, 12, 24, 2, 13, 26, 2, 14, 28, 2, 15, 30, 2, 16, 32, 2, 17, 34, 2, 18, 36, 2, 19, 38, 2, 20, 40, 2, 21, 42, 2, 22, 44, 2, 23, 46, 2, 24, 48, 2, 25, 50, 2, 26, 52, 2, 27, 54, 2, 28, 56, 2, 29, 58, 2, 30, 60, 2, 31, 62, 2, 32, 64, 2, 33, 66, 2, 34, 68, 2, 35, 70, 2, 36, 72, 2, 37, 74, 2, 38, 76, 2, 39, 78, 2, 40, 80, 2, 41, 82, 2, 42, 84, 2, 43, 86, 2, 44, 88, 2, 45, 90, 2, 46, 92, 2, 47, 94, 2, 48, 96, 2, 49, 98, 2, 50, 100, 2, 51, 102, 2, 52, 104, 2, 53, 106, 2, 54, 108, 2, 55, 110, 2, 56, 112, 2, 57, 114, 2, 58, 116, 2, 59, 118, 2, 60, 120, 2, 61, 122, 2, 62, 124, 2, 63, 126, 2, 64, 128, 2, 65, 130, 2, 66, 132, 2, 67, 134, 2, 68, 136, 2, 69, 138, 2, 70, 140, 2, 71, 142, 2, 72, 144, 2, 73, 146, 2, 74, 148, 2, 75, 150, 2, 76, 152, 2, 77, 154, 2, 78, 156, 2, 79, 158, 2, 80, 160, 2, 81, 162, 2, 82, 164, 2, 83, 166, 2, 84, 168, 2, 85, 170, 2, 86, 172, 2, 87, 174, 2, 88, 4, 2, 3, 21, 5, 2, 11, 12, 15, 15, 34, 34, 4, 2, 12, 12, 15, 15, 3, 2, 50, 57, 4, 2, 78, 78, 110, 110, 4, 2, 90, 90, 122, 122, 5, 2, 50, 59, 67, 72, 99, 104, 3, 2, 51, 59, 3, 2, 50, 59, 8, 2, 70, 70, 72, 72, 78, 78, 102, 102, 104, 104, 110, 110, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 6, 2, 70, 70, 72, 72, 102, 102, 104, 104, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2, 12, 12, 4, 2, 12, 12, 49, 49, 9, 2, 87, 87, 101, 101, 107, 107, 110, 111, 117, 117, 119, 119, 122, 122, 5, 2, 67, 92, 97, 97, 99, 124, 6, 2, 50, 59, 67, 92, 97, 97, 99, 124, 2, 676, 2, 4, 3, 2, 2, 2, 2, 6, 3, 2, 2, 2, 2, 8, 3, 2, 2, 2, 2, 10, 3, 2, 2, 2, 2, 12, 3, 2, 2, 2, 2, 14, 3, 2, 2, 2, 2, 16, 3, 2, 2, 2, 2, 18, 3, 2, 2, 2, 2, 20, 3, 2, 2, 2, 2, 22, 3, 2, 2, 2, 2, 24, 3, 2, 2, 2, 2, 26, 3, 2, 2, 2, 2, 28, 3, 2, 2, 2, 2, 30, 3, 2, 2, 2, 2, 32, 3, 2, 2, 2, 2, 34, 3, 2, 2, 2, 2, 36, 3, 2, 2, 2, 2, 38, 3, 2, 2, 2, 2, 40, 3, 2, 2, 2, 2, 42, 3, 2, 2, 2, 2, 44, 3, 2, 2, 2, 2, 46, 3, 2, 2, 2, 2, 48, 3, 2, 2, 2, 2, 50, 3, 2, 2, 2, 2, 52, 3, 2, 2, 2, 2, 54, 3, 2, 2, 2, 2, 56, 3, 2, 2, 2, 2, 58, 3, 2, 2, 2, 2, 60, 3, 2, 2, 2, 2, 62, 3, 2, 2, 2, 2, 64, 3, 2, 2, 2, 2, 66, 3, 2, 2, 2, 2, 68, 3, 2, 2, 2, 2, 70, 3, 2, 2, 2, 2, 72, 3, 2, 2, 2, 2, 74, 3, 2, 2, 2, 2, 76, 3, 2, 2, 2, 2, 78, 3, 2, 2, 2, 2, 80, 3, 2, 2, 2, 2, 82, 3, 2, 2, 2, 2, 84, 3, 2, 2, 2, 2, 86, 3, 2, 2, 2, 2, 88, 3, 2, 2, 2, 2, 90, 3, 2, 2, 2, 2, 92, 3, 2, 2, 2, 2, 94, 3, 2, 2, 2, 2, 96, 3, 2, 2, 2, 2, 98, 3, 2, 2, 2, 2, 100, 3, 2, 2, 2, 2, 102, 3, 2, 2, 2, 2, 104, 3, 2, 2, 2, 2, 106, 3, 2, 2, 2, 2, 108, 3, 2, 2, 2, 2, 110, 3, 2, 2, 2, 2, 112, 3, 2, 2, 2, 2, 114, 3, 2, 2, 2, 2, 116, 3, 2, 2, 2, 2, 118, 3, 2, 2, 2, 2, 120, 3, 2, 2, 2, 2, 122, 3, 2, 2, 2, 2, 124, 3, 2, 2, 2, 2, 126, 3, 2, 2, 2, 2, 128, 3, 2, 2, 2, 2, 130, 3, 2, 2, 2, 2, 132, 3, 2, 2, 2, 2, 134, 3, 2, 2, 2, 2, 136, 3, 2, 2, 2, 2, 138, 3, 2, 2, 2, 2, 140, 3, 2, 2, 2, 2, 142, 3, 2, 2, 2, 2, 144, 3, 2, 2, 2, 2, 146, 3, 2, 2, 2, 2, 148, 3, 2, 2, 2, 2, 150, 3, 2, 2, 2, 2, 152, 3, 2, 2, 2, 2, 154, 3, 2, 2, 2, 2, 156, 3, 2, 2, 2, 2, 158, 3, 2, 2, 2, 2, 160, 3, 2, 2, 2, 2, 162, 3, 2, 2, 2, 2, 164, 3, 2, 2, 2, 2, 166, 3, 2, 2, 2, 2, 168, 3, 2, 2, 2, 2, 170, 3, 2, 2, 2, 3, 172, 3, 2, 2, 2, 3, 174, 3, 2, 2, 2, 4, 177, 3, 2, 2, 2, 6, 204, 3, 2, 2, 2, 8, 208, 3, 2, 2, 2, 10, 210, 3, 2, 2, 2, 12, 212, 3, 2, 2, 2, 14, 214, 3, 2, 2, 2, 16, 216, 3, 2, 2, 2, 18, 218, 3, 2, 2, 2, 20, 220, 3, 2, 2, 2, 22, 222, 3, 2, 2, 2, 24, 226, 3, 2, 2, 2, 26, 231, 3, 2, 2, 2, 28, 233, 3, 2, 2, 2, 30, 235, 3, 2, 2, 2, 32, 238, 3, 2, 2, 2, 34, 241, 3, 2, 2, 2, 36, 246, 3, 2, 2, 2, 38, 252, 3, 2, 2, 2, 40, 255, 3, 2, 2, 2, 42, 259, 3, 2, 2, 2, 44, 268, 3, 2, 2, 2, 46, 274, 3, 2, 2, 2, 48, 281, 3, 2, 2, 2, 50, 285, 3, 2, 2, 2, 52, 289, 3, 2, 2, 2, 54, 295, 3, 2, 2, 2, 56, 301, 3, 2, 2, 2, 58, 306, 3, 2, 2, 2, 60, 317, 3, 2, 2, 2, 62, 319, 3, 2, 2, 2, 64, 321, 3, 2, 2, 2, 66, 323, 3, 2, 2, 2, 68, 326, 3, 2, 2, 2, 70, 328, 3, 2, 2, 2, 72, 330, 3, 2, 2, 2, 74, 332, 3, 2, 2, 2, 76, 335, 3, 2, 2, 2, 78, 338, 3, 2, 2, 2, 80, 342, 3, 2, 2, 2, 82, 344, 3, 2, 2, 2, 84, 347, 3, 2, 2, 2, 86, 349, 3, 2, 2, 2, 88, 352, 3, 2, 2, 2, 90, 355, 3, 2, 2, 2, 92, 359, 3, 2, 2, 2, 94, 362, 3, 2, 2, 2, 96, 366, 3, 2, 2, 2, 98, 368, 3, 2, 2, 2, 100, 370, 3, 2, 2, 2, 102, 372, 3, 2, 2, 2, 104, 375, 3, 2, 2, 2, 106, 378, 3, 2, 2, 2, 108, 380, 3, 2, 2, 2, 110, 382, 3, 2, 2, 2, 112, 385, 3, 2, 2, 2, 114, 388, 3, 2, 2, 2, 116, 391, 3, 2, 2, 2, 118, 394, 3, 2, 2, 2, 120, 398, 3, 2, 2, 2, 122, 401, 3, 2, 2, 2, 124, 404, 3, 2, 2, 2, 126, 406, 3, 2, 2, 2, 128, 409, 3, 2, 2, 2, 130, 412, 3, 2, 2, 2, 132, 415, 3, 2, 2, 2, 134, 418, 3, 2, 2, 2, 136, 421, 3, 2, 2, 2, 138, 424, 3, 2, 2, 2, 140, 427, 3, 2, 2, 2, 142, 430, 3, 2, 2, 2, 144, 434, 3, 2, 2, 2, 146, 438, 3, 2, 2, 2, 148, 443, 3, 2, 2, 2, 150, 452, 3, 2, 2, 2, 152, 470, 3, 2, 2, 2, 154, 483, 3, 2, 2, 2, 156, 531, 3, 2, 2, 2, 158, 533, 3, 2, 2, 2, 160, 550, 3, 2, 2, 2, 162, 555, 3, 2, 2, 2, 164, 561, 3, 2, 2, 2, 166, 604, 3, 2, 2, 2, 168, 606, 3, 2, 2, 2, 170, 610, 3, 2, 2, 2, 172, 625, 3, 2, 2, 2, 174, 629, 3, 2, 2, 2, 176, 178, 9, 2, 2, 2, 177, 176, 3, 2, 2, 2, 178, 179, 3, 2, 2, 2, 179, 177, 3, 2, 2, 2, 179, 180, 3, 2, 2, 2, 180, 181, 3, 2, 2, 2, 181, 182, 8, 2, 2, 2, 182, 5, 3, 2, 2, 2, 183, 184, 7, 49, 2, 2, 184, 185, 7, 49, 2, 2, 185, 189, 3, 2, 2, 2, 186, 188, 11, 2, 2, 2, 187, 186, 3, 2, 2, 2, 188, 191, 3, 2, 2, 2, 189, 190, 3, 2, 2, 2, 189, 187, 3, 2, 2, 2, 190, 192, 3, 2, 2, 2, 191, 189, 3, 2, 2, 2, 192, 205, 9, 3, 2, 2, 193, 194, 7, 49, 2, 2, 194, 195, 7, 44, 2, 2, 195, 199, 3, 2, 2, 2, 196, 198, 11, 2, 2, 2, 197, 196, 3, 2, 2, 2, 198, 201, 3, 2, 2, 2, 199, 200, 3, 2, 2, 2, 199, 197, 3, 2, 2, 2, 200, 202, 3, 2, 2, 2, 201, 199, 3, 2, 2, 2, 202, 203, 7, 44, 2, 2, 203, 205, 7, 49, 2, 2, 204, 183, 3, 2, 2, 2, 204, 193, 3, 2, 2, 2, 205, 206, 3, 2, 2, 2, 206, 207, 8, 3, 2, 2, 207, 7, 3, 2, 2, 2, 208, 209, 7, 125, 2, 2, 209, 9, 3, 2, 2, 2, 210, 211, 7, 127, 2, 2, 211, 11, 3, 2, 2, 2, 212, 213, 7, 93, 2, 2, 213, 13, 3, 2, 2, 2, 214, 215, 7, 95, 2, 2, 215, 15, 3, 2, 2, 2, 216, 217, 7, 42, 2, 2, 217, 17, 3, 2, 2, 2, 218, 219, 7, 43, 2, 2, 219, 19, 3, 2, 2, 2, 220, 221, 7, 38, 2, 2, 221, 21, 3, 2, 2, 2, 222, 223, 7, 48, 2, 2, 223, 224, 3, 2, 2, 2, 224, 225, 8, 11, 3, 2, 225, 23, 3, 2, 2, 2, 226, 227, 7, 65, 2, 2, 227, 228, 7, 48, 2, 2, 228, 229, 3, 2, 2, 2, 229, 230, 8, 12, 3, 2, 230, 25, 3, 2, 2, 2, 231, 232, 7, 46, 2, 2, 232, 27, 3, 2, 2, 2, 233, 234, 7, 61, 2, 2, 234, 29, 3, 2, 2, 2, 235, 236, 7, 107, 2, 2, 236, 237, 7, 104, 2, 2, 237, 31, 3, 2, 2, 2, 238, 239, 7, 107, 2, 2, 239, 240, 7, 112, 2, 2, 240, 33, 3, 2, 2, 2, 241, 242, 7, 103, 2, 2, 242, 243, 7, 110, 2, 2, 243, 244, 7, 117, 2, 2, 244, 245, 7, 103, 2, 2, 245, 35, 3, 2, 2, 2, 246, 247, 7, 121, 2, 2, 247, 248, 7, 106, 2, 2, 248, 249, 7, 107, 2, 2, 249, 250, 7, 110, 2, 2, 250, 251, 7, 103, 2, 2, 251, 37, 3, 2, 2, 2, 252, 253, 7, 102, 2, 2, 253, 254, 7, 113, 2, 2, 254, 39, 3, 2, 2, 2, 255, 256, 7, 104, 2, 2, 256, 257, 7, 113, 2, 2, 257, 258, 7, 116, 2, 2, 258, 41, 3, 2, 2, 2, 259, 260, 7, 101, 2, 2, 260, 261, 7, 113, 2, 2, 261, 262, 7, 112, 2, 2, 262, 263, 7, 118, 2, 2, 263, 264, 7, 107, 2, 2, 264, 265, 7, 112, 2, 2, 265, 266, 7, 119, 2, 2, 266, 267, 7, 103, 2, 2, 267, 43, 3, 2, 2, 2, 268, 269, 7, 100, 2, 2, 269, 270, 7, 116, 2, 2, 270, 271, 7, 103, 2, 2, 271, 272, 7, 99, 2, 2, 272, 273, 7, 109, 2, 2, 273, 45, 3, 2, 2, 2, 274, 275, 7, 116, 2, 2, 275, 276, 7, 103, 2, 2, 276, 277, 7, 118, 2, 2, 277, 278, 7, 119, 2, 2, 278, 279, 7, 116, 2, 2, 279, 280, 7, 112, 2, 2, 280, 47, 3, 2, 2, 2, 281, 282, 7, 112, 2, 2, 282, 283, 7, 103, 2, 2, 283, 284, 7, 121, 2, 2, 284, 49, 3, 2, 2, 2, 285, 286, 7, 118, 2, 2, 286, 287, 7, 116, 2, 2, 287, 288, 7, 123, 2, 2, 288, 51, 3, 2, 2, 2, 289, 290, 7, 101, 2, 2, 290, 291, 7, 99, 2, 2, 291, 292, 7, 118, 2, 2, 292, 293, 7, 101, 2, 2, 293, 294, 7, 106, 2, 2, 294, 53, 3, 2, 2, 2, 295, 296, 7, 118, 2, 2, 296, 297, 7, 106, 2, 2, 297, 298, 7, 116, 2, 2, 298, 299, 7, 113, 2, 2, 299, 300, 7, 121, 2, 2, 300, 55, 3, 2, 2, 2, 301, 302, 7, 118, 2, 2, 302, 303, 7, 106, 2, 2, 303, 304, 7, 107, 2, 2, 304, 305, 7, 117, 2, 2, 305, 57, 3, 2, 2, 2, 306, 307, 7, 107, 2, 2, 307, 308, 7, 112, 2, 2, 308, 309, 7, 117, 2, 2, 309, 310, 7, 118, 2, 2, 310, 311, 7, 99, 2, 2, 311, 312, 7, 112, 2, 2, 312, 313, 7, 101, 2, 2, 313, 314, 7, 103, 2, 2, 314, 315, 7, 113, 2, 2, 315, 316, 7, 104, 2, 2, 316, 59, 3, 2, 2, 2, 317, 318, 7, 35, 2, 2, 318, 61, 3, 2, 2, 2, 319, 320, 7, 128, 2, 2, 320, 63, 3, 2, 2, 2, 321, 322, 7, 44, 2, 2, 322, 65, 3, 2, 2, 2, 323, 324, 7, 49, 2, 2, 324, 325, 6, 33, 2, 2, 325, 67, 3, 2, 2, 2, 326, 327, 7, 39, 2, 2, 327, 69, 3, 2, 2, 2, 328, 329, 7, 45, 2, 2, 329, 71, 3, 2, 2, 2, 330, 331, 7, 47, 2, 2, 331, 73, 3, 2, 2, 2, 332, 333, 7, 62, 2, 2, 333, 334, 7, 62, 2, 2, 334, 75, 3, 2, 2, 2, 335, 336, 7, 64, 2, 2, 336, 337, 7, 64, 2, 2, 337, 77, 3, 2, 2, 2, 338, 339, 7, 64, 2, 2, 339, 340, 7, 64, 2, 2, 340, 341, 7, 64, 2, 2, 341, 79, 3, 2, 2, 2, 342, 343, 7, 62, 2, 2, 343, 81, 3, 2, 2, 2, 344, 345, 7, 62, 2, 2, 345, 346, 7, 63, 2, 2, 346, 83, 3, 2, 2, 2, 347, 348, 7, 64, 2, 2, 348, 85, 3, 2, 2, 2, 349, 350, 7, 64, 2, 2, 350, 351, 7, 63, 2, 2, 351, 87, 3, 2, 2, 2, 352, 353, 7, 63, 2, 2, 353, 354, 7, 63, 2, 2, 354, 89, 3, 2, 2, 2, 355, 356, 7, 63, 2, 2, 356, 357, 7, 63, 2, 2, 357, 358, 7, 63, 2, 2, 358, 91, 3, 2, 2, 2, 359, 360, 7, 35, 2, 2, 360, 361, 7, 63, 2, 2, 361, 93, 3, 2, 2, 2, 362, 363, 7, 35, 2, 2, 363, 364, 7, 63, 2, 2, 364, 365, 7, 63, 2, 2, 365, 95, 3, 2, 2, 2, 366, 367, 7, 40, 2, 2, 367, 97, 3, 2, 2, 2, 368, 369, 7, 96, 2, 2, 369, 99, 3, 2, 2, 2, 370, 371, 7, 126, 2, 2, 371, 101, 3, 2, 2, 2, 372, 373, 7, 40, 2, 2, 373, 374, 7, 40, 2, 2, 374, 103, 3, 2, 2, 2, 375, 376, 7, 126, 2, 2, 376, 377, 7, 126, 2, 2, 377, 105, 3, 2, 2, 2, 378, 379, 7, 65, 2, 2, 379, 107, 3, 2, 2, 2, 380, 381, 7, 60, 2, 2, 381, 109, 3, 2, 2, 2, 382, 383, 7, 65, 2, 2, 383, 384, 7, 60, 2, 2, 384, 111, 3, 2, 2, 2, 385, 386, 7, 60, 2, 2, 386, 387, 7, 60, 2, 2, 387, 113, 3, 2, 2, 2, 388, 389, 7, 47, 2, 2, 389, 390, 7, 64, 2, 2, 390, 115, 3, 2, 2, 2, 391, 392, 7, 63, 2, 2, 392, 393, 7, 128, 2, 2, 393, 117, 3, 2, 2, 2, 394, 395, 7, 63, 2, 2, 395, 396, 7, 63, 2, 2, 396, 397, 7, 128, 2, 2, 397, 119, 3, 2, 2, 2, 398, 399, 7, 45, 2, 2, 399, 400, 7, 45, 2, 2, 400, 121, 3, 2, 2, 2, 401, 402, 7, 47, 2, 2, 402, 403, 7, 47, 2, 2, 403, 123, 3, 2, 2, 2, 404, 405, 7, 63, 2, 2, 405, 125, 3, 2, 2, 2, 406, 407, 7, 45, 2, 2, 407, 408, 7, 63, 2, 2, 408, 127, 3, 2, 2, 2, 409, 410, 7, 47, 2, 2, 410, 411, 7, 63, 2, 2, 411, 129, 3, 2, 2, 2, 412, 413, 7, 44, 2, 2, 413, 414, 7, 63, 2, 2, 414, 131, 3, 2, 2, 2, 415, 416, 7, 49, 2, 2, 416, 417, 7, 63, 2, 2, 417, 133, 3, 2, 2, 2, 418, 419, 7, 39, 2, 2, 419, 420, 7, 63, 2, 2, 420, 135, 3, 2, 2, 2, 421, 422, 7, 40, 2, 2, 422, 423, 7, 63, 2, 2, 423, 137, 3, 2, 2, 2, 424, 425, 7, 96, 2, 2, 425, 426, 7, 63, 2, 2, 426, 139, 3, 2, 2, 2, 427, 428, 7, 126, 2, 2, 428, 429, 7, 63, 2, 2, 429, 141, 3, 2, 2, 2, 430, 431, 7, 62, 2, 2, 431, 432, 7, 62, 2, 2, 432, 433, 7, 63, 2, 2, 433, 143, 3, 2, 2, 2, 434, 435, 7, 64, 2, 2, 435, 436, 7, 64, 2, 2, 436, 437, 7, 63, 2, 2, 437, 145, 3, 2, 2, 2, 438, 439, 7, 64, 2, 2, 439, 440, 7, 64, 2, 2, 440, 441, 7, 64, 2, 2, 441, 442, 7, 63, 2, 2, 442, 147, 3, 2, 2, 2, 443, 445, 7, 50, 2, 2, 444, 446, 9, 4, 2, 2, 445, 444, 3, 2, 2, 2, 446, 447, 3, 2, 2, 2, 447, 445, 3, 2, 2, 2, 447, 448, 3, 2, 2, 2, 448, 450, 3, 2, 2, 2, 449, 451, 9, 5, 2, 2, 450, 449, 3, 2, 2, 2, 450, 451, 3, 2, 2, 2, 451, 149, 3, 2, 2, 2, 452, 453, 7, 50, 2, 2, 453, 455, 9, 6, 2, 2, 454, 456, 9, 7, 2, 2, 455, 454, 3, 2, 2, 2, 456, 457, 3, 2, 2, 2, 457, 455, 3, 2, 2, 2, 457, 458, 3, 2, 2, 2, 458, 460, 3, 2, 2, 2, 459, 461, 9, 5, 2, 2, 460, 459, 3, 2, 2, 2, 460, 461, 3, 2, 2, 2, 461, 151, 3, 2, 2, 2, 462, 471, 7, 50, 2, 2, 463, 467, 9, 8, 2, 2, 464, 466, 9, 9, 2, 2, 465, 464, 3, 2, 2, 2, 466, 469, 3, 2, 2, 2, 467, 465, 3, 2, 2, 2, 467, 468, 3, 2, 2, 2, 468, 471, 3, 2, 2, 2, 469, 467, 3, 2, 2, 2, 470, 462, 3, 2, 2, 2, 470, 463, 3, 2, 2, 2, 471, 473, 3, 2, 2, 2, 472, 474, 9, 10, 2, 2, 473, 472, 3, 2, 2, 2, 473, 474, 3, 2, 2, 2, 474, 153, 3, 2, 2, 2, 475, 484, 7, 50, 2, 2, 476, 480, 9, 8, 2, 2, 477, 479, 9, 9, 2, 2, 478, 477, 3, 2, 2, 2, 479, 482, 3, 2, 2, 2, 480, 478, 3, 2, 2, 2, 480, 481, 3, 2, 2, 2, 481, 484, 3, 2, 2, 2, 482, 480, 3, 2, 2, 2, 483, 475, 3, 2, 2, 2, 483, 476, 3, 2, 2, 2, 484, 491, 3, 2, 2, 2, 485, 487, 5, 22, 11, 2, 486, 488, 9, 9, 2, 2, 487, 486, 3, 2, 2, 2, 488, 489, 3, 2, 2, 2, 489, 487, 3, 2, 2, 2, 489, 490, 3, 2, 2, 2, 490, 492, 3, 2, 2, 2, 491, 485, 3, 2, 2, 2, 491, 492, 3, 2, 2, 2, 492, 502, 3, 2, 2, 2, 493, 495, 9, 11, 2, 2, 494, 496, 9, 12, 2, 2, 495, 494, 3, 2, 2, 2, 495, 496, 3, 2, 2, 2, 496, 498, 3, 2, 2, 2, 497, 499, 9, 9, 2, 2, 498, 497, 3, 2, 2, 2, 499, 500, 3, 2, 2, 2, 500, 498, 3, 2, 2, 2, 500, 501, 3, 2, 2, 2, 501, 503, 3, 2, 2, 2, 502, 493, 3, 2, 2, 2, 502, 503, 3, 2, 2, 2, 503, 505, 3, 2, 2, 2, 504, 506, 9, 13, 2, 2, 505, 504, 3, 2, 2, 2, 505, 506, 3, 2, 2, 2, 506, 155, 3, 2, 2, 2, 507, 515, 7, 36, 2, 2, 508, 509, 7, 94, 2, 2, 509, 514, 7, 36, 2, 2, 510, 511, 7, 94, 2, 2, 511, 514, 7, 94, 2, 2, 512, 514, 10, 14, 2, 2, 513, 508, 3, 2, 2, 2, 513, 510, 3, 2, 2, 2, 513, 512, 3, 2, 2, 2, 514, 517, 3, 2, 2, 2, 515, 516, 3, 2, 2, 2, 515, 513, 3, 2, 2, 2, 516, 518, 3, 2, 2, 2, 517, 515, 3, 2, 2, 2, 518, 532, 7, 36, 2, 2, 519, 527, 7, 41, 2, 2, 520, 521, 7, 94, 2, 2, 521, 526, 7, 41, 2, 2, 522, 523, 7, 94, 2, 2, 523, 526, 7, 94, 2, 2, 524, 526, 10, 15, 2, 2, 525, 520, 3, 2, 2, 2, 525, 522, 3, 2, 2, 2, 525, 524, 3, 2, 2, 2, 526, 529, 3, 2, 2, 2, 527, 528, 3, 2, 2, 2, 527, 525, 3, 2, 2, 2, 528, 530, 3, 2, 2, 2, 529, 527, 3, 2, 2, 2, 530, 532, 7, 41, 2, 2, 531, 507, 3, 2, 2, 2, 531, 519, 3, 2, 2, 2, 532, 157, 3, 2, 2, 2, 533, 537, 7, 49, 2, 2, 534, 535, 7, 94, 2, 2, 535, 538, 10, 16, 2, 2, 536, 538, 10, 17, 2, 2, 537, 534, 3, 2, 2, 2, 537, 536, 3, 2, 2, 2, 538, 539, 3, 2, 2, 2, 539, 540, 3, 2, 2, 2, 539, 537, 3, 2, 2, 2, 540, 541, 3, 2, 2, 2, 541, 545, 7, 49, 2, 2, 542, 544, 9, 18, 2, 2, 543, 542, 3, 2, 2, 2, 544, 547, 3, 2, 2, 2, 545, 543, 3, 2, 2, 2, 545, 546, 3, 2, 2, 2, 546, 548, 3, 2, 2, 2, 547, 545, 3, 2, 2, 2, 548, 549, 6, 79, 3, 2, 549, 159, 3, 2, 2, 2, 550, 551, 7, 118, 2, 2, 551, 552, 7, 116, 2, 2, 552, 553, 7, 119, 2, 2, 553, 554, 7, 103, 2, 2, 554, 161, 3, 2, 2, 2, 555, 556, 7, 104, 2, 2, 556, 557, 7, 99, 2, 2, 557, 558, 7, 110, 2, 2, 558, 559, 7, 117, 2, 2, 559, 560, 7, 103, 2, 2, 560, 163, 3, 2, 2, 2, 561, 562, 7, 112, 2, 2, 562, 563, 7, 119, 2, 2, 563, 564, 7, 110, 2, 2, 564, 565, 7, 110, 2, 2, 565, 165, 3, 2, 2, 2, 566, 567, 7, 100, 2, 2, 567, 568, 7, 113, 2, 2, 568, 569, 7, 113, 2, 2, 569, 570, 7, 110, 2, 2, 570, 571, 7, 103, 2, 2, 571, 572, 7, 99, 2, 2, 572, 605, 7, 112, 2, 2, 573, 574, 7, 100, 2, 2, 574, 575, 7, 123, 2, 2, 575, 576, 7, 118, 2, 2, 576, 605, 7, 103, 2, 2, 577, 578, 7, 117, 2, 2, 578, 579, 7, 106, 2, 2, 579, 580, 7, 113, 2, 2, 580, 581, 7, 116, 2, 2, 581, 605, 7, 118, 2, 2, 582, 583, 7, 101, 2, 2, 583, 584, 7, 106, 2, 2, 584, 585, 7, 99, 2, 2, 585, 605, 7, 116, 2, 2, 586, 587, 7, 107, 2, 2, 587, 588, 7, 112, 2, 2, 588, 605, 7, 118, 2, 2, 589, 590, 7, 110, 2, 2, 590, 591, 7, 113, 2, 2, 591, 592, 7, 112, 2, 2, 592, 605, 7, 105, 2, 2, 593, 594, 7, 104, 2, 2, 594, 595, 7, 110, 2, 2, 595, 596, 7, 113, 2, 2, 596, 597, 7, 99, 2, 2, 597, 605, 7, 118, 2, 2, 598, 599, 7, 102, 2, 2, 599, 600, 7, 113, 2, 2, 600, 601, 7, 119, 2, 2, 601, 602, 7, 100, 2, 2, 602, 603, 7, 110, 2, 2, 603, 605, 7, 103, 2, 2, 604, 566, 3, 2, 2, 2, 604, 573, 3, 2, 2, 2, 604, 577, 3, 2, 2, 2, 604, 582, 3, 2, 2, 2, 604, 586, 3, 2, 2, 2, 604, 589, 3, 2, 2, 2, 604, 593, 3, 2, 2, 2, 604, 598, 3, 2, 2, 2, 605, 167, 3, 2, 2, 2, 606, 607, 7, 102, 2, 2, 607, 608, 7, 103, 2, 2, 608, 609, 7, 104, 2, 2, 609, 169, 3, 2, 2, 2, 610, 614, 9, 19, 2, 2, 611, 613, 9, 20, 2, 2, 612, 611, 3, 2, 2, 2, 613, 616, 3, 2, 2, 2, 614, 612, 3, 2, 2, 2, 614, 615, 3, 2, 2, 2, 615, 171, 3, 2, 2, 2, 616, 614, 3, 2, 2, 2, 617, 626, 7, 50, 2, 2, 618, 622, 9, 8, 2, 2, 619, 621, 9, 9, 2, 2, 620, 619, 3, 2, 2, 2, 621, 624, 3, 2, 2, 2, 622, 620, 3, 2, 2, 2, 622, 623, 3, 2, 2, 2, 623, 626, 3, 2, 2, 2, 624, 622, 3, 2, 2, 2, 625, 617, 3, 2, 2, 2, 625, 618, 3, 2, 2, 2, 626, 627, 3, 2, 2, 2, 627, 628, 8, 86, 4, 2, 628, 173, 3, 2, 2, 2, 629, 633, 9, 19, 2, 2, 630, 632, 9, 20, 2, 2, 631, 630, 3, 2, 2, 2, 632, 635, 3, 2, 2, 2, 633, 631, 3, 2, 2, 2, 633, 634, 3, 2, 2, 2, 634, 636, 3, 2, 2, 2, 635, 633, 3, 2, 2, 2, 636, 637, 8, 87, 4, 2, 637, 175, 3, 2, 2, 2, 36, 2, 3, 179, 189, 199, 204, 447, 450, 457, 460, 467, 470, 473, 480, 483, 489, 491, 495, 500, 502, 505, 513, 515, 525, 527, 531, 537, 539, 545, 604, 614, 622, 625, 633, 5, 8, 2, 2, 4, 3, 2, 4, 2, 2] \ No newline at end of file diff --git a/packages/kbn-monaco/src/painless/antlr/painless_lexer.tokens b/packages/kbn-monaco/src/painless/antlr/painless_lexer.tokens index ff62343c92ba5..3a1af2a5154d3 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_lexer.tokens +++ b/packages/kbn-monaco/src/painless/antlr/painless_lexer.tokens @@ -6,153 +6,155 @@ LBRACE=5 RBRACE=6 LP=7 RP=8 -DOT=9 -NSDOT=10 -COMMA=11 -SEMICOLON=12 -IF=13 -IN=14 -ELSE=15 -WHILE=16 -DO=17 -FOR=18 -CONTINUE=19 -BREAK=20 -RETURN=21 -NEW=22 -TRY=23 -CATCH=24 -THROW=25 -THIS=26 -INSTANCEOF=27 -BOOLNOT=28 -BWNOT=29 -MUL=30 -DIV=31 -REM=32 -ADD=33 -SUB=34 -LSH=35 -RSH=36 -USH=37 -LT=38 -LTE=39 -GT=40 -GTE=41 -EQ=42 -EQR=43 -NE=44 -NER=45 -BWAND=46 -XOR=47 -BWOR=48 -BOOLAND=49 -BOOLOR=50 -COND=51 -COLON=52 -ELVIS=53 -REF=54 -ARROW=55 -FIND=56 -MATCH=57 -INCR=58 -DECR=59 -ASSIGN=60 -AADD=61 -ASUB=62 -AMUL=63 -ADIV=64 -AREM=65 -AAND=66 -AXOR=67 -AOR=68 -ALSH=69 -ARSH=70 -AUSH=71 -OCTAL=72 -HEX=73 -INTEGER=74 -DECIMAL=75 -STRING=76 -REGEX=77 -TRUE=78 -FALSE=79 -NULL=80 -PRIMITIVE=81 -DEF=82 -ID=83 -DOTINTEGER=84 -DOTID=85 +DOLLAR=9 +DOT=10 +NSDOT=11 +COMMA=12 +SEMICOLON=13 +IF=14 +IN=15 +ELSE=16 +WHILE=17 +DO=18 +FOR=19 +CONTINUE=20 +BREAK=21 +RETURN=22 +NEW=23 +TRY=24 +CATCH=25 +THROW=26 +THIS=27 +INSTANCEOF=28 +BOOLNOT=29 +BWNOT=30 +MUL=31 +DIV=32 +REM=33 +ADD=34 +SUB=35 +LSH=36 +RSH=37 +USH=38 +LT=39 +LTE=40 +GT=41 +GTE=42 +EQ=43 +EQR=44 +NE=45 +NER=46 +BWAND=47 +XOR=48 +BWOR=49 +BOOLAND=50 +BOOLOR=51 +COND=52 +COLON=53 +ELVIS=54 +REF=55 +ARROW=56 +FIND=57 +MATCH=58 +INCR=59 +DECR=60 +ASSIGN=61 +AADD=62 +ASUB=63 +AMUL=64 +ADIV=65 +AREM=66 +AAND=67 +AXOR=68 +AOR=69 +ALSH=70 +ARSH=71 +AUSH=72 +OCTAL=73 +HEX=74 +INTEGER=75 +DECIMAL=76 +STRING=77 +REGEX=78 +TRUE=79 +FALSE=80 +NULL=81 +PRIMITIVE=82 +DEF=83 +ID=84 +DOTINTEGER=85 +DOTID=86 '{'=3 '}'=4 '['=5 ']'=6 '('=7 ')'=8 -'.'=9 -'?.'=10 -','=11 -';'=12 -'if'=13 -'in'=14 -'else'=15 -'while'=16 -'do'=17 -'for'=18 -'continue'=19 -'break'=20 -'return'=21 -'new'=22 -'try'=23 -'catch'=24 -'throw'=25 -'this'=26 -'instanceof'=27 -'!'=28 -'~'=29 -'*'=30 -'/'=31 -'%'=32 -'+'=33 -'-'=34 -'<<'=35 -'>>'=36 -'>>>'=37 -'<'=38 -'<='=39 -'>'=40 -'>='=41 -'=='=42 -'==='=43 -'!='=44 -'!=='=45 -'&'=46 -'^'=47 -'|'=48 -'&&'=49 -'||'=50 -'?'=51 -':'=52 -'?:'=53 -'::'=54 -'->'=55 -'=~'=56 -'==~'=57 -'++'=58 -'--'=59 -'='=60 -'+='=61 -'-='=62 -'*='=63 -'/='=64 -'%='=65 -'&='=66 -'^='=67 -'|='=68 -'<<='=69 -'>>='=70 -'>>>='=71 -'true'=78 -'false'=79 -'null'=80 -'def'=82 +'$'=9 +'.'=10 +'?.'=11 +','=12 +';'=13 +'if'=14 +'in'=15 +'else'=16 +'while'=17 +'do'=18 +'for'=19 +'continue'=20 +'break'=21 +'return'=22 +'new'=23 +'try'=24 +'catch'=25 +'throw'=26 +'this'=27 +'instanceof'=28 +'!'=29 +'~'=30 +'*'=31 +'/'=32 +'%'=33 +'+'=34 +'-'=35 +'<<'=36 +'>>'=37 +'>>>'=38 +'<'=39 +'<='=40 +'>'=41 +'>='=42 +'=='=43 +'==='=44 +'!='=45 +'!=='=46 +'&'=47 +'^'=48 +'|'=49 +'&&'=50 +'||'=51 +'?'=52 +':'=53 +'?:'=54 +'::'=55 +'->'=56 +'=~'=57 +'==~'=58 +'++'=59 +'--'=60 +'='=61 +'+='=62 +'-='=63 +'*='=64 +'/='=65 +'%='=66 +'&='=67 +'^='=68 +'|='=69 +'<<='=70 +'>>='=71 +'>>>='=72 +'true'=79 +'false'=80 +'null'=81 +'def'=83 diff --git a/packages/kbn-monaco/src/painless/antlr/painless_lexer.ts b/packages/kbn-monaco/src/painless/antlr/painless_lexer.ts index eb335c73d94b2..5c102850d89f7 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_lexer.ts +++ b/packages/kbn-monaco/src/painless/antlr/painless_lexer.ts @@ -25,83 +25,84 @@ export class painless_lexer extends Lexer { public static readonly RBRACE = 6; public static readonly LP = 7; public static readonly RP = 8; - public static readonly DOT = 9; - public static readonly NSDOT = 10; - public static readonly COMMA = 11; - public static readonly SEMICOLON = 12; - public static readonly IF = 13; - public static readonly IN = 14; - public static readonly ELSE = 15; - public static readonly WHILE = 16; - public static readonly DO = 17; - public static readonly FOR = 18; - public static readonly CONTINUE = 19; - public static readonly BREAK = 20; - public static readonly RETURN = 21; - public static readonly NEW = 22; - public static readonly TRY = 23; - public static readonly CATCH = 24; - public static readonly THROW = 25; - public static readonly THIS = 26; - public static readonly INSTANCEOF = 27; - public static readonly BOOLNOT = 28; - public static readonly BWNOT = 29; - public static readonly MUL = 30; - public static readonly DIV = 31; - public static readonly REM = 32; - public static readonly ADD = 33; - public static readonly SUB = 34; - public static readonly LSH = 35; - public static readonly RSH = 36; - public static readonly USH = 37; - public static readonly LT = 38; - public static readonly LTE = 39; - public static readonly GT = 40; - public static readonly GTE = 41; - public static readonly EQ = 42; - public static readonly EQR = 43; - public static readonly NE = 44; - public static readonly NER = 45; - public static readonly BWAND = 46; - public static readonly XOR = 47; - public static readonly BWOR = 48; - public static readonly BOOLAND = 49; - public static readonly BOOLOR = 50; - public static readonly COND = 51; - public static readonly COLON = 52; - public static readonly ELVIS = 53; - public static readonly REF = 54; - public static readonly ARROW = 55; - public static readonly FIND = 56; - public static readonly MATCH = 57; - public static readonly INCR = 58; - public static readonly DECR = 59; - public static readonly ASSIGN = 60; - public static readonly AADD = 61; - public static readonly ASUB = 62; - public static readonly AMUL = 63; - public static readonly ADIV = 64; - public static readonly AREM = 65; - public static readonly AAND = 66; - public static readonly AXOR = 67; - public static readonly AOR = 68; - public static readonly ALSH = 69; - public static readonly ARSH = 70; - public static readonly AUSH = 71; - public static readonly OCTAL = 72; - public static readonly HEX = 73; - public static readonly INTEGER = 74; - public static readonly DECIMAL = 75; - public static readonly STRING = 76; - public static readonly REGEX = 77; - public static readonly TRUE = 78; - public static readonly FALSE = 79; - public static readonly NULL = 80; - public static readonly PRIMITIVE = 81; - public static readonly DEF = 82; - public static readonly ID = 83; - public static readonly DOTINTEGER = 84; - public static readonly DOTID = 85; + public static readonly DOLLAR = 9; + public static readonly DOT = 10; + public static readonly NSDOT = 11; + public static readonly COMMA = 12; + public static readonly SEMICOLON = 13; + public static readonly IF = 14; + public static readonly IN = 15; + public static readonly ELSE = 16; + public static readonly WHILE = 17; + public static readonly DO = 18; + public static readonly FOR = 19; + public static readonly CONTINUE = 20; + public static readonly BREAK = 21; + public static readonly RETURN = 22; + public static readonly NEW = 23; + public static readonly TRY = 24; + public static readonly CATCH = 25; + public static readonly THROW = 26; + public static readonly THIS = 27; + public static readonly INSTANCEOF = 28; + public static readonly BOOLNOT = 29; + public static readonly BWNOT = 30; + public static readonly MUL = 31; + public static readonly DIV = 32; + public static readonly REM = 33; + public static readonly ADD = 34; + public static readonly SUB = 35; + public static readonly LSH = 36; + public static readonly RSH = 37; + public static readonly USH = 38; + public static readonly LT = 39; + public static readonly LTE = 40; + public static readonly GT = 41; + public static readonly GTE = 42; + public static readonly EQ = 43; + public static readonly EQR = 44; + public static readonly NE = 45; + public static readonly NER = 46; + public static readonly BWAND = 47; + public static readonly XOR = 48; + public static readonly BWOR = 49; + public static readonly BOOLAND = 50; + public static readonly BOOLOR = 51; + public static readonly COND = 52; + public static readonly COLON = 53; + public static readonly ELVIS = 54; + public static readonly REF = 55; + public static readonly ARROW = 56; + public static readonly FIND = 57; + public static readonly MATCH = 58; + public static readonly INCR = 59; + public static readonly DECR = 60; + public static readonly ASSIGN = 61; + public static readonly AADD = 62; + public static readonly ASUB = 63; + public static readonly AMUL = 64; + public static readonly ADIV = 65; + public static readonly AREM = 66; + public static readonly AAND = 67; + public static readonly AXOR = 68; + public static readonly AOR = 69; + public static readonly ALSH = 70; + public static readonly ARSH = 71; + public static readonly AUSH = 72; + public static readonly OCTAL = 73; + public static readonly HEX = 74; + public static readonly INTEGER = 75; + public static readonly DECIMAL = 76; + public static readonly STRING = 77; + public static readonly REGEX = 78; + public static readonly TRUE = 79; + public static readonly FALSE = 80; + public static readonly NULL = 81; + public static readonly PRIMITIVE = 82; + public static readonly DEF = 83; + public static readonly ID = 84; + public static readonly DOTINTEGER = 85; + public static readonly DOTID = 86; public static readonly AFTER_DOT = 1; // tslint:disable:no-trailing-whitespace @@ -115,41 +116,41 @@ export class painless_lexer extends Lexer { ]; public static readonly ruleNames: string[] = [ - "WS", "COMMENT", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "LP", "RP", "DOT", - "NSDOT", "COMMA", "SEMICOLON", "IF", "IN", "ELSE", "WHILE", "DO", "FOR", - "CONTINUE", "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", "THIS", - "INSTANCEOF", "BOOLNOT", "BWNOT", "MUL", "DIV", "REM", "ADD", "SUB", "LSH", - "RSH", "USH", "LT", "LTE", "GT", "GTE", "EQ", "EQR", "NE", "NER", "BWAND", - "XOR", "BWOR", "BOOLAND", "BOOLOR", "COND", "COLON", "ELVIS", "REF", "ARROW", - "FIND", "MATCH", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", "AMUL", "ADIV", - "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", "OCTAL", "HEX", - "INTEGER", "DECIMAL", "STRING", "REGEX", "TRUE", "FALSE", "NULL", "PRIMITIVE", - "DEF", "ID", "DOTINTEGER", "DOTID", + "WS", "COMMENT", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "LP", "RP", "DOLLAR", + "DOT", "NSDOT", "COMMA", "SEMICOLON", "IF", "IN", "ELSE", "WHILE", "DO", + "FOR", "CONTINUE", "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", + "THIS", "INSTANCEOF", "BOOLNOT", "BWNOT", "MUL", "DIV", "REM", "ADD", + "SUB", "LSH", "RSH", "USH", "LT", "LTE", "GT", "GTE", "EQ", "EQR", "NE", + "NER", "BWAND", "XOR", "BWOR", "BOOLAND", "BOOLOR", "COND", "COLON", "ELVIS", + "REF", "ARROW", "FIND", "MATCH", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", + "AMUL", "ADIV", "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", + "OCTAL", "HEX", "INTEGER", "DECIMAL", "STRING", "REGEX", "TRUE", "FALSE", + "NULL", "PRIMITIVE", "DEF", "ID", "DOTINTEGER", "DOTID", ]; private static readonly _LITERAL_NAMES: Array = [ - undefined, undefined, undefined, "'{'", "'}'", "'['", "']'", "'('", "')'", - "'.'", "'?.'", "','", "';'", "'if'", "'in'", "'else'", "'while'", "'do'", - "'for'", "'continue'", "'break'", "'return'", "'new'", "'try'", "'catch'", - "'throw'", "'this'", "'instanceof'", "'!'", "'~'", "'*'", "'/'", "'%'", - "'+'", "'-'", "'<<'", "'>>'", "'>>>'", "'<'", "'<='", "'>'", "'>='", "'=='", - "'==='", "'!='", "'!=='", "'&'", "'^'", "'|'", "'&&'", "'||'", "'?'", - "':'", "'?:'", "'::'", "'->'", "'=~'", "'==~'", "'++'", "'--'", "'='", - "'+='", "'-='", "'*='", "'/='", "'%='", "'&='", "'^='", "'|='", "'<<='", - "'>>='", "'>>>='", undefined, undefined, undefined, undefined, undefined, - undefined, "'true'", "'false'", "'null'", undefined, "'def'", + undefined, undefined, undefined, "'{'", "'}'", "'['", "']'", "'('", "')'", + "'$'", "'.'", "'?.'", "','", "';'", "'if'", "'in'", "'else'", "'while'", + "'do'", "'for'", "'continue'", "'break'", "'return'", "'new'", "'try'", + "'catch'", "'throw'", "'this'", "'instanceof'", "'!'", "'~'", "'*'", "'/'", + "'%'", "'+'", "'-'", "'<<'", "'>>'", "'>>>'", "'<'", "'<='", "'>'", "'>='", + "'=='", "'==='", "'!='", "'!=='", "'&'", "'^'", "'|'", "'&&'", "'||'", + "'?'", "':'", "'?:'", "'::'", "'->'", "'=~'", "'==~'", "'++'", "'--'", + "'='", "'+='", "'-='", "'*='", "'/='", "'%='", "'&='", "'^='", "'|='", + "'<<='", "'>>='", "'>>>='", undefined, undefined, undefined, undefined, + undefined, undefined, "'true'", "'false'", "'null'", undefined, "'def'", ]; private static readonly _SYMBOLIC_NAMES: Array = [ - undefined, "WS", "COMMENT", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "LP", - "RP", "DOT", "NSDOT", "COMMA", "SEMICOLON", "IF", "IN", "ELSE", "WHILE", - "DO", "FOR", "CONTINUE", "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", - "THIS", "INSTANCEOF", "BOOLNOT", "BWNOT", "MUL", "DIV", "REM", "ADD", - "SUB", "LSH", "RSH", "USH", "LT", "LTE", "GT", "GTE", "EQ", "EQR", "NE", - "NER", "BWAND", "XOR", "BWOR", "BOOLAND", "BOOLOR", "COND", "COLON", "ELVIS", - "REF", "ARROW", "FIND", "MATCH", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", - "AMUL", "ADIV", "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", - "OCTAL", "HEX", "INTEGER", "DECIMAL", "STRING", "REGEX", "TRUE", "FALSE", - "NULL", "PRIMITIVE", "DEF", "ID", "DOTINTEGER", "DOTID", + undefined, "WS", "COMMENT", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "LP", + "RP", "DOLLAR", "DOT", "NSDOT", "COMMA", "SEMICOLON", "IF", "IN", "ELSE", + "WHILE", "DO", "FOR", "CONTINUE", "BREAK", "RETURN", "NEW", "TRY", "CATCH", + "THROW", "THIS", "INSTANCEOF", "BOOLNOT", "BWNOT", "MUL", "DIV", "REM", + "ADD", "SUB", "LSH", "RSH", "USH", "LT", "LTE", "GT", "GTE", "EQ", "EQR", + "NE", "NER", "BWAND", "XOR", "BWOR", "BOOLAND", "BOOLOR", "COND", "COLON", + "ELVIS", "REF", "ARROW", "FIND", "MATCH", "INCR", "DECR", "ASSIGN", "AADD", + "ASUB", "AMUL", "ADIV", "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", + "AUSH", "OCTAL", "HEX", "INTEGER", "DECIMAL", "STRING", "REGEX", "TRUE", + "FALSE", "NULL", "PRIMITIVE", "DEF", "ID", "DOTINTEGER", "DOTID", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(painless_lexer._LITERAL_NAMES, painless_lexer._SYMBOLIC_NAMES, []); @@ -185,13 +186,13 @@ export class painless_lexer extends Lexer { public sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { switch (ruleIndex) { // DO NOT CHANGE - // This is a manual fix to handle slashes appropriately - case 31: + // This is a manual fix to handle slashes appropriately, DIV: 32 + case 32: return this.DIV_sempred(_localctx, predIndex); // DO NOT CHANGE - // This is a manual fix to handle regexes appropriately - case 77: + // This is a manual fix to handle regexes appropriately, REGEX: 78 + case 78: return this.REGEX_sempred(_localctx, predIndex); } return true; @@ -213,7 +214,7 @@ export class painless_lexer extends Lexer { private static readonly _serializedATNSegments: number = 2; private static readonly _serializedATNSegment0: string = - "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02W\u027A\b\x01" + + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02X\u027E\b\x01" + "\b\x01\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06" + "\t\x06\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f" + "\x04\r\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04" + @@ -225,299 +226,301 @@ export class painless_lexer extends Lexer { "3\t3\x044\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04;\t;\x04" + "<\t<\x04=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x04C\tC\x04D\tD\x04" + "E\tE\x04F\tF\x04G\tG\x04H\tH\x04I\tI\x04J\tJ\x04K\tK\x04L\tL\x04M\tM\x04" + - "N\tN\x04O\tO\x04P\tP\x04Q\tQ\x04R\tR\x04S\tS\x04T\tT\x04U\tU\x04V\tV\x03" + - "\x02\x06\x02\xB0\n\x02\r\x02\x0E\x02\xB1\x03\x02\x03\x02\x03\x03\x03\x03" + - "\x03\x03\x03\x03\x07\x03\xBA\n\x03\f\x03\x0E\x03\xBD\v\x03\x03\x03\x03" + - "\x03\x03\x03\x03\x03\x03\x03\x07\x03\xC4\n\x03\f\x03\x0E\x03\xC7\v\x03" + - "\x03\x03\x03\x03\x05\x03\xCB\n\x03\x03\x03\x03\x03\x03\x04\x03\x04\x03" + - "\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03\b\x03\b\x03\t\x03\t\x03" + - "\n\x03\n\x03\n\x03\n\x03\v\x03\v\x03\v\x03\v\x03\v\x03\f\x03\f\x03\r\x03" + - "\r\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x10\x03\x10\x03" + - "\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x11\x03\x11\x03\x11\x03\x11\x03" + - "\x12\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03" + - "\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03\x15\x03\x15\x03" + - "\x15\x03\x15\x03\x15\x03\x15\x03\x16\x03\x16\x03\x16\x03\x16\x03\x16\x03" + - "\x16\x03\x16\x03\x17\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18\x03\x18\x03" + - "\x18\x03\x19\x03\x19\x03\x19\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03" + - "\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B\x03\x1B\x03\x1B\x03" + - "\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03" + - "\x1C\x03\x1C\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03 \x03" + - " \x03 \x03!\x03!\x03\"\x03\"\x03#\x03#\x03$\x03$\x03$\x03%\x03%\x03%\x03" + - "&\x03&\x03&\x03&\x03\'\x03\'\x03(\x03(\x03(\x03)\x03)\x03*\x03*\x03*\x03" + - "+\x03+\x03+\x03,\x03,\x03,\x03,\x03-\x03-\x03-\x03.\x03.\x03.\x03.\x03" + - "/\x03/\x030\x030\x031\x031\x032\x032\x032\x033\x033\x033\x034\x034\x03" + - "5\x035\x036\x036\x036\x037\x037\x037\x038\x038\x038\x039\x039\x039\x03" + - ":\x03:\x03:\x03:\x03;\x03;\x03;\x03<\x03<\x03<\x03=\x03=\x03>\x03>\x03" + - ">\x03?\x03?\x03?\x03@\x03@\x03@\x03A\x03A\x03A\x03B\x03B\x03B\x03C\x03" + - "C\x03C\x03D\x03D\x03D\x03E\x03E\x03E\x03F\x03F\x03F\x03F\x03G\x03G\x03" + - "G\x03G\x03H\x03H\x03H\x03H\x03H\x03I\x03I\x06I\u01BA\nI\rI\x0EI\u01BB" + - "\x03I\x05I\u01BF\nI\x03J\x03J\x03J\x06J\u01C4\nJ\rJ\x0EJ\u01C5\x03J\x05" + - "J\u01C9\nJ\x03K\x03K\x03K\x07K\u01CE\nK\fK\x0EK\u01D1\vK\x05K\u01D3\n" + - "K\x03K\x05K\u01D6\nK\x03L\x03L\x03L\x07L\u01DB\nL\fL\x0EL\u01DE\vL\x05" + - "L\u01E0\nL\x03L\x03L\x06L\u01E4\nL\rL\x0EL\u01E5\x05L\u01E8\nL\x03L\x03" + - "L\x05L\u01EC\nL\x03L\x06L\u01EF\nL\rL\x0EL\u01F0\x05L\u01F3\nL\x03L\x05" + - "L\u01F6\nL\x03M\x03M\x03M\x03M\x03M\x03M\x07M\u01FE\nM\fM\x0EM\u0201\v" + - "M\x03M\x03M\x03M\x03M\x03M\x03M\x03M\x07M\u020A\nM\fM\x0EM\u020D\vM\x03" + - "M\x05M\u0210\nM\x03N\x03N\x03N\x03N\x06N\u0216\nN\rN\x0EN\u0217\x03N\x03" + - "N\x07N\u021C\nN\fN\x0EN\u021F\vN\x03N\x03N\x03O\x03O\x03O\x03O\x03O\x03" + - "P\x03P\x03P\x03P\x03P\x03P\x03Q\x03Q\x03Q\x03Q\x03Q\x03R\x03R\x03R\x03" + - "R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03" + - "R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03R\x03" + - "R\x03R\x03R\x03R\x03R\x03R\x03R\x05R\u0259\nR\x03S\x03S\x03S\x03S\x03" + - "T\x03T\x07T\u0261\nT\fT\x0ET\u0264\vT\x03U\x03U\x03U\x07U\u0269\nU\fU" + - "\x0EU\u026C\vU\x05U\u026E\nU\x03U\x03U\x03V\x03V\x07V\u0274\nV\fV\x0E" + - "V\u0277\vV\x03V\x03V\x07\xBB\xC5\u01FF\u020B\u0217\x02\x02W\x04\x02\x03" + - "\x06\x02\x04\b\x02\x05\n\x02\x06\f\x02\x07\x0E\x02\b\x10\x02\t\x12\x02" + - "\n\x14\x02\v\x16\x02\f\x18\x02\r\x1A\x02\x0E\x1C\x02\x0F\x1E\x02\x10 " + - "\x02\x11\"\x02\x12$\x02\x13&\x02\x14(\x02\x15*\x02\x16,\x02\x17.\x02\x18" + - "0\x02\x192\x02\x1A4\x02\x1B6\x02\x1C8\x02\x1D:\x02\x1E<\x02\x1F>\x02 " + - "@\x02!B\x02\"D\x02#F\x02$H\x02%J\x02&L\x02\'N\x02(P\x02)R\x02*T\x02+V" + - "\x02,X\x02-Z\x02.\\\x02/^\x020`\x021b\x022d\x023f\x024h\x025j\x026l\x02" + - "7n\x028p\x029r\x02:t\x02;v\x02|\x02?~\x02@\x80\x02A\x82\x02" + - "B\x84\x02C\x86\x02D\x88\x02E\x8A\x02F\x8C\x02G\x8E\x02H\x90\x02I\x92\x02" + - "J\x94\x02K\x96\x02L\x98\x02M\x9A\x02N\x9C\x02O\x9E\x02P\xA0\x02Q\xA2\x02" + - "R\xA4\x02S\xA6\x02T\xA8\x02U\xAA\x02V\xAC\x02W\x04\x02\x03\x15\x05\x02" + - "\v\f\x0F\x0F\"\"\x04\x02\f\f\x0F\x0F\x03\x0229\x04\x02NNnn\x04\x02ZZz" + - "z\x05\x022;CHch\x03\x023;\x03\x022;\b\x02FFHHNNffhhnn\x04\x02GGgg\x04" + - "\x02--//\x06\x02FFHHffhh\x04\x02$$^^\x04\x02))^^\x03\x02\f\f\x04\x02\f" + - "\f11\t\x02WWeekknouuwwzz\x05\x02C\\aac|\x06\x022;C\\aac|\x02\u02A0\x02" + - "\x04\x03\x02\x02\x02\x02\x06\x03\x02\x02\x02\x02\b\x03\x02\x02\x02\x02" + - "\n\x03\x02\x02\x02\x02\f\x03\x02\x02\x02\x02\x0E\x03\x02\x02\x02\x02\x10" + - "\x03\x02\x02\x02\x02\x12\x03\x02\x02\x02\x02\x14\x03\x02\x02\x02\x02\x16" + - "\x03\x02\x02\x02\x02\x18\x03\x02\x02\x02\x02\x1A\x03\x02\x02\x02\x02\x1C" + - "\x03\x02\x02\x02\x02\x1E\x03\x02\x02\x02\x02 \x03\x02\x02\x02\x02\"\x03" + - "\x02\x02\x02\x02$\x03\x02\x02\x02\x02&\x03\x02\x02\x02\x02(\x03\x02\x02" + - "\x02\x02*\x03\x02\x02\x02\x02,\x03\x02\x02\x02\x02.\x03\x02\x02\x02\x02" + - "0\x03\x02\x02\x02\x022\x03\x02\x02\x02\x024\x03\x02\x02\x02\x026\x03\x02" + - "\x02\x02\x028\x03\x02\x02\x02\x02:\x03\x02\x02\x02\x02<\x03\x02\x02\x02" + - "\x02>\x03\x02\x02\x02\x02@\x03\x02\x02\x02\x02B\x03\x02\x02\x02\x02D\x03" + - "\x02\x02\x02\x02F\x03\x02\x02\x02\x02H\x03\x02\x02\x02\x02J\x03\x02\x02" + - "\x02\x02L\x03\x02\x02\x02\x02N\x03\x02\x02\x02\x02P\x03\x02\x02\x02\x02" + - "R\x03\x02\x02\x02\x02T\x03\x02\x02\x02\x02V\x03\x02\x02\x02\x02X\x03\x02" + - "\x02\x02\x02Z\x03\x02\x02\x02\x02\\\x03\x02\x02\x02\x02^\x03\x02\x02\x02" + - "\x02`\x03\x02\x02\x02\x02b\x03\x02\x02\x02\x02d\x03\x02\x02\x02\x02f\x03" + - "\x02\x02\x02\x02h\x03\x02\x02\x02\x02j\x03\x02\x02\x02\x02l\x03\x02\x02" + - "\x02\x02n\x03\x02\x02\x02\x02p\x03\x02\x02\x02\x02r\x03\x02\x02\x02\x02" + - "t\x03\x02\x02\x02\x02v\x03\x02\x02\x02\x02x\x03\x02\x02\x02\x02z\x03\x02" + - "\x02\x02\x02|\x03\x02\x02\x02\x02~\x03\x02\x02\x02\x02\x80\x03\x02\x02" + - "\x02\x02\x82\x03\x02\x02\x02\x02\x84\x03\x02\x02\x02\x02\x86\x03\x02\x02" + - "\x02\x02\x88\x03\x02\x02\x02\x02\x8A\x03\x02\x02\x02\x02\x8C\x03\x02\x02" + - "\x02\x02\x8E\x03\x02\x02\x02\x02\x90\x03\x02\x02\x02\x02\x92\x03\x02\x02" + - "\x02\x02\x94\x03\x02\x02\x02\x02\x96\x03\x02\x02\x02\x02\x98\x03\x02\x02" + - "\x02\x02\x9A\x03\x02\x02\x02\x02\x9C\x03\x02\x02\x02\x02\x9E\x03\x02\x02" + - "\x02\x02\xA0\x03\x02\x02\x02\x02\xA2\x03\x02\x02\x02\x02\xA4\x03\x02\x02" + - "\x02\x02\xA6\x03\x02\x02\x02\x02\xA8\x03\x02\x02\x02\x03\xAA\x03\x02\x02" + - "\x02\x03\xAC\x03\x02\x02\x02\x04\xAF\x03\x02\x02\x02\x06\xCA\x03\x02\x02" + - "\x02\b\xCE\x03\x02\x02\x02\n\xD0\x03\x02\x02\x02\f\xD2\x03\x02\x02\x02" + - "\x0E\xD4\x03\x02\x02\x02\x10\xD6\x03\x02\x02\x02\x12\xD8\x03\x02\x02\x02" + - "\x14\xDA\x03\x02\x02\x02\x16\xDE\x03\x02\x02\x02\x18\xE3\x03\x02\x02\x02" + - "\x1A\xE5\x03\x02\x02\x02\x1C\xE7\x03\x02\x02\x02\x1E\xEA\x03\x02\x02\x02" + - " \xED\x03\x02\x02\x02\"\xF2\x03\x02\x02\x02$\xF8\x03\x02\x02\x02&\xFB" + - "\x03\x02\x02\x02(\xFF\x03\x02\x02\x02*\u0108\x03\x02\x02\x02,\u010E\x03" + - "\x02\x02\x02.\u0115\x03\x02\x02\x020\u0119\x03\x02\x02\x022\u011D\x03" + - "\x02\x02\x024\u0123\x03\x02\x02\x026\u0129\x03\x02\x02\x028\u012E\x03" + - "\x02\x02\x02:\u0139\x03\x02\x02\x02<\u013B\x03\x02\x02\x02>\u013D\x03" + - "\x02\x02\x02@\u013F\x03\x02\x02\x02B\u0142\x03\x02\x02\x02D\u0144\x03" + - "\x02\x02\x02F\u0146\x03\x02\x02\x02H\u0148\x03\x02\x02\x02J\u014B\x03" + - "\x02\x02\x02L\u014E\x03\x02\x02\x02N\u0152\x03\x02\x02\x02P\u0154\x03" + - "\x02\x02\x02R\u0157\x03\x02\x02\x02T\u0159\x03\x02\x02\x02V\u015C\x03" + - "\x02\x02\x02X\u015F\x03\x02\x02\x02Z\u0163\x03\x02\x02\x02\\\u0166\x03" + - "\x02\x02\x02^\u016A\x03\x02\x02\x02`\u016C\x03\x02\x02\x02b\u016E\x03" + - "\x02\x02\x02d\u0170\x03\x02\x02\x02f\u0173\x03\x02\x02\x02h\u0176\x03" + - "\x02\x02\x02j\u0178\x03\x02\x02\x02l\u017A\x03\x02\x02\x02n\u017D\x03" + - "\x02\x02\x02p\u0180\x03\x02\x02\x02r\u0183\x03\x02\x02\x02t\u0186\x03" + - "\x02\x02\x02v\u018A\x03\x02\x02\x02x\u018D\x03\x02\x02\x02z\u0190\x03" + - "\x02\x02\x02|\u0192\x03\x02\x02\x02~\u0195\x03\x02\x02\x02\x80\u0198\x03" + - "\x02\x02\x02\x82\u019B\x03\x02\x02\x02\x84\u019E\x03\x02\x02\x02\x86\u01A1" + - "\x03\x02\x02\x02\x88\u01A4\x03\x02\x02\x02\x8A\u01A7\x03\x02\x02\x02\x8C" + - "\u01AA\x03\x02\x02\x02\x8E\u01AE\x03\x02\x02\x02\x90\u01B2\x03\x02\x02" + - "\x02\x92\u01B7\x03\x02\x02\x02\x94\u01C0\x03\x02\x02\x02\x96\u01D2\x03" + - "\x02\x02\x02\x98\u01DF\x03\x02\x02\x02\x9A\u020F\x03\x02\x02\x02\x9C\u0211" + - "\x03\x02\x02\x02\x9E\u0222\x03\x02\x02\x02\xA0\u0227\x03\x02\x02\x02\xA2" + - "\u022D\x03\x02\x02\x02\xA4\u0258\x03\x02\x02\x02\xA6\u025A\x03\x02\x02" + - "\x02\xA8\u025E\x03\x02\x02\x02\xAA\u026D\x03\x02\x02\x02\xAC\u0271\x03" + - "\x02\x02\x02\xAE\xB0\t\x02\x02\x02\xAF\xAE\x03\x02\x02\x02\xB0\xB1\x03" + - "\x02\x02\x02\xB1\xAF\x03\x02\x02\x02\xB1\xB2\x03\x02\x02\x02\xB2\xB3\x03" + - "\x02\x02\x02\xB3\xB4\b\x02\x02\x02\xB4\x05\x03\x02\x02\x02\xB5\xB6\x07" + - "1\x02\x02\xB6\xB7\x071\x02\x02\xB7\xBB\x03\x02\x02\x02\xB8\xBA\v\x02\x02" + - "\x02\xB9\xB8\x03\x02\x02\x02\xBA\xBD\x03\x02\x02\x02\xBB\xBC\x03\x02\x02" + - "\x02\xBB\xB9\x03\x02\x02\x02\xBC\xBE\x03\x02\x02\x02\xBD\xBB\x03\x02\x02" + - "\x02\xBE\xCB\t\x03\x02\x02\xBF\xC0\x071\x02\x02\xC0\xC1\x07,\x02\x02\xC1" + - "\xC5\x03\x02\x02\x02\xC2\xC4\v\x02\x02\x02\xC3\xC2\x03\x02\x02\x02\xC4" + - "\xC7\x03\x02\x02\x02\xC5\xC6\x03\x02\x02\x02\xC5\xC3\x03\x02\x02\x02\xC6" + - "\xC8\x03\x02\x02\x02\xC7\xC5\x03\x02\x02\x02\xC8\xC9\x07,\x02\x02\xC9" + - "\xCB\x071\x02\x02\xCA\xB5\x03\x02\x02\x02\xCA\xBF\x03\x02\x02\x02\xCB" + - "\xCC\x03\x02\x02\x02\xCC\xCD\b\x03\x02\x02\xCD\x07\x03\x02\x02\x02\xCE" + - "\xCF\x07}\x02\x02\xCF\t\x03\x02\x02\x02\xD0\xD1\x07\x7F\x02\x02\xD1\v" + - "\x03\x02\x02\x02\xD2\xD3\x07]\x02\x02\xD3\r\x03\x02\x02\x02\xD4\xD5\x07" + - "_\x02\x02\xD5\x0F\x03\x02\x02\x02\xD6\xD7\x07*\x02\x02\xD7\x11\x03\x02" + - "\x02\x02\xD8\xD9\x07+\x02\x02\xD9\x13\x03\x02\x02\x02\xDA\xDB\x070\x02" + - "\x02\xDB\xDC\x03\x02\x02\x02\xDC\xDD\b\n\x03\x02\xDD\x15\x03\x02\x02\x02" + - "\xDE\xDF\x07A\x02\x02\xDF\xE0\x070\x02\x02\xE0\xE1\x03\x02\x02\x02\xE1" + - "\xE2\b\v\x03\x02\xE2\x17\x03\x02\x02\x02\xE3\xE4\x07.\x02\x02\xE4\x19" + - "\x03\x02\x02\x02\xE5\xE6\x07=\x02\x02\xE6\x1B\x03\x02\x02\x02\xE7\xE8" + - "\x07k\x02\x02\xE8\xE9\x07h\x02\x02\xE9\x1D\x03\x02\x02\x02\xEA\xEB\x07" + - "k\x02\x02\xEB\xEC\x07p\x02\x02\xEC\x1F\x03\x02\x02\x02\xED\xEE\x07g\x02" + - "\x02\xEE\xEF\x07n\x02\x02\xEF\xF0\x07u\x02\x02\xF0\xF1\x07g\x02\x02\xF1" + - "!\x03\x02\x02\x02\xF2\xF3\x07y\x02\x02\xF3\xF4\x07j\x02\x02\xF4\xF5\x07" + - "k\x02\x02\xF5\xF6\x07n\x02\x02\xF6\xF7\x07g\x02\x02\xF7#\x03\x02\x02\x02" + - "\xF8\xF9\x07f\x02\x02\xF9\xFA\x07q\x02\x02\xFA%\x03\x02\x02\x02\xFB\xFC" + - "\x07h\x02\x02\xFC\xFD\x07q\x02\x02\xFD\xFE\x07t\x02\x02\xFE\'\x03\x02" + - "\x02\x02\xFF\u0100\x07e\x02\x02\u0100\u0101\x07q\x02\x02\u0101\u0102\x07" + - "p\x02\x02\u0102\u0103\x07v\x02\x02\u0103\u0104\x07k\x02\x02\u0104\u0105" + - "\x07p\x02\x02\u0105\u0106\x07w\x02\x02\u0106\u0107\x07g\x02\x02\u0107" + - ")\x03\x02\x02\x02\u0108\u0109\x07d\x02\x02\u0109\u010A\x07t\x02\x02\u010A" + - "\u010B\x07g\x02\x02\u010B\u010C\x07c\x02\x02\u010C\u010D\x07m\x02\x02" + - "\u010D+\x03\x02\x02\x02\u010E\u010F\x07t\x02\x02\u010F\u0110\x07g\x02" + - "\x02\u0110\u0111\x07v\x02\x02\u0111\u0112\x07w\x02\x02\u0112\u0113\x07" + - "t\x02\x02\u0113\u0114\x07p\x02\x02\u0114-\x03\x02\x02\x02\u0115\u0116" + - "\x07p\x02\x02\u0116\u0117\x07g\x02\x02\u0117\u0118\x07y\x02\x02\u0118" + - "/\x03\x02\x02\x02\u0119\u011A\x07v\x02\x02\u011A\u011B\x07t\x02\x02\u011B" + - "\u011C\x07{\x02\x02\u011C1\x03\x02\x02\x02\u011D\u011E\x07e\x02\x02\u011E" + - "\u011F\x07c\x02\x02\u011F\u0120\x07v\x02\x02\u0120\u0121\x07e\x02\x02" + - "\u0121\u0122\x07j\x02\x02\u01223\x03\x02\x02\x02\u0123\u0124\x07v\x02" + - "\x02\u0124\u0125\x07j\x02\x02\u0125\u0126\x07t\x02\x02\u0126\u0127\x07" + - "q\x02\x02\u0127\u0128\x07y\x02\x02\u01285\x03\x02\x02\x02\u0129\u012A" + - "\x07v\x02\x02\u012A\u012B\x07j\x02\x02\u012B\u012C\x07k\x02\x02\u012C" + - "\u012D\x07u\x02\x02\u012D7\x03\x02\x02\x02\u012E\u012F\x07k\x02\x02\u012F" + - "\u0130\x07p\x02\x02\u0130\u0131\x07u\x02\x02\u0131\u0132\x07v\x02\x02" + - "\u0132\u0133\x07c\x02\x02\u0133\u0134\x07p\x02\x02\u0134\u0135\x07e\x02" + - "\x02\u0135\u0136\x07g\x02\x02\u0136\u0137\x07q\x02\x02\u0137\u0138\x07" + - "h\x02\x02\u01389\x03\x02\x02\x02\u0139\u013A\x07#\x02\x02\u013A;\x03\x02" + - "\x02\x02\u013B\u013C\x07\x80\x02\x02\u013C=\x03\x02\x02\x02\u013D\u013E" + - "\x07,\x02\x02\u013E?\x03\x02\x02\x02\u013F\u0140\x071\x02\x02\u0140\u0141" + - "\x06 \x02\x02\u0141A\x03\x02\x02\x02\u0142\u0143\x07\'\x02\x02\u0143C" + - "\x03\x02\x02\x02\u0144\u0145\x07-\x02\x02\u0145E\x03\x02\x02\x02\u0146" + - "\u0147\x07/\x02\x02\u0147G\x03\x02\x02\x02\u0148\u0149\x07>\x02\x02\u0149" + - "\u014A\x07>\x02\x02\u014AI\x03\x02\x02\x02\u014B\u014C\x07@\x02\x02\u014C" + - "\u014D\x07@\x02\x02\u014DK\x03\x02\x02\x02\u014E\u014F\x07@\x02\x02\u014F" + - "\u0150\x07@\x02\x02\u0150\u0151\x07@\x02\x02\u0151M\x03\x02\x02\x02\u0152" + - "\u0153\x07>\x02\x02\u0153O\x03\x02\x02\x02\u0154\u0155\x07>\x02\x02\u0155" + - "\u0156\x07?\x02\x02\u0156Q\x03\x02\x02\x02\u0157\u0158\x07@\x02\x02\u0158" + - "S\x03\x02\x02\x02\u0159\u015A\x07@\x02\x02\u015A\u015B\x07?\x02\x02\u015B" + - "U\x03\x02\x02\x02\u015C\u015D\x07?\x02\x02\u015D\u015E\x07?\x02\x02\u015E" + - "W\x03\x02\x02\x02\u015F\u0160\x07?\x02\x02\u0160\u0161\x07?\x02\x02\u0161" + - "\u0162\x07?\x02\x02\u0162Y\x03\x02\x02\x02\u0163\u0164\x07#\x02\x02\u0164" + - "\u0165\x07?\x02\x02\u0165[\x03\x02\x02\x02\u0166\u0167\x07#\x02\x02\u0167" + - "\u0168\x07?\x02\x02\u0168\u0169\x07?\x02\x02\u0169]\x03\x02\x02\x02\u016A" + - "\u016B\x07(\x02\x02\u016B_\x03\x02\x02\x02\u016C\u016D\x07`\x02\x02\u016D" + - "a\x03\x02\x02\x02\u016E\u016F\x07~\x02\x02\u016Fc\x03\x02\x02\x02\u0170" + - "\u0171\x07(\x02\x02\u0171\u0172\x07(\x02\x02\u0172e\x03\x02\x02\x02\u0173" + - "\u0174\x07~\x02\x02\u0174\u0175\x07~\x02\x02\u0175g\x03\x02\x02\x02\u0176" + - "\u0177\x07A\x02\x02\u0177i\x03\x02\x02\x02\u0178\u0179\x07<\x02\x02\u0179" + - "k\x03\x02\x02\x02\u017A\u017B\x07A\x02\x02\u017B\u017C\x07<\x02\x02\u017C" + - "m\x03\x02\x02\x02\u017D\u017E\x07<\x02\x02\u017E\u017F\x07<\x02\x02\u017F" + - "o\x03\x02\x02\x02\u0180\u0181\x07/\x02\x02\u0181\u0182\x07@\x02\x02\u0182" + - "q\x03\x02\x02\x02\u0183\u0184\x07?\x02\x02\u0184\u0185\x07\x80\x02\x02" + - "\u0185s\x03\x02\x02\x02\u0186\u0187\x07?\x02\x02\u0187\u0188\x07?\x02" + - "\x02\u0188\u0189\x07\x80\x02\x02\u0189u\x03\x02\x02\x02\u018A\u018B\x07" + - "-\x02\x02\u018B\u018C\x07-\x02\x02\u018Cw\x03\x02\x02\x02\u018D\u018E" + - "\x07/\x02\x02\u018E\u018F\x07/\x02\x02\u018Fy\x03\x02\x02\x02\u0190\u0191" + - "\x07?\x02\x02\u0191{\x03\x02\x02\x02\u0192\u0193\x07-\x02\x02\u0193\u0194" + - "\x07?\x02\x02\u0194}\x03\x02\x02\x02\u0195\u0196\x07/\x02\x02\u0196\u0197" + - "\x07?\x02\x02\u0197\x7F\x03\x02\x02\x02\u0198\u0199\x07,\x02\x02\u0199" + - "\u019A\x07?\x02\x02\u019A\x81\x03\x02\x02\x02\u019B\u019C\x071\x02\x02" + - "\u019C\u019D\x07?\x02\x02\u019D\x83\x03\x02\x02\x02\u019E\u019F\x07\'" + - "\x02\x02\u019F\u01A0\x07?\x02\x02\u01A0\x85\x03\x02\x02\x02\u01A1\u01A2" + - "\x07(\x02\x02\u01A2\u01A3\x07?\x02\x02\u01A3\x87\x03\x02\x02\x02\u01A4" + - "\u01A5\x07`\x02\x02\u01A5\u01A6\x07?\x02\x02\u01A6\x89\x03\x02\x02\x02" + - "\u01A7\u01A8\x07~\x02\x02\u01A8\u01A9\x07?\x02\x02\u01A9\x8B\x03\x02\x02" + - "\x02\u01AA\u01AB\x07>\x02\x02\u01AB\u01AC\x07>\x02\x02\u01AC\u01AD\x07" + - "?\x02\x02\u01AD\x8D\x03\x02\x02\x02\u01AE\u01AF\x07@\x02\x02\u01AF\u01B0" + - "\x07@\x02\x02\u01B0\u01B1\x07?\x02\x02\u01B1\x8F\x03\x02\x02\x02\u01B2" + - "\u01B3\x07@\x02\x02\u01B3\u01B4\x07@\x02\x02\u01B4\u01B5\x07@\x02\x02" + - "\u01B5\u01B6\x07?\x02\x02\u01B6\x91\x03\x02\x02\x02\u01B7\u01B9\x072\x02" + - "\x02\u01B8\u01BA\t\x04\x02\x02\u01B9\u01B8\x03\x02\x02\x02\u01BA\u01BB" + - "\x03\x02\x02\x02\u01BB\u01B9\x03\x02\x02\x02\u01BB\u01BC\x03\x02\x02\x02" + - "\u01BC\u01BE\x03\x02\x02\x02\u01BD\u01BF\t\x05\x02\x02\u01BE\u01BD\x03" + - "\x02\x02\x02\u01BE\u01BF\x03\x02\x02\x02\u01BF\x93\x03\x02\x02\x02\u01C0" + - "\u01C1\x072\x02\x02\u01C1\u01C3\t\x06\x02\x02\u01C2\u01C4\t\x07\x02\x02" + - "\u01C3\u01C2\x03\x02\x02\x02\u01C4\u01C5\x03\x02\x02\x02\u01C5\u01C3\x03" + - "\x02\x02\x02\u01C5\u01C6\x03\x02\x02\x02\u01C6\u01C8\x03\x02\x02\x02\u01C7" + - "\u01C9\t\x05\x02\x02\u01C8\u01C7\x03\x02\x02\x02\u01C8\u01C9\x03\x02\x02" + - "\x02\u01C9\x95\x03\x02\x02\x02\u01CA\u01D3\x072\x02\x02\u01CB\u01CF\t" + - "\b\x02\x02\u01CC\u01CE\t\t\x02\x02\u01CD\u01CC\x03\x02\x02\x02\u01CE\u01D1" + - "\x03\x02\x02\x02\u01CF\u01CD\x03\x02\x02\x02\u01CF\u01D0\x03\x02\x02\x02" + - "\u01D0\u01D3\x03\x02\x02\x02\u01D1\u01CF\x03\x02\x02\x02\u01D2\u01CA\x03" + - "\x02\x02\x02\u01D2\u01CB\x03\x02\x02\x02\u01D3\u01D5\x03\x02\x02\x02\u01D4" + - "\u01D6\t\n\x02\x02\u01D5\u01D4\x03\x02\x02\x02\u01D5\u01D6\x03\x02\x02" + - "\x02\u01D6\x97\x03\x02\x02\x02\u01D7\u01E0\x072\x02\x02\u01D8\u01DC\t" + - "\b\x02\x02\u01D9\u01DB\t\t\x02\x02\u01DA\u01D9\x03\x02\x02\x02\u01DB\u01DE" + - "\x03\x02\x02\x02\u01DC\u01DA\x03\x02\x02\x02\u01DC\u01DD\x03\x02\x02\x02" + - "\u01DD\u01E0\x03\x02\x02\x02\u01DE\u01DC\x03\x02\x02\x02\u01DF\u01D7\x03" + - "\x02\x02\x02\u01DF\u01D8\x03\x02\x02\x02\u01E0\u01E7\x03\x02\x02\x02\u01E1" + - "\u01E3\x05\x14\n\x02\u01E2\u01E4\t\t\x02\x02\u01E3\u01E2\x03\x02\x02\x02" + - "\u01E4\u01E5\x03\x02\x02\x02\u01E5\u01E3\x03\x02\x02\x02\u01E5\u01E6\x03" + - "\x02\x02\x02\u01E6\u01E8\x03\x02\x02\x02\u01E7\u01E1\x03\x02\x02\x02\u01E7" + - "\u01E8\x03\x02\x02\x02\u01E8\u01F2\x03\x02\x02\x02\u01E9\u01EB\t\v\x02" + - "\x02\u01EA\u01EC\t\f\x02\x02\u01EB\u01EA\x03\x02\x02\x02\u01EB\u01EC\x03" + - "\x02\x02\x02\u01EC\u01EE\x03\x02\x02\x02\u01ED\u01EF\t\t\x02\x02\u01EE" + - "\u01ED\x03\x02\x02\x02\u01EF\u01F0\x03\x02\x02\x02\u01F0\u01EE\x03\x02" + - "\x02\x02\u01F0\u01F1\x03\x02\x02\x02\u01F1\u01F3\x03\x02\x02\x02\u01F2" + - "\u01E9\x03\x02\x02\x02\u01F2\u01F3\x03\x02\x02\x02\u01F3\u01F5\x03\x02" + - "\x02\x02\u01F4\u01F6\t\r\x02\x02\u01F5\u01F4\x03\x02\x02\x02\u01F5\u01F6" + - "\x03\x02\x02\x02\u01F6\x99\x03\x02\x02\x02\u01F7\u01FF\x07$\x02\x02\u01F8" + - "\u01F9\x07^\x02\x02\u01F9\u01FE\x07$\x02\x02\u01FA\u01FB\x07^\x02\x02" + - "\u01FB\u01FE\x07^\x02\x02\u01FC\u01FE\n\x0E\x02\x02\u01FD\u01F8\x03\x02" + - "\x02\x02\u01FD\u01FA\x03\x02\x02\x02\u01FD\u01FC\x03\x02\x02\x02\u01FE" + - "\u0201\x03\x02\x02\x02\u01FF\u0200\x03\x02\x02\x02\u01FF\u01FD\x03\x02" + - "\x02\x02\u0200\u0202\x03\x02\x02\x02\u0201\u01FF\x03\x02\x02\x02\u0202" + - "\u0210\x07$\x02\x02\u0203\u020B\x07)\x02\x02\u0204\u0205\x07^\x02\x02" + - "\u0205\u020A\x07)\x02\x02\u0206\u0207\x07^\x02\x02\u0207\u020A\x07^\x02" + - "\x02\u0208\u020A\n\x0F\x02\x02\u0209\u0204\x03\x02\x02\x02\u0209\u0206" + - "\x03\x02\x02\x02\u0209\u0208\x03\x02\x02\x02\u020A\u020D\x03\x02\x02\x02" + - "\u020B\u020C\x03\x02\x02\x02\u020B\u0209\x03\x02\x02\x02\u020C"; + "N\tN\x04O\tO\x04P\tP\x04Q\tQ\x04R\tR\x04S\tS\x04T\tT\x04U\tU\x04V\tV\x04" + + "W\tW\x03\x02\x06\x02\xB2\n\x02\r\x02\x0E\x02\xB3\x03\x02\x03\x02\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x07\x03\xBC\n\x03\f\x03\x0E\x03\xBF\v\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x07\x03\xC6\n\x03\f\x03\x0E\x03\xC9" + + "\v\x03\x03\x03\x03\x03\x05\x03\xCD\n\x03\x03\x03\x03\x03\x03\x04\x03\x04" + + "\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03\b\x03\b\x03\t\x03" + + "\t\x03\n\x03\n\x03\v\x03\v\x03\v\x03\v\x03\f\x03\f\x03\f\x03\f\x03\f\x03" + + "\r\x03\r\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x10\x03\x10\x03\x10" + + "\x03\x11\x03\x11\x03\x11\x03\x11\x03\x11\x03\x12\x03\x12\x03\x12\x03\x12" + + "\x03\x12\x03\x12\x03\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03\x14\x03\x14" + + "\x03\x15\x03\x15\x03\x15\x03\x15\x03\x15\x03\x15\x03\x15\x03\x15\x03\x15" + + "\x03\x16\x03\x16\x03\x16\x03\x16\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17" + + "\x03\x17\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18\x03\x18\x03\x18\x03\x19" + + "\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A\x03\x1A" + + "\x03\x1B\x03\x1B\x03\x1B\x03\x1B\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1C" + + "\x03\x1C\x03\x1C\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1D" + + "\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03 " + + "\x03 \x03!\x03!\x03!\x03\"\x03\"\x03#\x03#\x03$\x03$\x03%\x03%\x03%\x03" + + "&\x03&\x03&\x03\'\x03\'\x03\'\x03\'\x03(\x03(\x03)\x03)\x03)\x03*\x03" + + "*\x03+\x03+\x03+\x03,\x03,\x03,\x03-\x03-\x03-\x03-\x03.\x03.\x03.\x03" + + "/\x03/\x03/\x03/\x030\x030\x031\x031\x032\x032\x033\x033\x033\x034\x03" + + "4\x034\x035\x035\x036\x036\x037\x037\x037\x038\x038\x038\x039\x039\x03" + + "9\x03:\x03:\x03:\x03;\x03;\x03;\x03;\x03<\x03<\x03<\x03=\x03=\x03=\x03" + + ">\x03>\x03?\x03?\x03?\x03@\x03@\x03@\x03A\x03A\x03A\x03B\x03B\x03B\x03" + + "C\x03C\x03C\x03D\x03D\x03D\x03E\x03E\x03E\x03F\x03F\x03F\x03G\x03G\x03" + + "G\x03G\x03H\x03H\x03H\x03H\x03I\x03I\x03I\x03I\x03I\x03J\x03J\x06J\u01BE" + + "\nJ\rJ\x0EJ\u01BF\x03J\x05J\u01C3\nJ\x03K\x03K\x03K\x06K\u01C8\nK\rK\x0E" + + "K\u01C9\x03K\x05K\u01CD\nK\x03L\x03L\x03L\x07L\u01D2\nL\fL\x0EL\u01D5" + + "\vL\x05L\u01D7\nL\x03L\x05L\u01DA\nL\x03M\x03M\x03M\x07M\u01DF\nM\fM\x0E" + + "M\u01E2\vM\x05M\u01E4\nM\x03M\x03M\x06M\u01E8\nM\rM\x0EM\u01E9\x05M\u01EC" + + "\nM\x03M\x03M\x05M\u01F0\nM\x03M\x06M\u01F3\nM\rM\x0EM\u01F4\x05M\u01F7" + + "\nM\x03M\x05M\u01FA\nM\x03N\x03N\x03N\x03N\x03N\x03N\x07N\u0202\nN\fN" + + "\x0EN\u0205\vN\x03N\x03N\x03N\x03N\x03N\x03N\x03N\x07N\u020E\nN\fN\x0E" + + "N\u0211\vN\x03N\x05N\u0214\nN\x03O\x03O\x03O\x03O\x06O\u021A\nO\rO\x0E" + + "O\u021B\x03O\x03O\x07O\u0220\nO\fO\x0EO\u0223\vO\x03O\x03O\x03P\x03P\x03" + + "P\x03P\x03P\x03Q\x03Q\x03Q\x03Q\x03Q\x03Q\x03R\x03R\x03R\x03R\x03R\x03" + + "S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03" + + "S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03" + + "S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x03S\x05S\u025D\nS\x03T\x03" + + "T\x03T\x03T\x03U\x03U\x07U\u0265\nU\fU\x0EU\u0268\vU\x03V\x03V\x03V\x07" + + "V\u026D\nV\fV\x0EV\u0270\vV\x05V\u0272\nV\x03V\x03V\x03W\x03W\x07W\u0278" + + "\nW\fW\x0EW\u027B\vW\x03W\x03W\x07\xBD\xC7\u0203\u020F\u021B\x02\x02X" + + "\x04\x02\x03\x06\x02\x04\b\x02\x05\n\x02\x06\f\x02\x07\x0E\x02\b\x10\x02" + + "\t\x12\x02\n\x14\x02\v\x16\x02\f\x18\x02\r\x1A\x02\x0E\x1C\x02\x0F\x1E" + + "\x02\x10 \x02\x11\"\x02\x12$\x02\x13&\x02\x14(\x02\x15*\x02\x16,\x02\x17" + + ".\x02\x180\x02\x192\x02\x1A4\x02\x1B6\x02\x1C8\x02\x1D:\x02\x1E<\x02\x1F" + + ">\x02 @\x02!B\x02\"D\x02#F\x02$H\x02%J\x02&L\x02\'N\x02(P\x02)R\x02*T" + + "\x02+V\x02,X\x02-Z\x02.\\\x02/^\x020`\x021b\x022d\x023f\x024h\x025j\x02" + + "6l\x027n\x028p\x029r\x02:t\x02;v\x02|\x02?~\x02@\x80\x02" + + "A\x82\x02B\x84\x02C\x86\x02D\x88\x02E\x8A\x02F\x8C\x02G\x8E\x02H\x90\x02" + + "I\x92\x02J\x94\x02K\x96\x02L\x98\x02M\x9A\x02N\x9C\x02O\x9E\x02P\xA0\x02" + + "Q\xA2\x02R\xA4\x02S\xA6\x02T\xA8\x02U\xAA\x02V\xAC\x02W\xAE\x02X\x04\x02" + + "\x03\x15\x05\x02\v\f\x0F\x0F\"\"\x04\x02\f\f\x0F\x0F\x03\x0229\x04\x02" + + "NNnn\x04\x02ZZzz\x05\x022;CHch\x03\x023;\x03\x022;\b\x02FFHHNNffhhnn\x04" + + "\x02GGgg\x04\x02--//\x06\x02FFHHffhh\x04\x02$$^^\x04\x02))^^\x03\x02\f" + + "\f\x04\x02\f\f11\t\x02WWeekknouuwwzz\x05\x02C\\aac|\x06\x022;C\\aac|\x02" + + "\u02A4\x02\x04\x03\x02\x02\x02\x02\x06\x03\x02\x02\x02\x02\b\x03\x02\x02" + + "\x02\x02\n\x03\x02\x02\x02\x02\f\x03\x02\x02\x02\x02\x0E\x03\x02\x02\x02" + + "\x02\x10\x03\x02\x02\x02\x02\x12\x03\x02\x02\x02\x02\x14\x03\x02\x02\x02" + + "\x02\x16\x03\x02\x02\x02\x02\x18\x03\x02\x02\x02\x02\x1A\x03\x02\x02\x02" + + "\x02\x1C\x03\x02\x02\x02\x02\x1E\x03\x02\x02\x02\x02 \x03\x02\x02\x02" + + "\x02\"\x03\x02\x02\x02\x02$\x03\x02\x02\x02\x02&\x03\x02\x02\x02\x02(" + + "\x03\x02\x02\x02\x02*\x03\x02\x02\x02\x02,\x03\x02\x02\x02\x02.\x03\x02" + + "\x02\x02\x020\x03\x02\x02\x02\x022\x03\x02\x02\x02\x024\x03\x02\x02\x02" + + "\x026\x03\x02\x02\x02\x028\x03\x02\x02\x02\x02:\x03\x02\x02\x02\x02<\x03" + + "\x02\x02\x02\x02>\x03\x02\x02\x02\x02@\x03\x02\x02\x02\x02B\x03\x02\x02" + + "\x02\x02D\x03\x02\x02\x02\x02F\x03\x02\x02\x02\x02H\x03\x02\x02\x02\x02" + + "J\x03\x02\x02\x02\x02L\x03\x02\x02\x02\x02N\x03\x02\x02\x02\x02P\x03\x02" + + "\x02\x02\x02R\x03\x02\x02\x02\x02T\x03\x02\x02\x02\x02V\x03\x02\x02\x02" + + "\x02X\x03\x02\x02\x02\x02Z\x03\x02\x02\x02\x02\\\x03\x02\x02\x02\x02^" + + "\x03\x02\x02\x02\x02`\x03\x02\x02\x02\x02b\x03\x02\x02\x02\x02d\x03\x02" + + "\x02\x02\x02f\x03\x02\x02\x02\x02h\x03\x02\x02\x02\x02j\x03\x02\x02\x02" + + "\x02l\x03\x02\x02\x02\x02n\x03\x02\x02\x02\x02p\x03\x02\x02\x02\x02r\x03" + + "\x02\x02\x02\x02t\x03\x02\x02\x02\x02v\x03\x02\x02\x02\x02x\x03\x02\x02" + + "\x02\x02z\x03\x02\x02\x02\x02|\x03\x02\x02\x02\x02~\x03\x02\x02\x02\x02" + + "\x80\x03\x02\x02\x02\x02\x82\x03\x02\x02\x02\x02\x84\x03\x02\x02\x02\x02" + + "\x86\x03\x02\x02\x02\x02\x88\x03\x02\x02\x02\x02\x8A\x03\x02\x02\x02\x02" + + "\x8C\x03\x02\x02\x02\x02\x8E\x03\x02\x02\x02\x02\x90\x03\x02\x02\x02\x02" + + "\x92\x03\x02\x02\x02\x02\x94\x03\x02\x02\x02\x02\x96\x03\x02\x02\x02\x02" + + "\x98\x03\x02\x02\x02\x02\x9A\x03\x02\x02\x02\x02\x9C\x03\x02\x02\x02\x02" + + "\x9E\x03\x02\x02\x02\x02\xA0\x03\x02\x02\x02\x02\xA2\x03\x02\x02\x02\x02" + + "\xA4\x03\x02\x02\x02\x02\xA6\x03\x02\x02\x02\x02\xA8\x03\x02\x02\x02\x02" + + "\xAA\x03\x02\x02\x02\x03\xAC\x03\x02\x02\x02\x03\xAE\x03\x02\x02\x02\x04" + + "\xB1\x03\x02\x02\x02\x06\xCC\x03\x02\x02\x02\b\xD0\x03\x02\x02\x02\n\xD2" + + "\x03\x02\x02\x02\f\xD4\x03\x02\x02\x02\x0E\xD6\x03\x02\x02\x02\x10\xD8" + + "\x03\x02\x02\x02\x12\xDA\x03\x02\x02\x02\x14\xDC\x03\x02\x02\x02\x16\xDE" + + "\x03\x02\x02\x02\x18\xE2\x03\x02\x02\x02\x1A\xE7\x03\x02\x02\x02\x1C\xE9" + + "\x03\x02\x02\x02\x1E\xEB\x03\x02\x02\x02 \xEE\x03\x02\x02\x02\"\xF1\x03" + + "\x02\x02\x02$\xF6\x03\x02\x02\x02&\xFC\x03\x02\x02\x02(\xFF\x03\x02\x02" + + "\x02*\u0103\x03\x02\x02\x02,\u010C\x03\x02\x02\x02.\u0112\x03\x02\x02" + + "\x020\u0119\x03\x02\x02\x022\u011D\x03\x02\x02\x024\u0121\x03\x02\x02" + + "\x026\u0127\x03\x02\x02\x028\u012D\x03\x02\x02\x02:\u0132\x03\x02\x02" + + "\x02<\u013D\x03\x02\x02\x02>\u013F\x03\x02\x02\x02@\u0141\x03\x02\x02" + + "\x02B\u0143\x03\x02\x02\x02D\u0146\x03\x02\x02\x02F\u0148\x03\x02\x02" + + "\x02H\u014A\x03\x02\x02\x02J\u014C\x03\x02\x02\x02L\u014F\x03\x02\x02" + + "\x02N\u0152\x03\x02\x02\x02P\u0156\x03\x02\x02\x02R\u0158\x03\x02\x02" + + "\x02T\u015B\x03\x02\x02\x02V\u015D\x03\x02\x02\x02X\u0160\x03\x02\x02" + + "\x02Z\u0163\x03\x02\x02\x02\\\u0167\x03\x02\x02\x02^\u016A\x03\x02\x02" + + "\x02`\u016E\x03\x02\x02\x02b\u0170\x03\x02\x02\x02d\u0172\x03\x02\x02" + + "\x02f\u0174\x03\x02\x02\x02h\u0177\x03\x02\x02\x02j\u017A\x03\x02\x02" + + "\x02l\u017C\x03\x02\x02\x02n\u017E\x03\x02\x02\x02p\u0181\x03\x02\x02" + + "\x02r\u0184\x03\x02\x02\x02t\u0187\x03\x02\x02\x02v\u018A\x03\x02\x02" + + "\x02x\u018E\x03\x02\x02\x02z\u0191\x03\x02\x02\x02|\u0194\x03\x02\x02" + + "\x02~\u0196\x03\x02\x02\x02\x80\u0199\x03\x02\x02\x02\x82\u019C\x03\x02" + + "\x02\x02\x84\u019F\x03\x02\x02\x02\x86\u01A2\x03\x02\x02\x02\x88\u01A5" + + "\x03\x02\x02\x02\x8A\u01A8\x03\x02\x02\x02\x8C\u01AB\x03\x02\x02\x02\x8E" + + "\u01AE\x03\x02\x02\x02\x90\u01B2\x03\x02\x02\x02\x92\u01B6\x03\x02\x02" + + "\x02\x94\u01BB\x03\x02\x02\x02\x96\u01C4\x03\x02\x02\x02\x98\u01D6\x03" + + "\x02\x02\x02\x9A\u01E3\x03\x02\x02\x02\x9C\u0213\x03\x02\x02\x02\x9E\u0215" + + "\x03\x02\x02\x02\xA0\u0226\x03\x02\x02\x02\xA2\u022B\x03\x02\x02\x02\xA4" + + "\u0231\x03\x02\x02\x02\xA6\u025C\x03\x02\x02\x02\xA8\u025E\x03\x02\x02" + + "\x02\xAA\u0262\x03\x02\x02\x02\xAC\u0271\x03\x02\x02\x02\xAE\u0275\x03" + + "\x02\x02\x02\xB0\xB2\t\x02\x02\x02\xB1\xB0\x03\x02\x02\x02\xB2\xB3\x03" + + "\x02\x02\x02\xB3\xB1\x03\x02\x02\x02\xB3\xB4\x03\x02\x02\x02\xB4\xB5\x03" + + "\x02\x02\x02\xB5\xB6\b\x02\x02\x02\xB6\x05\x03\x02\x02\x02\xB7\xB8\x07" + + "1\x02\x02\xB8\xB9\x071\x02\x02\xB9\xBD\x03\x02\x02\x02\xBA\xBC\v\x02\x02" + + "\x02\xBB\xBA\x03\x02\x02\x02\xBC\xBF\x03\x02\x02\x02\xBD\xBE\x03\x02\x02" + + "\x02\xBD\xBB\x03\x02\x02\x02\xBE\xC0\x03\x02\x02\x02\xBF\xBD\x03\x02\x02" + + "\x02\xC0\xCD\t\x03\x02\x02\xC1\xC2\x071\x02\x02\xC2\xC3\x07,\x02\x02\xC3" + + "\xC7\x03\x02\x02\x02\xC4\xC6\v\x02\x02\x02\xC5\xC4\x03\x02\x02\x02\xC6" + + "\xC9\x03\x02\x02\x02\xC7\xC8\x03\x02\x02\x02\xC7\xC5\x03\x02\x02\x02\xC8" + + "\xCA\x03\x02\x02\x02\xC9\xC7\x03\x02\x02\x02\xCA\xCB\x07,\x02\x02\xCB" + + "\xCD\x071\x02\x02\xCC\xB7\x03\x02\x02\x02\xCC\xC1\x03\x02\x02\x02\xCD" + + "\xCE\x03\x02\x02\x02\xCE\xCF\b\x03\x02\x02\xCF\x07\x03\x02\x02\x02\xD0" + + "\xD1\x07}\x02\x02\xD1\t\x03\x02\x02\x02\xD2\xD3\x07\x7F\x02\x02\xD3\v" + + "\x03\x02\x02\x02\xD4\xD5\x07]\x02\x02\xD5\r\x03\x02\x02\x02\xD6\xD7\x07" + + "_\x02\x02\xD7\x0F\x03\x02\x02\x02\xD8\xD9\x07*\x02\x02\xD9\x11\x03\x02" + + "\x02\x02\xDA\xDB\x07+\x02\x02\xDB\x13\x03\x02\x02\x02\xDC\xDD\x07&\x02" + + "\x02\xDD\x15\x03\x02\x02\x02\xDE\xDF\x070\x02\x02\xDF\xE0\x03\x02\x02" + + "\x02\xE0\xE1\b\v\x03\x02\xE1\x17\x03\x02\x02\x02\xE2\xE3\x07A\x02\x02" + + "\xE3\xE4\x070\x02\x02\xE4\xE5\x03\x02\x02\x02\xE5\xE6\b\f\x03\x02\xE6" + + "\x19\x03\x02\x02\x02\xE7\xE8\x07.\x02\x02\xE8\x1B\x03\x02\x02\x02\xE9" + + "\xEA\x07=\x02\x02\xEA\x1D\x03\x02\x02\x02\xEB\xEC\x07k\x02\x02\xEC\xED" + + "\x07h\x02\x02\xED\x1F\x03\x02\x02\x02\xEE\xEF\x07k\x02\x02\xEF\xF0\x07" + + "p\x02\x02\xF0!\x03\x02\x02\x02\xF1\xF2\x07g\x02\x02\xF2\xF3\x07n\x02\x02" + + "\xF3\xF4\x07u\x02\x02\xF4\xF5\x07g\x02\x02\xF5#\x03\x02\x02\x02\xF6\xF7" + + "\x07y\x02\x02\xF7\xF8\x07j\x02\x02\xF8\xF9\x07k\x02\x02\xF9\xFA\x07n\x02" + + "\x02\xFA\xFB\x07g\x02\x02\xFB%\x03\x02\x02\x02\xFC\xFD\x07f\x02\x02\xFD" + + "\xFE\x07q\x02\x02\xFE\'\x03\x02\x02\x02\xFF\u0100\x07h\x02\x02\u0100\u0101" + + "\x07q\x02\x02\u0101\u0102\x07t\x02\x02\u0102)\x03\x02\x02\x02\u0103\u0104" + + "\x07e\x02\x02\u0104\u0105\x07q\x02\x02\u0105\u0106\x07p\x02\x02\u0106" + + "\u0107\x07v\x02\x02\u0107\u0108\x07k\x02\x02\u0108\u0109\x07p\x02\x02" + + "\u0109\u010A\x07w\x02\x02\u010A\u010B\x07g\x02\x02\u010B+\x03\x02\x02" + + "\x02\u010C\u010D\x07d\x02\x02\u010D\u010E\x07t\x02\x02\u010E\u010F\x07" + + "g\x02\x02\u010F\u0110\x07c\x02\x02\u0110\u0111\x07m\x02\x02\u0111-\x03" + + "\x02\x02\x02\u0112\u0113\x07t\x02\x02\u0113\u0114\x07g\x02\x02\u0114\u0115" + + "\x07v\x02\x02\u0115\u0116\x07w\x02\x02\u0116\u0117\x07t\x02\x02\u0117" + + "\u0118\x07p\x02\x02\u0118/\x03\x02\x02\x02\u0119\u011A\x07p\x02\x02\u011A" + + "\u011B\x07g\x02\x02\u011B\u011C\x07y\x02\x02\u011C1\x03\x02\x02\x02\u011D" + + "\u011E\x07v\x02\x02\u011E\u011F\x07t\x02\x02\u011F\u0120\x07{\x02\x02" + + "\u01203\x03\x02\x02\x02\u0121\u0122\x07e\x02\x02\u0122\u0123\x07c\x02" + + "\x02\u0123\u0124\x07v\x02\x02\u0124\u0125\x07e\x02\x02\u0125\u0126\x07" + + "j\x02\x02\u01265\x03\x02\x02\x02\u0127\u0128\x07v\x02\x02\u0128\u0129" + + "\x07j\x02\x02\u0129\u012A\x07t\x02\x02\u012A\u012B\x07q\x02\x02\u012B" + + "\u012C\x07y\x02\x02\u012C7\x03\x02\x02\x02\u012D\u012E\x07v\x02\x02\u012E" + + "\u012F\x07j\x02\x02\u012F\u0130\x07k\x02\x02\u0130\u0131\x07u\x02\x02" + + "\u01319\x03\x02\x02\x02\u0132\u0133\x07k\x02\x02\u0133\u0134\x07p\x02" + + "\x02\u0134\u0135\x07u\x02\x02\u0135\u0136\x07v\x02\x02\u0136\u0137\x07" + + "c\x02\x02\u0137\u0138\x07p\x02\x02\u0138\u0139\x07e\x02\x02\u0139\u013A" + + "\x07g\x02\x02\u013A\u013B\x07q\x02\x02\u013B\u013C\x07h\x02\x02\u013C" + + ";\x03\x02\x02\x02\u013D\u013E\x07#\x02\x02\u013E=\x03\x02\x02\x02\u013F" + + "\u0140\x07\x80\x02\x02\u0140?\x03\x02\x02\x02\u0141\u0142\x07,\x02\x02" + + "\u0142A\x03\x02\x02\x02\u0143\u0144\x071\x02\x02\u0144\u0145\x06!\x02" + + "\x02\u0145C\x03\x02\x02\x02\u0146\u0147\x07\'\x02\x02\u0147E\x03\x02\x02" + + "\x02\u0148\u0149\x07-\x02\x02\u0149G\x03\x02\x02\x02\u014A\u014B\x07/" + + "\x02\x02\u014BI\x03\x02\x02\x02\u014C\u014D\x07>\x02\x02\u014D\u014E\x07" + + ">\x02\x02\u014EK\x03\x02\x02\x02\u014F\u0150\x07@\x02\x02\u0150\u0151" + + "\x07@\x02\x02\u0151M\x03\x02\x02\x02\u0152\u0153\x07@\x02\x02\u0153\u0154" + + "\x07@\x02\x02\u0154\u0155\x07@\x02\x02\u0155O\x03\x02\x02\x02\u0156\u0157" + + "\x07>\x02\x02\u0157Q\x03\x02\x02\x02\u0158\u0159\x07>\x02\x02\u0159\u015A" + + "\x07?\x02\x02\u015AS\x03\x02\x02\x02\u015B\u015C\x07@\x02\x02\u015CU\x03" + + "\x02\x02\x02\u015D\u015E\x07@\x02\x02\u015E\u015F\x07?\x02\x02\u015FW" + + "\x03\x02\x02\x02\u0160\u0161\x07?\x02\x02\u0161\u0162\x07?\x02\x02\u0162" + + "Y\x03\x02\x02\x02\u0163\u0164\x07?\x02\x02\u0164\u0165\x07?\x02\x02\u0165" + + "\u0166\x07?\x02\x02\u0166[\x03\x02\x02\x02\u0167\u0168\x07#\x02\x02\u0168" + + "\u0169\x07?\x02\x02\u0169]\x03\x02\x02\x02\u016A\u016B\x07#\x02\x02\u016B" + + "\u016C\x07?\x02\x02\u016C\u016D\x07?\x02\x02\u016D_\x03\x02\x02\x02\u016E" + + "\u016F\x07(\x02\x02\u016Fa\x03\x02\x02\x02\u0170\u0171\x07`\x02\x02\u0171" + + "c\x03\x02\x02\x02\u0172\u0173\x07~\x02\x02\u0173e\x03\x02\x02\x02\u0174" + + "\u0175\x07(\x02\x02\u0175\u0176\x07(\x02\x02\u0176g\x03\x02\x02\x02\u0177" + + "\u0178\x07~\x02\x02\u0178\u0179\x07~\x02\x02\u0179i\x03\x02\x02\x02\u017A" + + "\u017B\x07A\x02\x02\u017Bk\x03\x02\x02\x02\u017C\u017D\x07<\x02\x02\u017D" + + "m\x03\x02\x02\x02\u017E\u017F\x07A\x02\x02\u017F\u0180\x07<\x02\x02\u0180" + + "o\x03\x02\x02\x02\u0181\u0182\x07<\x02\x02\u0182\u0183\x07<\x02\x02\u0183" + + "q\x03\x02\x02\x02\u0184\u0185\x07/\x02\x02\u0185\u0186\x07@\x02\x02\u0186" + + "s\x03\x02\x02\x02\u0187\u0188\x07?\x02\x02\u0188\u0189\x07\x80\x02\x02" + + "\u0189u\x03\x02\x02\x02\u018A\u018B\x07?\x02\x02\u018B\u018C\x07?\x02" + + "\x02\u018C\u018D\x07\x80\x02\x02\u018Dw\x03\x02\x02\x02\u018E\u018F\x07" + + "-\x02\x02\u018F\u0190\x07-\x02\x02\u0190y\x03\x02\x02\x02\u0191\u0192" + + "\x07/\x02\x02\u0192\u0193\x07/\x02\x02\u0193{\x03\x02\x02\x02\u0194\u0195" + + "\x07?\x02\x02\u0195}\x03\x02\x02\x02\u0196\u0197\x07-\x02\x02\u0197\u0198" + + "\x07?\x02\x02\u0198\x7F\x03\x02\x02\x02\u0199\u019A\x07/\x02\x02\u019A" + + "\u019B\x07?\x02\x02\u019B\x81\x03\x02\x02\x02\u019C\u019D\x07,\x02\x02" + + "\u019D\u019E\x07?\x02\x02\u019E\x83\x03\x02\x02\x02\u019F\u01A0\x071\x02" + + "\x02\u01A0\u01A1\x07?\x02\x02\u01A1\x85\x03\x02\x02\x02\u01A2\u01A3\x07" + + "\'\x02\x02\u01A3\u01A4\x07?\x02\x02\u01A4\x87\x03\x02\x02\x02\u01A5\u01A6" + + "\x07(\x02\x02\u01A6\u01A7\x07?\x02\x02\u01A7\x89\x03\x02\x02\x02\u01A8" + + "\u01A9\x07`\x02\x02\u01A9\u01AA\x07?\x02\x02\u01AA\x8B\x03\x02\x02\x02" + + "\u01AB\u01AC\x07~\x02\x02\u01AC\u01AD\x07?\x02\x02\u01AD\x8D\x03\x02\x02" + + "\x02\u01AE\u01AF\x07>\x02\x02\u01AF\u01B0\x07>\x02\x02\u01B0\u01B1\x07" + + "?\x02\x02\u01B1\x8F\x03\x02\x02\x02\u01B2\u01B3\x07@\x02\x02\u01B3\u01B4" + + "\x07@\x02\x02\u01B4\u01B5\x07?\x02\x02\u01B5\x91\x03\x02\x02\x02\u01B6" + + "\u01B7\x07@\x02\x02\u01B7\u01B8\x07@\x02\x02\u01B8\u01B9\x07@\x02\x02" + + "\u01B9\u01BA\x07?\x02\x02\u01BA\x93\x03\x02\x02\x02\u01BB\u01BD\x072\x02" + + "\x02\u01BC\u01BE\t\x04\x02\x02\u01BD\u01BC\x03\x02\x02\x02\u01BE\u01BF" + + "\x03\x02\x02\x02\u01BF\u01BD\x03\x02\x02\x02\u01BF\u01C0\x03\x02\x02\x02" + + "\u01C0\u01C2\x03\x02\x02\x02\u01C1\u01C3\t\x05\x02\x02\u01C2\u01C1\x03" + + "\x02\x02\x02\u01C2\u01C3\x03\x02\x02\x02\u01C3\x95\x03\x02\x02\x02\u01C4" + + "\u01C5\x072\x02\x02\u01C5\u01C7\t\x06\x02\x02\u01C6\u01C8\t\x07\x02\x02" + + "\u01C7\u01C6\x03\x02\x02\x02\u01C8\u01C9\x03\x02\x02\x02\u01C9\u01C7\x03" + + "\x02\x02\x02\u01C9\u01CA\x03\x02\x02\x02\u01CA\u01CC\x03\x02\x02\x02\u01CB" + + "\u01CD\t\x05\x02\x02\u01CC\u01CB\x03\x02\x02\x02\u01CC\u01CD\x03\x02\x02" + + "\x02\u01CD\x97\x03\x02\x02\x02\u01CE\u01D7\x072\x02\x02\u01CF\u01D3\t" + + "\b\x02\x02\u01D0\u01D2\t\t\x02\x02\u01D1\u01D0\x03\x02\x02\x02\u01D2\u01D5" + + "\x03\x02\x02\x02\u01D3\u01D1\x03\x02\x02\x02\u01D3\u01D4\x03\x02\x02\x02" + + "\u01D4\u01D7\x03\x02\x02\x02\u01D5\u01D3\x03\x02\x02\x02\u01D6\u01CE\x03" + + "\x02\x02\x02\u01D6\u01CF\x03\x02\x02\x02\u01D7\u01D9\x03\x02\x02\x02\u01D8" + + "\u01DA\t\n\x02\x02\u01D9\u01D8\x03\x02\x02\x02\u01D9\u01DA\x03\x02\x02" + + "\x02\u01DA\x99\x03\x02\x02\x02\u01DB\u01E4\x072\x02\x02\u01DC\u01E0\t" + + "\b\x02\x02\u01DD\u01DF\t\t\x02\x02\u01DE\u01DD\x03\x02\x02\x02\u01DF\u01E2" + + "\x03\x02\x02\x02\u01E0\u01DE\x03\x02\x02\x02\u01E0\u01E1\x03\x02\x02\x02" + + "\u01E1\u01E4\x03\x02\x02\x02\u01E2\u01E0\x03\x02\x02\x02\u01E3\u01DB\x03" + + "\x02\x02\x02\u01E3\u01DC\x03\x02\x02\x02\u01E4\u01EB\x03\x02\x02\x02\u01E5" + + "\u01E7\x05\x16\v\x02\u01E6\u01E8\t\t\x02\x02\u01E7\u01E6\x03\x02\x02\x02" + + "\u01E8\u01E9\x03\x02\x02\x02\u01E9\u01E7\x03\x02\x02\x02\u01E9\u01EA\x03" + + "\x02\x02\x02\u01EA\u01EC\x03\x02\x02\x02\u01EB\u01E5\x03\x02\x02\x02\u01EB" + + "\u01EC\x03\x02\x02\x02\u01EC\u01F6\x03\x02\x02\x02\u01ED\u01EF\t\v\x02" + + "\x02\u01EE\u01F0\t\f\x02\x02\u01EF\u01EE\x03\x02\x02\x02\u01EF\u01F0\x03" + + "\x02\x02\x02\u01F0\u01F2\x03\x02\x02\x02\u01F1\u01F3\t\t\x02\x02\u01F2" + + "\u01F1\x03\x02\x02\x02\u01F3\u01F4\x03\x02\x02\x02\u01F4\u01F2\x03\x02" + + "\x02\x02\u01F4\u01F5\x03\x02\x02\x02\u01F5\u01F7\x03\x02\x02\x02\u01F6" + + "\u01ED\x03\x02\x02\x02\u01F6\u01F7\x03\x02\x02\x02\u01F7\u01F9\x03\x02" + + "\x02\x02\u01F8\u01FA\t\r\x02\x02\u01F9\u01F8\x03\x02\x02\x02\u01F9\u01FA" + + "\x03\x02\x02\x02\u01FA\x9B\x03\x02\x02\x02\u01FB\u0203\x07$\x02\x02\u01FC" + + "\u01FD\x07^\x02\x02\u01FD\u0202\x07$\x02\x02\u01FE\u01FF\x07^\x02\x02" + + "\u01FF\u0202\x07^\x02\x02\u0200\u0202\n\x0E\x02\x02\u0201\u01FC\x03\x02" + + "\x02\x02\u0201\u01FE\x03\x02\x02\x02\u0201\u0200\x03\x02\x02\x02\u0202" + + "\u0205\x03\x02\x02\x02\u0203\u0204\x03\x02\x02\x02\u0203\u0201\x03\x02" + + "\x02\x02\u0204\u0206\x03\x02\x02\x02\u0205\u0203\x03\x02\x02\x02\u0206" + + "\u0214\x07$\x02\x02\u0207\u020F\x07)\x02\x02\u0208\u0209\x07^\x02\x02" + + "\u0209\u020E\x07)\x02\x02\u020A\u020B\x07^\x02\x02\u020B\u020E\x07^\x02" + + "\x02\u020C\u020E\n\x0F\x02\x02\u020D\u0208"; private static readonly _serializedATNSegment1: string = - "\u020E\x03\x02\x02\x02\u020D\u020B\x03\x02\x02\x02\u020E\u0210\x07)\x02" + - "\x02\u020F\u01F7\x03\x02\x02\x02\u020F\u0203\x03\x02\x02\x02\u0210\x9B" + - "\x03\x02\x02\x02\u0211\u0215\x071\x02\x02\u0212\u0213\x07^\x02\x02\u0213" + - "\u0216\n\x10\x02\x02\u0214\u0216\n\x11\x02\x02\u0215\u0212\x03\x02\x02" + - "\x02\u0215\u0214\x03\x02\x02\x02\u0216\u0217\x03\x02\x02\x02\u0217\u0218" + - "\x03\x02\x02\x02\u0217\u0215\x03\x02\x02\x02\u0218\u0219\x03\x02\x02\x02" + - "\u0219\u021D\x071\x02\x02\u021A\u021C\t\x12\x02\x02\u021B\u021A\x03\x02" + - "\x02\x02\u021C\u021F\x03\x02\x02\x02\u021D\u021B\x03\x02\x02\x02\u021D" + - "\u021E\x03\x02\x02\x02\u021E\u0220\x03\x02\x02\x02\u021F\u021D\x03\x02" + - "\x02\x02\u0220\u0221\x06N\x03\x02\u0221\x9D\x03\x02\x02\x02\u0222\u0223" + - "\x07v\x02\x02\u0223\u0224\x07t\x02\x02\u0224\u0225\x07w\x02\x02\u0225" + - "\u0226\x07g\x02\x02\u0226\x9F\x03\x02\x02\x02\u0227\u0228\x07h\x02\x02" + - "\u0228\u0229\x07c\x02\x02\u0229\u022A\x07n\x02\x02\u022A\u022B\x07u\x02" + - "\x02\u022B\u022C\x07g\x02\x02\u022C\xA1\x03\x02\x02\x02\u022D\u022E\x07" + - "p\x02\x02\u022E\u022F\x07w\x02\x02\u022F\u0230\x07n\x02\x02\u0230\u0231" + - "\x07n\x02\x02\u0231\xA3\x03\x02\x02\x02\u0232\u0233\x07d\x02\x02\u0233" + - "\u0234\x07q\x02\x02\u0234\u0235\x07q\x02\x02\u0235\u0236\x07n\x02\x02" + - "\u0236\u0237\x07g\x02\x02\u0237\u0238\x07c\x02\x02\u0238\u0259\x07p\x02" + - "\x02\u0239\u023A\x07d\x02\x02\u023A\u023B\x07{\x02\x02\u023B\u023C\x07" + - "v\x02\x02\u023C\u0259\x07g\x02\x02\u023D\u023E\x07u\x02\x02\u023E\u023F" + - "\x07j\x02\x02\u023F\u0240\x07q\x02\x02\u0240\u0241\x07t\x02\x02\u0241" + - "\u0259\x07v\x02\x02\u0242\u0243\x07e\x02\x02\u0243\u0244\x07j\x02\x02" + - "\u0244\u0245\x07c\x02\x02\u0245\u0259\x07t\x02\x02\u0246\u0247\x07k\x02" + - "\x02\u0247\u0248\x07p\x02\x02\u0248\u0259\x07v\x02\x02\u0249\u024A\x07" + - "n\x02\x02\u024A\u024B\x07q\x02\x02\u024B\u024C\x07p\x02\x02\u024C\u0259" + - "\x07i\x02\x02\u024D\u024E\x07h\x02\x02\u024E\u024F\x07n\x02\x02\u024F" + - "\u0250\x07q\x02\x02\u0250\u0251\x07c\x02\x02\u0251\u0259\x07v\x02\x02" + - "\u0252\u0253\x07f\x02\x02\u0253\u0254\x07q\x02\x02\u0254\u0255\x07w\x02" + - "\x02\u0255\u0256\x07d\x02\x02\u0256\u0257\x07n\x02\x02\u0257\u0259\x07" + - "g\x02\x02\u0258\u0232\x03\x02\x02\x02\u0258\u0239\x03\x02\x02\x02\u0258" + - "\u023D\x03\x02\x02\x02\u0258\u0242\x03\x02\x02\x02\u0258\u0246\x03\x02" + - "\x02\x02\u0258\u0249\x03\x02\x02\x02\u0258\u024D\x03\x02\x02\x02\u0258" + - "\u0252\x03\x02\x02\x02\u0259\xA5\x03\x02\x02\x02\u025A\u025B\x07f\x02" + - "\x02\u025B\u025C\x07g\x02\x02\u025C\u025D\x07h\x02\x02\u025D\xA7\x03\x02" + - "\x02\x02\u025E\u0262\t\x13\x02\x02\u025F\u0261\t\x14\x02\x02\u0260\u025F" + - "\x03\x02\x02\x02\u0261\u0264\x03\x02\x02\x02\u0262\u0260\x03\x02\x02\x02" + - "\u0262\u0263\x03\x02\x02\x02\u0263\xA9\x03\x02\x02\x02\u0264\u0262\x03" + - "\x02\x02\x02\u0265\u026E\x072\x02\x02\u0266\u026A\t\b\x02\x02\u0267\u0269" + - "\t\t\x02\x02\u0268\u0267\x03\x02\x02\x02\u0269\u026C\x03\x02\x02\x02\u026A" + - "\u0268\x03\x02\x02\x02\u026A\u026B\x03\x02\x02\x02\u026B\u026E\x03\x02" + - "\x02\x02\u026C\u026A\x03\x02\x02\x02\u026D\u0265\x03\x02\x02\x02\u026D" + - "\u0266\x03\x02\x02\x02\u026E\u026F\x03\x02\x02\x02\u026F\u0270\bU\x04" + - "\x02\u0270\xAB\x03\x02\x02\x02\u0271\u0275\t\x13\x02\x02\u0272\u0274\t" + - "\x14\x02\x02\u0273\u0272\x03\x02\x02\x02\u0274\u0277\x03\x02\x02\x02\u0275" + - "\u0273\x03\x02\x02\x02\u0275\u0276\x03\x02\x02\x02\u0276\u0278\x03\x02" + - "\x02\x02\u0277\u0275\x03\x02\x02\x02\u0278\u0279\bV\x04\x02\u0279\xAD" + - "\x03\x02\x02\x02$\x02\x03\xB1\xBB\xC5\xCA\u01BB\u01BE\u01C5\u01C8\u01CF" + - "\u01D2\u01D5\u01DC\u01DF\u01E5\u01E7\u01EB\u01F0\u01F2\u01F5\u01FD\u01FF" + - "\u0209\u020B\u020F\u0215\u0217\u021D\u0258\u0262\u026A\u026D\u0275\x05" + - "\b\x02\x02\x04\x03\x02\x04\x02\x02"; + "\x03\x02\x02\x02\u020D\u020A\x03\x02\x02\x02\u020D\u020C\x03\x02\x02\x02" + + "\u020E\u0211\x03\x02\x02\x02\u020F\u0210\x03\x02\x02\x02\u020F\u020D\x03" + + "\x02\x02\x02\u0210\u0212\x03\x02\x02\x02\u0211\u020F\x03\x02\x02\x02\u0212" + + "\u0214\x07)\x02\x02\u0213\u01FB\x03\x02\x02\x02\u0213\u0207\x03\x02\x02" + + "\x02\u0214\x9D\x03\x02\x02\x02\u0215\u0219\x071\x02\x02\u0216\u0217\x07" + + "^\x02\x02\u0217\u021A\n\x10\x02\x02\u0218\u021A\n\x11\x02\x02\u0219\u0216" + + "\x03\x02\x02\x02\u0219\u0218\x03\x02\x02\x02\u021A\u021B\x03\x02\x02\x02" + + "\u021B\u021C\x03\x02\x02\x02\u021B\u0219\x03\x02\x02\x02\u021C\u021D\x03" + + "\x02\x02\x02\u021D\u0221\x071\x02\x02\u021E\u0220\t\x12\x02\x02\u021F" + + "\u021E\x03\x02\x02\x02\u0220\u0223\x03\x02\x02\x02\u0221\u021F\x03\x02" + + "\x02\x02\u0221\u0222\x03\x02\x02\x02\u0222\u0224\x03\x02\x02\x02\u0223" + + "\u0221\x03\x02\x02\x02\u0224\u0225\x06O\x03\x02\u0225\x9F\x03\x02\x02" + + "\x02\u0226\u0227\x07v\x02\x02\u0227\u0228\x07t\x02\x02\u0228\u0229\x07" + + "w\x02\x02\u0229\u022A\x07g\x02\x02\u022A\xA1\x03\x02\x02\x02\u022B\u022C" + + "\x07h\x02\x02\u022C\u022D\x07c\x02\x02\u022D\u022E\x07n\x02\x02\u022E" + + "\u022F\x07u\x02\x02\u022F\u0230\x07g\x02\x02\u0230\xA3\x03\x02\x02\x02" + + "\u0231\u0232\x07p\x02\x02\u0232\u0233\x07w\x02\x02\u0233\u0234\x07n\x02" + + "\x02\u0234\u0235\x07n\x02\x02\u0235\xA5\x03\x02\x02\x02\u0236\u0237\x07" + + "d\x02\x02\u0237\u0238\x07q\x02\x02\u0238\u0239\x07q\x02\x02\u0239\u023A" + + "\x07n\x02\x02\u023A\u023B\x07g\x02\x02\u023B\u023C\x07c\x02\x02\u023C" + + "\u025D\x07p\x02\x02\u023D\u023E\x07d\x02\x02\u023E\u023F\x07{\x02\x02" + + "\u023F\u0240\x07v\x02\x02\u0240\u025D\x07g\x02\x02\u0241\u0242\x07u\x02" + + "\x02\u0242\u0243\x07j\x02\x02\u0243\u0244\x07q\x02\x02\u0244\u0245\x07" + + "t\x02\x02\u0245\u025D\x07v\x02\x02\u0246\u0247\x07e\x02\x02\u0247\u0248" + + "\x07j\x02\x02\u0248\u0249\x07c\x02\x02\u0249\u025D\x07t\x02\x02\u024A" + + "\u024B\x07k\x02\x02\u024B\u024C\x07p\x02\x02\u024C\u025D\x07v\x02\x02" + + "\u024D\u024E\x07n\x02\x02\u024E\u024F\x07q\x02\x02\u024F\u0250\x07p\x02" + + "\x02\u0250\u025D\x07i\x02\x02\u0251\u0252\x07h\x02\x02\u0252\u0253\x07" + + "n\x02\x02\u0253\u0254\x07q\x02\x02\u0254\u0255\x07c\x02\x02\u0255\u025D" + + "\x07v\x02\x02\u0256\u0257\x07f\x02\x02\u0257\u0258\x07q\x02\x02\u0258" + + "\u0259\x07w\x02\x02\u0259\u025A\x07d\x02\x02\u025A\u025B\x07n\x02\x02" + + "\u025B\u025D\x07g\x02\x02\u025C\u0236\x03\x02\x02\x02\u025C\u023D\x03" + + "\x02\x02\x02\u025C\u0241\x03\x02\x02\x02\u025C\u0246\x03\x02\x02\x02\u025C" + + "\u024A\x03\x02\x02\x02\u025C\u024D\x03\x02\x02\x02\u025C\u0251\x03\x02" + + "\x02\x02\u025C\u0256\x03\x02\x02\x02\u025D\xA7\x03\x02\x02\x02\u025E\u025F" + + "\x07f\x02\x02\u025F\u0260\x07g\x02\x02\u0260\u0261\x07h\x02\x02\u0261" + + "\xA9\x03\x02\x02\x02\u0262\u0266\t\x13\x02\x02\u0263\u0265\t\x14\x02\x02" + + "\u0264\u0263\x03\x02\x02\x02\u0265\u0268\x03\x02\x02\x02\u0266\u0264\x03" + + "\x02\x02\x02\u0266\u0267\x03\x02\x02\x02\u0267\xAB\x03\x02\x02\x02\u0268" + + "\u0266\x03\x02\x02\x02\u0269\u0272\x072\x02\x02\u026A\u026E\t\b\x02\x02" + + "\u026B\u026D\t\t\x02\x02\u026C\u026B\x03\x02\x02\x02\u026D\u0270\x03\x02" + + "\x02\x02\u026E\u026C\x03\x02\x02\x02\u026E\u026F\x03\x02\x02\x02\u026F" + + "\u0272\x03\x02\x02\x02\u0270\u026E\x03\x02\x02\x02\u0271\u0269\x03\x02" + + "\x02\x02\u0271\u026A\x03\x02\x02\x02\u0272\u0273\x03\x02\x02\x02\u0273" + + "\u0274\bV\x04\x02\u0274\xAD\x03\x02\x02\x02\u0275\u0279\t\x13\x02\x02" + + "\u0276\u0278\t\x14\x02\x02\u0277\u0276\x03\x02\x02\x02\u0278\u027B\x03" + + "\x02\x02\x02\u0279\u0277\x03\x02\x02\x02\u0279\u027A\x03\x02\x02\x02\u027A" + + "\u027C\x03\x02\x02\x02\u027B\u0279\x03\x02\x02\x02\u027C\u027D\bW\x04" + + "\x02\u027D\xAF\x03\x02\x02\x02$\x02\x03\xB3\xBD\xC7\xCC\u01BF\u01C2\u01C9" + + "\u01CC\u01D3\u01D6\u01D9\u01E0\u01E3\u01E9\u01EB\u01EF\u01F4\u01F6\u01F9" + + "\u0201\u0203\u020D\u020F\u0213\u0219\u021B\u0221\u025C\u0266\u026E\u0271" + + "\u0279\x05\b\x02\x02\x04\x03\x02\x04\x02\x02"; public static readonly _serializedATN: string = Utils.join( [ painless_lexer._serializedATNSegment0, diff --git a/packages/kbn-monaco/src/painless/antlr/painless_parser.g4 b/packages/kbn-monaco/src/painless/antlr/painless_parser.g4 index 58a9285c57a00..127b47617b1eb 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_parser.g4 +++ b/packages/kbn-monaco/src/painless/antlr/painless_parser.g4 @@ -155,7 +155,7 @@ primary | listinitializer # listinit | mapinitializer # mapinit | ID # variable - | ID arguments # calllocal + | (ID | DOLLAR) arguments # calllocal | NEW type arguments # newobject ; diff --git a/packages/kbn-monaco/src/painless/antlr/painless_parser.interp b/packages/kbn-monaco/src/painless/antlr/painless_parser.interp index 4c0a7a4399e4e..de912e1ced298 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_parser.interp +++ b/packages/kbn-monaco/src/painless/antlr/painless_parser.interp @@ -8,6 +8,7 @@ null ']' '(' ')' +'$' '.' '?.' ',' @@ -96,6 +97,7 @@ LBRACE RBRACE LP RP +DOLLAR DOT NSDOT COMMA @@ -217,4 +219,4 @@ funcref atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 87, 574, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 3, 2, 7, 2, 82, 10, 2, 12, 2, 14, 2, 85, 11, 2, 3, 2, 7, 2, 88, 10, 2, 12, 2, 14, 2, 91, 11, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 107, 10, 4, 12, 4, 14, 4, 110, 11, 4, 5, 4, 112, 10, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 120, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 130, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 138, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 143, 10, 6, 3, 6, 3, 6, 5, 6, 147, 10, 6, 3, 6, 3, 6, 5, 6, 151, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 156, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 6, 6, 178, 10, 6, 13, 6, 14, 6, 179, 5, 6, 182, 10, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 196, 10, 7, 3, 7, 3, 7, 3, 7, 5, 7, 201, 10, 7, 3, 8, 3, 8, 5, 8, 205, 10, 8, 3, 9, 3, 9, 7, 9, 209, 10, 9, 12, 9, 14, 9, 212, 11, 9, 3, 9, 5, 9, 215, 10, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 5, 11, 223, 10, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 13, 7, 13, 231, 10, 13, 12, 13, 14, 13, 234, 11, 13, 3, 14, 3, 14, 3, 14, 7, 14, 239, 10, 14, 12, 14, 14, 14, 242, 11, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 7, 15, 249, 10, 15, 12, 15, 14, 15, 252, 11, 15, 5, 15, 254, 10, 15, 3, 16, 3, 16, 3, 16, 5, 16, 259, 10, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 7, 18, 310, 10, 18, 12, 18, 14, 18, 313, 11, 18, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 5, 19, 326, 10, 19, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 5, 20, 333, 10, 20, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 5, 21, 342, 10, 21, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 5, 22, 354, 10, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 6, 24, 361, 10, 24, 13, 24, 14, 24, 362, 3, 24, 3, 24, 3, 24, 6, 24, 368, 10, 24, 13, 24, 14, 24, 369, 3, 24, 3, 24, 3, 24, 7, 24, 375, 10, 24, 12, 24, 14, 24, 378, 11, 24, 3, 24, 3, 24, 7, 24, 382, 10, 24, 12, 24, 14, 24, 385, 11, 24, 5, 24, 387, 10, 24, 3, 25, 3, 25, 7, 25, 391, 10, 25, 12, 25, 14, 25, 394, 11, 25, 3, 25, 5, 25, 397, 10, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 5, 26, 418, 10, 26, 3, 27, 3, 27, 3, 27, 5, 27, 423, 10, 27, 3, 28, 3, 28, 5, 28, 427, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 6, 32, 446, 10, 32, 13, 32, 14, 32, 447, 3, 32, 3, 32, 7, 32, 452, 10, 32, 12, 32, 14, 32, 455, 11, 32, 5, 32, 457, 10, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 7, 32, 467, 10, 32, 12, 32, 14, 32, 470, 11, 32, 5, 32, 472, 10, 32, 3, 32, 3, 32, 7, 32, 476, 10, 32, 12, 32, 14, 32, 479, 11, 32, 5, 32, 481, 10, 32, 3, 33, 3, 33, 3, 33, 3, 33, 7, 33, 487, 10, 33, 12, 33, 14, 33, 490, 11, 33, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 496, 10, 33, 3, 34, 3, 34, 3, 34, 3, 34, 7, 34, 502, 10, 34, 12, 34, 14, 34, 505, 11, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 5, 34, 512, 10, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36, 3, 36, 7, 36, 522, 10, 36, 12, 36, 14, 36, 525, 11, 36, 5, 36, 527, 10, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 5, 37, 534, 10, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 541, 10, 38, 12, 38, 14, 38, 544, 11, 38, 5, 38, 546, 10, 38, 3, 38, 5, 38, 549, 10, 38, 3, 38, 3, 38, 3, 38, 5, 38, 554, 10, 38, 3, 39, 5, 39, 557, 10, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 5, 40, 572, 10, 40, 3, 40, 2, 2, 3, 34, 41, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 22, 2, 24, 2, 26, 2, 28, 2, 30, 2, 32, 2, 34, 2, 36, 2, 38, 2, 40, 2, 42, 2, 44, 2, 46, 2, 48, 2, 50, 2, 52, 2, 54, 2, 56, 2, 58, 2, 60, 2, 62, 2, 64, 2, 66, 2, 68, 2, 70, 2, 72, 2, 74, 2, 76, 2, 78, 2, 2, 16, 3, 3, 14, 14, 3, 2, 32, 34, 3, 2, 35, 36, 3, 2, 58, 59, 3, 2, 37, 39, 3, 2, 40, 43, 3, 2, 44, 47, 3, 2, 62, 73, 3, 2, 60, 61, 3, 2, 30, 31, 3, 2, 83, 84, 3, 2, 74, 77, 3, 2, 11, 12, 3, 2, 86, 87, 2, 633, 2, 83, 3, 2, 2, 2, 4, 94, 3, 2, 2, 2, 6, 99, 3, 2, 2, 2, 8, 119, 3, 2, 2, 2, 10, 181, 3, 2, 2, 2, 12, 200, 3, 2, 2, 2, 14, 204, 3, 2, 2, 2, 16, 206, 3, 2, 2, 2, 18, 218, 3, 2, 2, 2, 20, 222, 3, 2, 2, 2, 22, 224, 3, 2, 2, 2, 24, 226, 3, 2, 2, 2, 26, 235, 3, 2, 2, 2, 28, 253, 3, 2, 2, 2, 30, 255, 3, 2, 2, 2, 32, 260, 3, 2, 2, 2, 34, 267, 3, 2, 2, 2, 36, 325, 3, 2, 2, 2, 38, 332, 3, 2, 2, 2, 40, 341, 3, 2, 2, 2, 42, 353, 3, 2, 2, 2, 44, 355, 3, 2, 2, 2, 46, 386, 3, 2, 2, 2, 48, 396, 3, 2, 2, 2, 50, 417, 3, 2, 2, 2, 52, 422, 3, 2, 2, 2, 54, 426, 3, 2, 2, 2, 56, 428, 3, 2, 2, 2, 58, 432, 3, 2, 2, 2, 60, 435, 3, 2, 2, 2, 62, 480, 3, 2, 2, 2, 64, 495, 3, 2, 2, 2, 66, 511, 3, 2, 2, 2, 68, 513, 3, 2, 2, 2, 70, 517, 3, 2, 2, 2, 72, 533, 3, 2, 2, 2, 74, 548, 3, 2, 2, 2, 76, 556, 3, 2, 2, 2, 78, 571, 3, 2, 2, 2, 80, 82, 5, 4, 3, 2, 81, 80, 3, 2, 2, 2, 82, 85, 3, 2, 2, 2, 83, 81, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 89, 3, 2, 2, 2, 85, 83, 3, 2, 2, 2, 86, 88, 5, 8, 5, 2, 87, 86, 3, 2, 2, 2, 88, 91, 3, 2, 2, 2, 89, 87, 3, 2, 2, 2, 89, 90, 3, 2, 2, 2, 90, 92, 3, 2, 2, 2, 91, 89, 3, 2, 2, 2, 92, 93, 7, 2, 2, 3, 93, 3, 3, 2, 2, 2, 94, 95, 5, 26, 14, 2, 95, 96, 7, 85, 2, 2, 96, 97, 5, 6, 4, 2, 97, 98, 5, 16, 9, 2, 98, 5, 3, 2, 2, 2, 99, 111, 7, 9, 2, 2, 100, 101, 5, 26, 14, 2, 101, 108, 7, 85, 2, 2, 102, 103, 7, 13, 2, 2, 103, 104, 5, 26, 14, 2, 104, 105, 7, 85, 2, 2, 105, 107, 3, 2, 2, 2, 106, 102, 3, 2, 2, 2, 107, 110, 3, 2, 2, 2, 108, 106, 3, 2, 2, 2, 108, 109, 3, 2, 2, 2, 109, 112, 3, 2, 2, 2, 110, 108, 3, 2, 2, 2, 111, 100, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 114, 7, 10, 2, 2, 114, 7, 3, 2, 2, 2, 115, 120, 5, 10, 6, 2, 116, 117, 5, 12, 7, 2, 117, 118, 9, 2, 2, 2, 118, 120, 3, 2, 2, 2, 119, 115, 3, 2, 2, 2, 119, 116, 3, 2, 2, 2, 120, 9, 3, 2, 2, 2, 121, 122, 7, 15, 2, 2, 122, 123, 7, 9, 2, 2, 123, 124, 5, 36, 19, 2, 124, 125, 7, 10, 2, 2, 125, 129, 5, 14, 8, 2, 126, 127, 7, 17, 2, 2, 127, 130, 5, 14, 8, 2, 128, 130, 6, 6, 2, 2, 129, 126, 3, 2, 2, 2, 129, 128, 3, 2, 2, 2, 130, 182, 3, 2, 2, 2, 131, 132, 7, 18, 2, 2, 132, 133, 7, 9, 2, 2, 133, 134, 5, 36, 19, 2, 134, 137, 7, 10, 2, 2, 135, 138, 5, 14, 8, 2, 136, 138, 5, 18, 10, 2, 137, 135, 3, 2, 2, 2, 137, 136, 3, 2, 2, 2, 138, 182, 3, 2, 2, 2, 139, 140, 7, 20, 2, 2, 140, 142, 7, 9, 2, 2, 141, 143, 5, 20, 11, 2, 142, 141, 3, 2, 2, 2, 142, 143, 3, 2, 2, 2, 143, 144, 3, 2, 2, 2, 144, 146, 7, 14, 2, 2, 145, 147, 5, 36, 19, 2, 146, 145, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 148, 3, 2, 2, 2, 148, 150, 7, 14, 2, 2, 149, 151, 5, 22, 12, 2, 150, 149, 3, 2, 2, 2, 150, 151, 3, 2, 2, 2, 151, 152, 3, 2, 2, 2, 152, 155, 7, 10, 2, 2, 153, 156, 5, 14, 8, 2, 154, 156, 5, 18, 10, 2, 155, 153, 3, 2, 2, 2, 155, 154, 3, 2, 2, 2, 156, 182, 3, 2, 2, 2, 157, 158, 7, 20, 2, 2, 158, 159, 7, 9, 2, 2, 159, 160, 5, 26, 14, 2, 160, 161, 7, 85, 2, 2, 161, 162, 7, 54, 2, 2, 162, 163, 5, 36, 19, 2, 163, 164, 7, 10, 2, 2, 164, 165, 5, 14, 8, 2, 165, 182, 3, 2, 2, 2, 166, 167, 7, 20, 2, 2, 167, 168, 7, 9, 2, 2, 168, 169, 7, 85, 2, 2, 169, 170, 7, 16, 2, 2, 170, 171, 5, 36, 19, 2, 171, 172, 7, 10, 2, 2, 172, 173, 5, 14, 8, 2, 173, 182, 3, 2, 2, 2, 174, 175, 7, 25, 2, 2, 175, 177, 5, 16, 9, 2, 176, 178, 5, 32, 17, 2, 177, 176, 3, 2, 2, 2, 178, 179, 3, 2, 2, 2, 179, 177, 3, 2, 2, 2, 179, 180, 3, 2, 2, 2, 180, 182, 3, 2, 2, 2, 181, 121, 3, 2, 2, 2, 181, 131, 3, 2, 2, 2, 181, 139, 3, 2, 2, 2, 181, 157, 3, 2, 2, 2, 181, 166, 3, 2, 2, 2, 181, 174, 3, 2, 2, 2, 182, 11, 3, 2, 2, 2, 183, 184, 7, 19, 2, 2, 184, 185, 5, 16, 9, 2, 185, 186, 7, 18, 2, 2, 186, 187, 7, 9, 2, 2, 187, 188, 5, 36, 19, 2, 188, 189, 7, 10, 2, 2, 189, 201, 3, 2, 2, 2, 190, 201, 5, 24, 13, 2, 191, 201, 7, 21, 2, 2, 192, 201, 7, 22, 2, 2, 193, 195, 7, 23, 2, 2, 194, 196, 5, 36, 19, 2, 195, 194, 3, 2, 2, 2, 195, 196, 3, 2, 2, 2, 196, 201, 3, 2, 2, 2, 197, 198, 7, 27, 2, 2, 198, 201, 5, 36, 19, 2, 199, 201, 5, 36, 19, 2, 200, 183, 3, 2, 2, 2, 200, 190, 3, 2, 2, 2, 200, 191, 3, 2, 2, 2, 200, 192, 3, 2, 2, 2, 200, 193, 3, 2, 2, 2, 200, 197, 3, 2, 2, 2, 200, 199, 3, 2, 2, 2, 201, 13, 3, 2, 2, 2, 202, 205, 5, 16, 9, 2, 203, 205, 5, 8, 5, 2, 204, 202, 3, 2, 2, 2, 204, 203, 3, 2, 2, 2, 205, 15, 3, 2, 2, 2, 206, 210, 7, 5, 2, 2, 207, 209, 5, 8, 5, 2, 208, 207, 3, 2, 2, 2, 209, 212, 3, 2, 2, 2, 210, 208, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2, 211, 214, 3, 2, 2, 2, 212, 210, 3, 2, 2, 2, 213, 215, 5, 12, 7, 2, 214, 213, 3, 2, 2, 2, 214, 215, 3, 2, 2, 2, 215, 216, 3, 2, 2, 2, 216, 217, 7, 6, 2, 2, 217, 17, 3, 2, 2, 2, 218, 219, 7, 14, 2, 2, 219, 19, 3, 2, 2, 2, 220, 223, 5, 24, 13, 2, 221, 223, 5, 36, 19, 2, 222, 220, 3, 2, 2, 2, 222, 221, 3, 2, 2, 2, 223, 21, 3, 2, 2, 2, 224, 225, 5, 36, 19, 2, 225, 23, 3, 2, 2, 2, 226, 227, 5, 26, 14, 2, 227, 232, 5, 30, 16, 2, 228, 229, 7, 13, 2, 2, 229, 231, 5, 30, 16, 2, 230, 228, 3, 2, 2, 2, 231, 234, 3, 2, 2, 2, 232, 230, 3, 2, 2, 2, 232, 233, 3, 2, 2, 2, 233, 25, 3, 2, 2, 2, 234, 232, 3, 2, 2, 2, 235, 240, 5, 28, 15, 2, 236, 237, 7, 7, 2, 2, 237, 239, 7, 8, 2, 2, 238, 236, 3, 2, 2, 2, 239, 242, 3, 2, 2, 2, 240, 238, 3, 2, 2, 2, 240, 241, 3, 2, 2, 2, 241, 27, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 243, 254, 7, 84, 2, 2, 244, 254, 7, 83, 2, 2, 245, 250, 7, 85, 2, 2, 246, 247, 7, 11, 2, 2, 247, 249, 7, 87, 2, 2, 248, 246, 3, 2, 2, 2, 249, 252, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 250, 251, 3, 2, 2, 2, 251, 254, 3, 2, 2, 2, 252, 250, 3, 2, 2, 2, 253, 243, 3, 2, 2, 2, 253, 244, 3, 2, 2, 2, 253, 245, 3, 2, 2, 2, 254, 29, 3, 2, 2, 2, 255, 258, 7, 85, 2, 2, 256, 257, 7, 62, 2, 2, 257, 259, 5, 36, 19, 2, 258, 256, 3, 2, 2, 2, 258, 259, 3, 2, 2, 2, 259, 31, 3, 2, 2, 2, 260, 261, 7, 26, 2, 2, 261, 262, 7, 9, 2, 2, 262, 263, 5, 28, 15, 2, 263, 264, 7, 85, 2, 2, 264, 265, 7, 10, 2, 2, 265, 266, 5, 16, 9, 2, 266, 33, 3, 2, 2, 2, 267, 268, 8, 18, 1, 2, 268, 269, 5, 38, 20, 2, 269, 311, 3, 2, 2, 2, 270, 271, 12, 15, 2, 2, 271, 272, 9, 3, 2, 2, 272, 310, 5, 34, 18, 16, 273, 274, 12, 14, 2, 2, 274, 275, 9, 4, 2, 2, 275, 310, 5, 34, 18, 15, 276, 277, 12, 13, 2, 2, 277, 278, 9, 5, 2, 2, 278, 310, 5, 34, 18, 14, 279, 280, 12, 12, 2, 2, 280, 281, 9, 6, 2, 2, 281, 310, 5, 34, 18, 13, 282, 283, 12, 11, 2, 2, 283, 284, 9, 7, 2, 2, 284, 310, 5, 34, 18, 12, 285, 286, 12, 9, 2, 2, 286, 287, 9, 8, 2, 2, 287, 310, 5, 34, 18, 10, 288, 289, 12, 8, 2, 2, 289, 290, 7, 48, 2, 2, 290, 310, 5, 34, 18, 9, 291, 292, 12, 7, 2, 2, 292, 293, 7, 49, 2, 2, 293, 310, 5, 34, 18, 8, 294, 295, 12, 6, 2, 2, 295, 296, 7, 50, 2, 2, 296, 310, 5, 34, 18, 7, 297, 298, 12, 5, 2, 2, 298, 299, 7, 51, 2, 2, 299, 310, 5, 34, 18, 6, 300, 301, 12, 4, 2, 2, 301, 302, 7, 52, 2, 2, 302, 310, 5, 34, 18, 5, 303, 304, 12, 3, 2, 2, 304, 305, 7, 55, 2, 2, 305, 310, 5, 34, 18, 3, 306, 307, 12, 10, 2, 2, 307, 308, 7, 29, 2, 2, 308, 310, 5, 26, 14, 2, 309, 270, 3, 2, 2, 2, 309, 273, 3, 2, 2, 2, 309, 276, 3, 2, 2, 2, 309, 279, 3, 2, 2, 2, 309, 282, 3, 2, 2, 2, 309, 285, 3, 2, 2, 2, 309, 288, 3, 2, 2, 2, 309, 291, 3, 2, 2, 2, 309, 294, 3, 2, 2, 2, 309, 297, 3, 2, 2, 2, 309, 300, 3, 2, 2, 2, 309, 303, 3, 2, 2, 2, 309, 306, 3, 2, 2, 2, 310, 313, 3, 2, 2, 2, 311, 309, 3, 2, 2, 2, 311, 312, 3, 2, 2, 2, 312, 35, 3, 2, 2, 2, 313, 311, 3, 2, 2, 2, 314, 326, 5, 34, 18, 2, 315, 316, 5, 34, 18, 2, 316, 317, 7, 53, 2, 2, 317, 318, 5, 36, 19, 2, 318, 319, 7, 54, 2, 2, 319, 320, 5, 36, 19, 2, 320, 326, 3, 2, 2, 2, 321, 322, 5, 34, 18, 2, 322, 323, 9, 9, 2, 2, 323, 324, 5, 36, 19, 2, 324, 326, 3, 2, 2, 2, 325, 314, 3, 2, 2, 2, 325, 315, 3, 2, 2, 2, 325, 321, 3, 2, 2, 2, 326, 37, 3, 2, 2, 2, 327, 328, 9, 10, 2, 2, 328, 333, 5, 48, 25, 2, 329, 330, 9, 4, 2, 2, 330, 333, 5, 38, 20, 2, 331, 333, 5, 40, 21, 2, 332, 327, 3, 2, 2, 2, 332, 329, 3, 2, 2, 2, 332, 331, 3, 2, 2, 2, 333, 39, 3, 2, 2, 2, 334, 342, 5, 48, 25, 2, 335, 336, 5, 48, 25, 2, 336, 337, 9, 10, 2, 2, 337, 342, 3, 2, 2, 2, 338, 339, 9, 11, 2, 2, 339, 342, 5, 38, 20, 2, 340, 342, 5, 42, 22, 2, 341, 334, 3, 2, 2, 2, 341, 335, 3, 2, 2, 2, 341, 338, 3, 2, 2, 2, 341, 340, 3, 2, 2, 2, 342, 41, 3, 2, 2, 2, 343, 344, 7, 9, 2, 2, 344, 345, 5, 44, 23, 2, 345, 346, 7, 10, 2, 2, 346, 347, 5, 38, 20, 2, 347, 354, 3, 2, 2, 2, 348, 349, 7, 9, 2, 2, 349, 350, 5, 46, 24, 2, 350, 351, 7, 10, 2, 2, 351, 352, 5, 40, 21, 2, 352, 354, 3, 2, 2, 2, 353, 343, 3, 2, 2, 2, 353, 348, 3, 2, 2, 2, 354, 43, 3, 2, 2, 2, 355, 356, 9, 12, 2, 2, 356, 45, 3, 2, 2, 2, 357, 360, 7, 84, 2, 2, 358, 359, 7, 7, 2, 2, 359, 361, 7, 8, 2, 2, 360, 358, 3, 2, 2, 2, 361, 362, 3, 2, 2, 2, 362, 360, 3, 2, 2, 2, 362, 363, 3, 2, 2, 2, 363, 387, 3, 2, 2, 2, 364, 367, 7, 83, 2, 2, 365, 366, 7, 7, 2, 2, 366, 368, 7, 8, 2, 2, 367, 365, 3, 2, 2, 2, 368, 369, 3, 2, 2, 2, 369, 367, 3, 2, 2, 2, 369, 370, 3, 2, 2, 2, 370, 387, 3, 2, 2, 2, 371, 376, 7, 85, 2, 2, 372, 373, 7, 11, 2, 2, 373, 375, 7, 87, 2, 2, 374, 372, 3, 2, 2, 2, 375, 378, 3, 2, 2, 2, 376, 374, 3, 2, 2, 2, 376, 377, 3, 2, 2, 2, 377, 383, 3, 2, 2, 2, 378, 376, 3, 2, 2, 2, 379, 380, 7, 7, 2, 2, 380, 382, 7, 8, 2, 2, 381, 379, 3, 2, 2, 2, 382, 385, 3, 2, 2, 2, 383, 381, 3, 2, 2, 2, 383, 384, 3, 2, 2, 2, 384, 387, 3, 2, 2, 2, 385, 383, 3, 2, 2, 2, 386, 357, 3, 2, 2, 2, 386, 364, 3, 2, 2, 2, 386, 371, 3, 2, 2, 2, 387, 47, 3, 2, 2, 2, 388, 392, 5, 50, 26, 2, 389, 391, 5, 52, 27, 2, 390, 389, 3, 2, 2, 2, 391, 394, 3, 2, 2, 2, 392, 390, 3, 2, 2, 2, 392, 393, 3, 2, 2, 2, 393, 397, 3, 2, 2, 2, 394, 392, 3, 2, 2, 2, 395, 397, 5, 62, 32, 2, 396, 388, 3, 2, 2, 2, 396, 395, 3, 2, 2, 2, 397, 49, 3, 2, 2, 2, 398, 399, 7, 9, 2, 2, 399, 400, 5, 36, 19, 2, 400, 401, 7, 10, 2, 2, 401, 418, 3, 2, 2, 2, 402, 418, 9, 13, 2, 2, 403, 418, 7, 80, 2, 2, 404, 418, 7, 81, 2, 2, 405, 418, 7, 82, 2, 2, 406, 418, 7, 78, 2, 2, 407, 418, 7, 79, 2, 2, 408, 418, 5, 64, 33, 2, 409, 418, 5, 66, 34, 2, 410, 418, 7, 85, 2, 2, 411, 412, 7, 85, 2, 2, 412, 418, 5, 70, 36, 2, 413, 414, 7, 24, 2, 2, 414, 415, 5, 28, 15, 2, 415, 416, 5, 70, 36, 2, 416, 418, 3, 2, 2, 2, 417, 398, 3, 2, 2, 2, 417, 402, 3, 2, 2, 2, 417, 403, 3, 2, 2, 2, 417, 404, 3, 2, 2, 2, 417, 405, 3, 2, 2, 2, 417, 406, 3, 2, 2, 2, 417, 407, 3, 2, 2, 2, 417, 408, 3, 2, 2, 2, 417, 409, 3, 2, 2, 2, 417, 410, 3, 2, 2, 2, 417, 411, 3, 2, 2, 2, 417, 413, 3, 2, 2, 2, 418, 51, 3, 2, 2, 2, 419, 423, 5, 56, 29, 2, 420, 423, 5, 58, 30, 2, 421, 423, 5, 60, 31, 2, 422, 419, 3, 2, 2, 2, 422, 420, 3, 2, 2, 2, 422, 421, 3, 2, 2, 2, 423, 53, 3, 2, 2, 2, 424, 427, 5, 56, 29, 2, 425, 427, 5, 58, 30, 2, 426, 424, 3, 2, 2, 2, 426, 425, 3, 2, 2, 2, 427, 55, 3, 2, 2, 2, 428, 429, 9, 14, 2, 2, 429, 430, 7, 87, 2, 2, 430, 431, 5, 70, 36, 2, 431, 57, 3, 2, 2, 2, 432, 433, 9, 14, 2, 2, 433, 434, 9, 15, 2, 2, 434, 59, 3, 2, 2, 2, 435, 436, 7, 7, 2, 2, 436, 437, 5, 36, 19, 2, 437, 438, 7, 8, 2, 2, 438, 61, 3, 2, 2, 2, 439, 440, 7, 24, 2, 2, 440, 445, 5, 28, 15, 2, 441, 442, 7, 7, 2, 2, 442, 443, 5, 36, 19, 2, 443, 444, 7, 8, 2, 2, 444, 446, 3, 2, 2, 2, 445, 441, 3, 2, 2, 2, 446, 447, 3, 2, 2, 2, 447, 445, 3, 2, 2, 2, 447, 448, 3, 2, 2, 2, 448, 456, 3, 2, 2, 2, 449, 453, 5, 54, 28, 2, 450, 452, 5, 52, 27, 2, 451, 450, 3, 2, 2, 2, 452, 455, 3, 2, 2, 2, 453, 451, 3, 2, 2, 2, 453, 454, 3, 2, 2, 2, 454, 457, 3, 2, 2, 2, 455, 453, 3, 2, 2, 2, 456, 449, 3, 2, 2, 2, 456, 457, 3, 2, 2, 2, 457, 481, 3, 2, 2, 2, 458, 459, 7, 24, 2, 2, 459, 460, 5, 28, 15, 2, 460, 461, 7, 7, 2, 2, 461, 462, 7, 8, 2, 2, 462, 471, 7, 5, 2, 2, 463, 468, 5, 36, 19, 2, 464, 465, 7, 13, 2, 2, 465, 467, 5, 36, 19, 2, 466, 464, 3, 2, 2, 2, 467, 470, 3, 2, 2, 2, 468, 466, 3, 2, 2, 2, 468, 469, 3, 2, 2, 2, 469, 472, 3, 2, 2, 2, 470, 468, 3, 2, 2, 2, 471, 463, 3, 2, 2, 2, 471, 472, 3, 2, 2, 2, 472, 473, 3, 2, 2, 2, 473, 477, 7, 6, 2, 2, 474, 476, 5, 52, 27, 2, 475, 474, 3, 2, 2, 2, 476, 479, 3, 2, 2, 2, 477, 475, 3, 2, 2, 2, 477, 478, 3, 2, 2, 2, 478, 481, 3, 2, 2, 2, 479, 477, 3, 2, 2, 2, 480, 439, 3, 2, 2, 2, 480, 458, 3, 2, 2, 2, 481, 63, 3, 2, 2, 2, 482, 483, 7, 7, 2, 2, 483, 488, 5, 36, 19, 2, 484, 485, 7, 13, 2, 2, 485, 487, 5, 36, 19, 2, 486, 484, 3, 2, 2, 2, 487, 490, 3, 2, 2, 2, 488, 486, 3, 2, 2, 2, 488, 489, 3, 2, 2, 2, 489, 491, 3, 2, 2, 2, 490, 488, 3, 2, 2, 2, 491, 492, 7, 8, 2, 2, 492, 496, 3, 2, 2, 2, 493, 494, 7, 7, 2, 2, 494, 496, 7, 8, 2, 2, 495, 482, 3, 2, 2, 2, 495, 493, 3, 2, 2, 2, 496, 65, 3, 2, 2, 2, 497, 498, 7, 7, 2, 2, 498, 503, 5, 68, 35, 2, 499, 500, 7, 13, 2, 2, 500, 502, 5, 68, 35, 2, 501, 499, 3, 2, 2, 2, 502, 505, 3, 2, 2, 2, 503, 501, 3, 2, 2, 2, 503, 504, 3, 2, 2, 2, 504, 506, 3, 2, 2, 2, 505, 503, 3, 2, 2, 2, 506, 507, 7, 8, 2, 2, 507, 512, 3, 2, 2, 2, 508, 509, 7, 7, 2, 2, 509, 510, 7, 54, 2, 2, 510, 512, 7, 8, 2, 2, 511, 497, 3, 2, 2, 2, 511, 508, 3, 2, 2, 2, 512, 67, 3, 2, 2, 2, 513, 514, 5, 36, 19, 2, 514, 515, 7, 54, 2, 2, 515, 516, 5, 36, 19, 2, 516, 69, 3, 2, 2, 2, 517, 526, 7, 9, 2, 2, 518, 523, 5, 72, 37, 2, 519, 520, 7, 13, 2, 2, 520, 522, 5, 72, 37, 2, 521, 519, 3, 2, 2, 2, 522, 525, 3, 2, 2, 2, 523, 521, 3, 2, 2, 2, 523, 524, 3, 2, 2, 2, 524, 527, 3, 2, 2, 2, 525, 523, 3, 2, 2, 2, 526, 518, 3, 2, 2, 2, 526, 527, 3, 2, 2, 2, 527, 528, 3, 2, 2, 2, 528, 529, 7, 10, 2, 2, 529, 71, 3, 2, 2, 2, 530, 534, 5, 36, 19, 2, 531, 534, 5, 74, 38, 2, 532, 534, 5, 78, 40, 2, 533, 530, 3, 2, 2, 2, 533, 531, 3, 2, 2, 2, 533, 532, 3, 2, 2, 2, 534, 73, 3, 2, 2, 2, 535, 549, 5, 76, 39, 2, 536, 545, 7, 9, 2, 2, 537, 542, 5, 76, 39, 2, 538, 539, 7, 13, 2, 2, 539, 541, 5, 76, 39, 2, 540, 538, 3, 2, 2, 2, 541, 544, 3, 2, 2, 2, 542, 540, 3, 2, 2, 2, 542, 543, 3, 2, 2, 2, 543, 546, 3, 2, 2, 2, 544, 542, 3, 2, 2, 2, 545, 537, 3, 2, 2, 2, 545, 546, 3, 2, 2, 2, 546, 547, 3, 2, 2, 2, 547, 549, 7, 10, 2, 2, 548, 535, 3, 2, 2, 2, 548, 536, 3, 2, 2, 2, 549, 550, 3, 2, 2, 2, 550, 553, 7, 57, 2, 2, 551, 554, 5, 16, 9, 2, 552, 554, 5, 36, 19, 2, 553, 551, 3, 2, 2, 2, 553, 552, 3, 2, 2, 2, 554, 75, 3, 2, 2, 2, 555, 557, 5, 26, 14, 2, 556, 555, 3, 2, 2, 2, 556, 557, 3, 2, 2, 2, 557, 558, 3, 2, 2, 2, 558, 559, 7, 85, 2, 2, 559, 77, 3, 2, 2, 2, 560, 561, 5, 26, 14, 2, 561, 562, 7, 56, 2, 2, 562, 563, 7, 85, 2, 2, 563, 572, 3, 2, 2, 2, 564, 565, 5, 26, 14, 2, 565, 566, 7, 56, 2, 2, 566, 567, 7, 24, 2, 2, 567, 572, 3, 2, 2, 2, 568, 569, 7, 28, 2, 2, 569, 570, 7, 56, 2, 2, 570, 572, 7, 85, 2, 2, 571, 560, 3, 2, 2, 2, 571, 564, 3, 2, 2, 2, 571, 568, 3, 2, 2, 2, 572, 79, 3, 2, 2, 2, 62, 83, 89, 108, 111, 119, 129, 137, 142, 146, 150, 155, 179, 181, 195, 200, 204, 210, 214, 222, 232, 240, 250, 253, 258, 309, 311, 325, 332, 341, 353, 362, 369, 376, 383, 386, 392, 396, 417, 422, 426, 447, 453, 456, 468, 471, 477, 480, 488, 495, 503, 511, 523, 526, 533, 542, 545, 548, 553, 556, 571] \ No newline at end of file +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 88, 574, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 3, 2, 7, 2, 82, 10, 2, 12, 2, 14, 2, 85, 11, 2, 3, 2, 7, 2, 88, 10, 2, 12, 2, 14, 2, 91, 11, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 107, 10, 4, 12, 4, 14, 4, 110, 11, 4, 5, 4, 112, 10, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 120, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 130, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 138, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 143, 10, 6, 3, 6, 3, 6, 5, 6, 147, 10, 6, 3, 6, 3, 6, 5, 6, 151, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 156, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 6, 6, 178, 10, 6, 13, 6, 14, 6, 179, 5, 6, 182, 10, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 196, 10, 7, 3, 7, 3, 7, 3, 7, 5, 7, 201, 10, 7, 3, 8, 3, 8, 5, 8, 205, 10, 8, 3, 9, 3, 9, 7, 9, 209, 10, 9, 12, 9, 14, 9, 212, 11, 9, 3, 9, 5, 9, 215, 10, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 5, 11, 223, 10, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 13, 7, 13, 231, 10, 13, 12, 13, 14, 13, 234, 11, 13, 3, 14, 3, 14, 3, 14, 7, 14, 239, 10, 14, 12, 14, 14, 14, 242, 11, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 7, 15, 249, 10, 15, 12, 15, 14, 15, 252, 11, 15, 5, 15, 254, 10, 15, 3, 16, 3, 16, 3, 16, 5, 16, 259, 10, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 7, 18, 310, 10, 18, 12, 18, 14, 18, 313, 11, 18, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 3, 19, 5, 19, 326, 10, 19, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 5, 20, 333, 10, 20, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 5, 21, 342, 10, 21, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 5, 22, 354, 10, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 6, 24, 361, 10, 24, 13, 24, 14, 24, 362, 3, 24, 3, 24, 3, 24, 6, 24, 368, 10, 24, 13, 24, 14, 24, 369, 3, 24, 3, 24, 3, 24, 7, 24, 375, 10, 24, 12, 24, 14, 24, 378, 11, 24, 3, 24, 3, 24, 7, 24, 382, 10, 24, 12, 24, 14, 24, 385, 11, 24, 5, 24, 387, 10, 24, 3, 25, 3, 25, 7, 25, 391, 10, 25, 12, 25, 14, 25, 394, 11, 25, 3, 25, 5, 25, 397, 10, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 5, 26, 418, 10, 26, 3, 27, 3, 27, 3, 27, 5, 27, 423, 10, 27, 3, 28, 3, 28, 5, 28, 427, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 6, 32, 446, 10, 32, 13, 32, 14, 32, 447, 3, 32, 3, 32, 7, 32, 452, 10, 32, 12, 32, 14, 32, 455, 11, 32, 5, 32, 457, 10, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 7, 32, 467, 10, 32, 12, 32, 14, 32, 470, 11, 32, 5, 32, 472, 10, 32, 3, 32, 3, 32, 7, 32, 476, 10, 32, 12, 32, 14, 32, 479, 11, 32, 5, 32, 481, 10, 32, 3, 33, 3, 33, 3, 33, 3, 33, 7, 33, 487, 10, 33, 12, 33, 14, 33, 490, 11, 33, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 496, 10, 33, 3, 34, 3, 34, 3, 34, 3, 34, 7, 34, 502, 10, 34, 12, 34, 14, 34, 505, 11, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 5, 34, 512, 10, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36, 3, 36, 7, 36, 522, 10, 36, 12, 36, 14, 36, 525, 11, 36, 5, 36, 527, 10, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 5, 37, 534, 10, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 541, 10, 38, 12, 38, 14, 38, 544, 11, 38, 5, 38, 546, 10, 38, 3, 38, 5, 38, 549, 10, 38, 3, 38, 3, 38, 3, 38, 5, 38, 554, 10, 38, 3, 39, 5, 39, 557, 10, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 5, 40, 572, 10, 40, 3, 40, 2, 2, 3, 34, 41, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 22, 2, 24, 2, 26, 2, 28, 2, 30, 2, 32, 2, 34, 2, 36, 2, 38, 2, 40, 2, 42, 2, 44, 2, 46, 2, 48, 2, 50, 2, 52, 2, 54, 2, 56, 2, 58, 2, 60, 2, 62, 2, 64, 2, 66, 2, 68, 2, 70, 2, 72, 2, 74, 2, 76, 2, 78, 2, 2, 17, 3, 3, 15, 15, 3, 2, 33, 35, 3, 2, 36, 37, 3, 2, 59, 60, 3, 2, 38, 40, 3, 2, 41, 44, 3, 2, 45, 48, 3, 2, 63, 74, 3, 2, 61, 62, 3, 2, 31, 32, 3, 2, 84, 85, 3, 2, 75, 78, 4, 2, 11, 11, 86, 86, 3, 2, 12, 13, 3, 2, 87, 88, 2, 633, 2, 83, 3, 2, 2, 2, 4, 94, 3, 2, 2, 2, 6, 99, 3, 2, 2, 2, 8, 119, 3, 2, 2, 2, 10, 181, 3, 2, 2, 2, 12, 200, 3, 2, 2, 2, 14, 204, 3, 2, 2, 2, 16, 206, 3, 2, 2, 2, 18, 218, 3, 2, 2, 2, 20, 222, 3, 2, 2, 2, 22, 224, 3, 2, 2, 2, 24, 226, 3, 2, 2, 2, 26, 235, 3, 2, 2, 2, 28, 253, 3, 2, 2, 2, 30, 255, 3, 2, 2, 2, 32, 260, 3, 2, 2, 2, 34, 267, 3, 2, 2, 2, 36, 325, 3, 2, 2, 2, 38, 332, 3, 2, 2, 2, 40, 341, 3, 2, 2, 2, 42, 353, 3, 2, 2, 2, 44, 355, 3, 2, 2, 2, 46, 386, 3, 2, 2, 2, 48, 396, 3, 2, 2, 2, 50, 417, 3, 2, 2, 2, 52, 422, 3, 2, 2, 2, 54, 426, 3, 2, 2, 2, 56, 428, 3, 2, 2, 2, 58, 432, 3, 2, 2, 2, 60, 435, 3, 2, 2, 2, 62, 480, 3, 2, 2, 2, 64, 495, 3, 2, 2, 2, 66, 511, 3, 2, 2, 2, 68, 513, 3, 2, 2, 2, 70, 517, 3, 2, 2, 2, 72, 533, 3, 2, 2, 2, 74, 548, 3, 2, 2, 2, 76, 556, 3, 2, 2, 2, 78, 571, 3, 2, 2, 2, 80, 82, 5, 4, 3, 2, 81, 80, 3, 2, 2, 2, 82, 85, 3, 2, 2, 2, 83, 81, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 89, 3, 2, 2, 2, 85, 83, 3, 2, 2, 2, 86, 88, 5, 8, 5, 2, 87, 86, 3, 2, 2, 2, 88, 91, 3, 2, 2, 2, 89, 87, 3, 2, 2, 2, 89, 90, 3, 2, 2, 2, 90, 92, 3, 2, 2, 2, 91, 89, 3, 2, 2, 2, 92, 93, 7, 2, 2, 3, 93, 3, 3, 2, 2, 2, 94, 95, 5, 26, 14, 2, 95, 96, 7, 86, 2, 2, 96, 97, 5, 6, 4, 2, 97, 98, 5, 16, 9, 2, 98, 5, 3, 2, 2, 2, 99, 111, 7, 9, 2, 2, 100, 101, 5, 26, 14, 2, 101, 108, 7, 86, 2, 2, 102, 103, 7, 14, 2, 2, 103, 104, 5, 26, 14, 2, 104, 105, 7, 86, 2, 2, 105, 107, 3, 2, 2, 2, 106, 102, 3, 2, 2, 2, 107, 110, 3, 2, 2, 2, 108, 106, 3, 2, 2, 2, 108, 109, 3, 2, 2, 2, 109, 112, 3, 2, 2, 2, 110, 108, 3, 2, 2, 2, 111, 100, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 114, 7, 10, 2, 2, 114, 7, 3, 2, 2, 2, 115, 120, 5, 10, 6, 2, 116, 117, 5, 12, 7, 2, 117, 118, 9, 2, 2, 2, 118, 120, 3, 2, 2, 2, 119, 115, 3, 2, 2, 2, 119, 116, 3, 2, 2, 2, 120, 9, 3, 2, 2, 2, 121, 122, 7, 16, 2, 2, 122, 123, 7, 9, 2, 2, 123, 124, 5, 36, 19, 2, 124, 125, 7, 10, 2, 2, 125, 129, 5, 14, 8, 2, 126, 127, 7, 18, 2, 2, 127, 130, 5, 14, 8, 2, 128, 130, 6, 6, 2, 2, 129, 126, 3, 2, 2, 2, 129, 128, 3, 2, 2, 2, 130, 182, 3, 2, 2, 2, 131, 132, 7, 19, 2, 2, 132, 133, 7, 9, 2, 2, 133, 134, 5, 36, 19, 2, 134, 137, 7, 10, 2, 2, 135, 138, 5, 14, 8, 2, 136, 138, 5, 18, 10, 2, 137, 135, 3, 2, 2, 2, 137, 136, 3, 2, 2, 2, 138, 182, 3, 2, 2, 2, 139, 140, 7, 21, 2, 2, 140, 142, 7, 9, 2, 2, 141, 143, 5, 20, 11, 2, 142, 141, 3, 2, 2, 2, 142, 143, 3, 2, 2, 2, 143, 144, 3, 2, 2, 2, 144, 146, 7, 15, 2, 2, 145, 147, 5, 36, 19, 2, 146, 145, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 148, 3, 2, 2, 2, 148, 150, 7, 15, 2, 2, 149, 151, 5, 22, 12, 2, 150, 149, 3, 2, 2, 2, 150, 151, 3, 2, 2, 2, 151, 152, 3, 2, 2, 2, 152, 155, 7, 10, 2, 2, 153, 156, 5, 14, 8, 2, 154, 156, 5, 18, 10, 2, 155, 153, 3, 2, 2, 2, 155, 154, 3, 2, 2, 2, 156, 182, 3, 2, 2, 2, 157, 158, 7, 21, 2, 2, 158, 159, 7, 9, 2, 2, 159, 160, 5, 26, 14, 2, 160, 161, 7, 86, 2, 2, 161, 162, 7, 55, 2, 2, 162, 163, 5, 36, 19, 2, 163, 164, 7, 10, 2, 2, 164, 165, 5, 14, 8, 2, 165, 182, 3, 2, 2, 2, 166, 167, 7, 21, 2, 2, 167, 168, 7, 9, 2, 2, 168, 169, 7, 86, 2, 2, 169, 170, 7, 17, 2, 2, 170, 171, 5, 36, 19, 2, 171, 172, 7, 10, 2, 2, 172, 173, 5, 14, 8, 2, 173, 182, 3, 2, 2, 2, 174, 175, 7, 26, 2, 2, 175, 177, 5, 16, 9, 2, 176, 178, 5, 32, 17, 2, 177, 176, 3, 2, 2, 2, 178, 179, 3, 2, 2, 2, 179, 177, 3, 2, 2, 2, 179, 180, 3, 2, 2, 2, 180, 182, 3, 2, 2, 2, 181, 121, 3, 2, 2, 2, 181, 131, 3, 2, 2, 2, 181, 139, 3, 2, 2, 2, 181, 157, 3, 2, 2, 2, 181, 166, 3, 2, 2, 2, 181, 174, 3, 2, 2, 2, 182, 11, 3, 2, 2, 2, 183, 184, 7, 20, 2, 2, 184, 185, 5, 16, 9, 2, 185, 186, 7, 19, 2, 2, 186, 187, 7, 9, 2, 2, 187, 188, 5, 36, 19, 2, 188, 189, 7, 10, 2, 2, 189, 201, 3, 2, 2, 2, 190, 201, 5, 24, 13, 2, 191, 201, 7, 22, 2, 2, 192, 201, 7, 23, 2, 2, 193, 195, 7, 24, 2, 2, 194, 196, 5, 36, 19, 2, 195, 194, 3, 2, 2, 2, 195, 196, 3, 2, 2, 2, 196, 201, 3, 2, 2, 2, 197, 198, 7, 28, 2, 2, 198, 201, 5, 36, 19, 2, 199, 201, 5, 36, 19, 2, 200, 183, 3, 2, 2, 2, 200, 190, 3, 2, 2, 2, 200, 191, 3, 2, 2, 2, 200, 192, 3, 2, 2, 2, 200, 193, 3, 2, 2, 2, 200, 197, 3, 2, 2, 2, 200, 199, 3, 2, 2, 2, 201, 13, 3, 2, 2, 2, 202, 205, 5, 16, 9, 2, 203, 205, 5, 8, 5, 2, 204, 202, 3, 2, 2, 2, 204, 203, 3, 2, 2, 2, 205, 15, 3, 2, 2, 2, 206, 210, 7, 5, 2, 2, 207, 209, 5, 8, 5, 2, 208, 207, 3, 2, 2, 2, 209, 212, 3, 2, 2, 2, 210, 208, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2, 211, 214, 3, 2, 2, 2, 212, 210, 3, 2, 2, 2, 213, 215, 5, 12, 7, 2, 214, 213, 3, 2, 2, 2, 214, 215, 3, 2, 2, 2, 215, 216, 3, 2, 2, 2, 216, 217, 7, 6, 2, 2, 217, 17, 3, 2, 2, 2, 218, 219, 7, 15, 2, 2, 219, 19, 3, 2, 2, 2, 220, 223, 5, 24, 13, 2, 221, 223, 5, 36, 19, 2, 222, 220, 3, 2, 2, 2, 222, 221, 3, 2, 2, 2, 223, 21, 3, 2, 2, 2, 224, 225, 5, 36, 19, 2, 225, 23, 3, 2, 2, 2, 226, 227, 5, 26, 14, 2, 227, 232, 5, 30, 16, 2, 228, 229, 7, 14, 2, 2, 229, 231, 5, 30, 16, 2, 230, 228, 3, 2, 2, 2, 231, 234, 3, 2, 2, 2, 232, 230, 3, 2, 2, 2, 232, 233, 3, 2, 2, 2, 233, 25, 3, 2, 2, 2, 234, 232, 3, 2, 2, 2, 235, 240, 5, 28, 15, 2, 236, 237, 7, 7, 2, 2, 237, 239, 7, 8, 2, 2, 238, 236, 3, 2, 2, 2, 239, 242, 3, 2, 2, 2, 240, 238, 3, 2, 2, 2, 240, 241, 3, 2, 2, 2, 241, 27, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 243, 254, 7, 85, 2, 2, 244, 254, 7, 84, 2, 2, 245, 250, 7, 86, 2, 2, 246, 247, 7, 12, 2, 2, 247, 249, 7, 88, 2, 2, 248, 246, 3, 2, 2, 2, 249, 252, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 250, 251, 3, 2, 2, 2, 251, 254, 3, 2, 2, 2, 252, 250, 3, 2, 2, 2, 253, 243, 3, 2, 2, 2, 253, 244, 3, 2, 2, 2, 253, 245, 3, 2, 2, 2, 254, 29, 3, 2, 2, 2, 255, 258, 7, 86, 2, 2, 256, 257, 7, 63, 2, 2, 257, 259, 5, 36, 19, 2, 258, 256, 3, 2, 2, 2, 258, 259, 3, 2, 2, 2, 259, 31, 3, 2, 2, 2, 260, 261, 7, 27, 2, 2, 261, 262, 7, 9, 2, 2, 262, 263, 5, 28, 15, 2, 263, 264, 7, 86, 2, 2, 264, 265, 7, 10, 2, 2, 265, 266, 5, 16, 9, 2, 266, 33, 3, 2, 2, 2, 267, 268, 8, 18, 1, 2, 268, 269, 5, 38, 20, 2, 269, 311, 3, 2, 2, 2, 270, 271, 12, 15, 2, 2, 271, 272, 9, 3, 2, 2, 272, 310, 5, 34, 18, 16, 273, 274, 12, 14, 2, 2, 274, 275, 9, 4, 2, 2, 275, 310, 5, 34, 18, 15, 276, 277, 12, 13, 2, 2, 277, 278, 9, 5, 2, 2, 278, 310, 5, 34, 18, 14, 279, 280, 12, 12, 2, 2, 280, 281, 9, 6, 2, 2, 281, 310, 5, 34, 18, 13, 282, 283, 12, 11, 2, 2, 283, 284, 9, 7, 2, 2, 284, 310, 5, 34, 18, 12, 285, 286, 12, 9, 2, 2, 286, 287, 9, 8, 2, 2, 287, 310, 5, 34, 18, 10, 288, 289, 12, 8, 2, 2, 289, 290, 7, 49, 2, 2, 290, 310, 5, 34, 18, 9, 291, 292, 12, 7, 2, 2, 292, 293, 7, 50, 2, 2, 293, 310, 5, 34, 18, 8, 294, 295, 12, 6, 2, 2, 295, 296, 7, 51, 2, 2, 296, 310, 5, 34, 18, 7, 297, 298, 12, 5, 2, 2, 298, 299, 7, 52, 2, 2, 299, 310, 5, 34, 18, 6, 300, 301, 12, 4, 2, 2, 301, 302, 7, 53, 2, 2, 302, 310, 5, 34, 18, 5, 303, 304, 12, 3, 2, 2, 304, 305, 7, 56, 2, 2, 305, 310, 5, 34, 18, 3, 306, 307, 12, 10, 2, 2, 307, 308, 7, 30, 2, 2, 308, 310, 5, 26, 14, 2, 309, 270, 3, 2, 2, 2, 309, 273, 3, 2, 2, 2, 309, 276, 3, 2, 2, 2, 309, 279, 3, 2, 2, 2, 309, 282, 3, 2, 2, 2, 309, 285, 3, 2, 2, 2, 309, 288, 3, 2, 2, 2, 309, 291, 3, 2, 2, 2, 309, 294, 3, 2, 2, 2, 309, 297, 3, 2, 2, 2, 309, 300, 3, 2, 2, 2, 309, 303, 3, 2, 2, 2, 309, 306, 3, 2, 2, 2, 310, 313, 3, 2, 2, 2, 311, 309, 3, 2, 2, 2, 311, 312, 3, 2, 2, 2, 312, 35, 3, 2, 2, 2, 313, 311, 3, 2, 2, 2, 314, 326, 5, 34, 18, 2, 315, 316, 5, 34, 18, 2, 316, 317, 7, 54, 2, 2, 317, 318, 5, 36, 19, 2, 318, 319, 7, 55, 2, 2, 319, 320, 5, 36, 19, 2, 320, 326, 3, 2, 2, 2, 321, 322, 5, 34, 18, 2, 322, 323, 9, 9, 2, 2, 323, 324, 5, 36, 19, 2, 324, 326, 3, 2, 2, 2, 325, 314, 3, 2, 2, 2, 325, 315, 3, 2, 2, 2, 325, 321, 3, 2, 2, 2, 326, 37, 3, 2, 2, 2, 327, 328, 9, 10, 2, 2, 328, 333, 5, 48, 25, 2, 329, 330, 9, 4, 2, 2, 330, 333, 5, 38, 20, 2, 331, 333, 5, 40, 21, 2, 332, 327, 3, 2, 2, 2, 332, 329, 3, 2, 2, 2, 332, 331, 3, 2, 2, 2, 333, 39, 3, 2, 2, 2, 334, 342, 5, 48, 25, 2, 335, 336, 5, 48, 25, 2, 336, 337, 9, 10, 2, 2, 337, 342, 3, 2, 2, 2, 338, 339, 9, 11, 2, 2, 339, 342, 5, 38, 20, 2, 340, 342, 5, 42, 22, 2, 341, 334, 3, 2, 2, 2, 341, 335, 3, 2, 2, 2, 341, 338, 3, 2, 2, 2, 341, 340, 3, 2, 2, 2, 342, 41, 3, 2, 2, 2, 343, 344, 7, 9, 2, 2, 344, 345, 5, 44, 23, 2, 345, 346, 7, 10, 2, 2, 346, 347, 5, 38, 20, 2, 347, 354, 3, 2, 2, 2, 348, 349, 7, 9, 2, 2, 349, 350, 5, 46, 24, 2, 350, 351, 7, 10, 2, 2, 351, 352, 5, 40, 21, 2, 352, 354, 3, 2, 2, 2, 353, 343, 3, 2, 2, 2, 353, 348, 3, 2, 2, 2, 354, 43, 3, 2, 2, 2, 355, 356, 9, 12, 2, 2, 356, 45, 3, 2, 2, 2, 357, 360, 7, 85, 2, 2, 358, 359, 7, 7, 2, 2, 359, 361, 7, 8, 2, 2, 360, 358, 3, 2, 2, 2, 361, 362, 3, 2, 2, 2, 362, 360, 3, 2, 2, 2, 362, 363, 3, 2, 2, 2, 363, 387, 3, 2, 2, 2, 364, 367, 7, 84, 2, 2, 365, 366, 7, 7, 2, 2, 366, 368, 7, 8, 2, 2, 367, 365, 3, 2, 2, 2, 368, 369, 3, 2, 2, 2, 369, 367, 3, 2, 2, 2, 369, 370, 3, 2, 2, 2, 370, 387, 3, 2, 2, 2, 371, 376, 7, 86, 2, 2, 372, 373, 7, 12, 2, 2, 373, 375, 7, 88, 2, 2, 374, 372, 3, 2, 2, 2, 375, 378, 3, 2, 2, 2, 376, 374, 3, 2, 2, 2, 376, 377, 3, 2, 2, 2, 377, 383, 3, 2, 2, 2, 378, 376, 3, 2, 2, 2, 379, 380, 7, 7, 2, 2, 380, 382, 7, 8, 2, 2, 381, 379, 3, 2, 2, 2, 382, 385, 3, 2, 2, 2, 383, 381, 3, 2, 2, 2, 383, 384, 3, 2, 2, 2, 384, 387, 3, 2, 2, 2, 385, 383, 3, 2, 2, 2, 386, 357, 3, 2, 2, 2, 386, 364, 3, 2, 2, 2, 386, 371, 3, 2, 2, 2, 387, 47, 3, 2, 2, 2, 388, 392, 5, 50, 26, 2, 389, 391, 5, 52, 27, 2, 390, 389, 3, 2, 2, 2, 391, 394, 3, 2, 2, 2, 392, 390, 3, 2, 2, 2, 392, 393, 3, 2, 2, 2, 393, 397, 3, 2, 2, 2, 394, 392, 3, 2, 2, 2, 395, 397, 5, 62, 32, 2, 396, 388, 3, 2, 2, 2, 396, 395, 3, 2, 2, 2, 397, 49, 3, 2, 2, 2, 398, 399, 7, 9, 2, 2, 399, 400, 5, 36, 19, 2, 400, 401, 7, 10, 2, 2, 401, 418, 3, 2, 2, 2, 402, 418, 9, 13, 2, 2, 403, 418, 7, 81, 2, 2, 404, 418, 7, 82, 2, 2, 405, 418, 7, 83, 2, 2, 406, 418, 7, 79, 2, 2, 407, 418, 7, 80, 2, 2, 408, 418, 5, 64, 33, 2, 409, 418, 5, 66, 34, 2, 410, 418, 7, 86, 2, 2, 411, 412, 9, 14, 2, 2, 412, 418, 5, 70, 36, 2, 413, 414, 7, 25, 2, 2, 414, 415, 5, 28, 15, 2, 415, 416, 5, 70, 36, 2, 416, 418, 3, 2, 2, 2, 417, 398, 3, 2, 2, 2, 417, 402, 3, 2, 2, 2, 417, 403, 3, 2, 2, 2, 417, 404, 3, 2, 2, 2, 417, 405, 3, 2, 2, 2, 417, 406, 3, 2, 2, 2, 417, 407, 3, 2, 2, 2, 417, 408, 3, 2, 2, 2, 417, 409, 3, 2, 2, 2, 417, 410, 3, 2, 2, 2, 417, 411, 3, 2, 2, 2, 417, 413, 3, 2, 2, 2, 418, 51, 3, 2, 2, 2, 419, 423, 5, 56, 29, 2, 420, 423, 5, 58, 30, 2, 421, 423, 5, 60, 31, 2, 422, 419, 3, 2, 2, 2, 422, 420, 3, 2, 2, 2, 422, 421, 3, 2, 2, 2, 423, 53, 3, 2, 2, 2, 424, 427, 5, 56, 29, 2, 425, 427, 5, 58, 30, 2, 426, 424, 3, 2, 2, 2, 426, 425, 3, 2, 2, 2, 427, 55, 3, 2, 2, 2, 428, 429, 9, 15, 2, 2, 429, 430, 7, 88, 2, 2, 430, 431, 5, 70, 36, 2, 431, 57, 3, 2, 2, 2, 432, 433, 9, 15, 2, 2, 433, 434, 9, 16, 2, 2, 434, 59, 3, 2, 2, 2, 435, 436, 7, 7, 2, 2, 436, 437, 5, 36, 19, 2, 437, 438, 7, 8, 2, 2, 438, 61, 3, 2, 2, 2, 439, 440, 7, 25, 2, 2, 440, 445, 5, 28, 15, 2, 441, 442, 7, 7, 2, 2, 442, 443, 5, 36, 19, 2, 443, 444, 7, 8, 2, 2, 444, 446, 3, 2, 2, 2, 445, 441, 3, 2, 2, 2, 446, 447, 3, 2, 2, 2, 447, 445, 3, 2, 2, 2, 447, 448, 3, 2, 2, 2, 448, 456, 3, 2, 2, 2, 449, 453, 5, 54, 28, 2, 450, 452, 5, 52, 27, 2, 451, 450, 3, 2, 2, 2, 452, 455, 3, 2, 2, 2, 453, 451, 3, 2, 2, 2, 453, 454, 3, 2, 2, 2, 454, 457, 3, 2, 2, 2, 455, 453, 3, 2, 2, 2, 456, 449, 3, 2, 2, 2, 456, 457, 3, 2, 2, 2, 457, 481, 3, 2, 2, 2, 458, 459, 7, 25, 2, 2, 459, 460, 5, 28, 15, 2, 460, 461, 7, 7, 2, 2, 461, 462, 7, 8, 2, 2, 462, 471, 7, 5, 2, 2, 463, 468, 5, 36, 19, 2, 464, 465, 7, 14, 2, 2, 465, 467, 5, 36, 19, 2, 466, 464, 3, 2, 2, 2, 467, 470, 3, 2, 2, 2, 468, 466, 3, 2, 2, 2, 468, 469, 3, 2, 2, 2, 469, 472, 3, 2, 2, 2, 470, 468, 3, 2, 2, 2, 471, 463, 3, 2, 2, 2, 471, 472, 3, 2, 2, 2, 472, 473, 3, 2, 2, 2, 473, 477, 7, 6, 2, 2, 474, 476, 5, 52, 27, 2, 475, 474, 3, 2, 2, 2, 476, 479, 3, 2, 2, 2, 477, 475, 3, 2, 2, 2, 477, 478, 3, 2, 2, 2, 478, 481, 3, 2, 2, 2, 479, 477, 3, 2, 2, 2, 480, 439, 3, 2, 2, 2, 480, 458, 3, 2, 2, 2, 481, 63, 3, 2, 2, 2, 482, 483, 7, 7, 2, 2, 483, 488, 5, 36, 19, 2, 484, 485, 7, 14, 2, 2, 485, 487, 5, 36, 19, 2, 486, 484, 3, 2, 2, 2, 487, 490, 3, 2, 2, 2, 488, 486, 3, 2, 2, 2, 488, 489, 3, 2, 2, 2, 489, 491, 3, 2, 2, 2, 490, 488, 3, 2, 2, 2, 491, 492, 7, 8, 2, 2, 492, 496, 3, 2, 2, 2, 493, 494, 7, 7, 2, 2, 494, 496, 7, 8, 2, 2, 495, 482, 3, 2, 2, 2, 495, 493, 3, 2, 2, 2, 496, 65, 3, 2, 2, 2, 497, 498, 7, 7, 2, 2, 498, 503, 5, 68, 35, 2, 499, 500, 7, 14, 2, 2, 500, 502, 5, 68, 35, 2, 501, 499, 3, 2, 2, 2, 502, 505, 3, 2, 2, 2, 503, 501, 3, 2, 2, 2, 503, 504, 3, 2, 2, 2, 504, 506, 3, 2, 2, 2, 505, 503, 3, 2, 2, 2, 506, 507, 7, 8, 2, 2, 507, 512, 3, 2, 2, 2, 508, 509, 7, 7, 2, 2, 509, 510, 7, 55, 2, 2, 510, 512, 7, 8, 2, 2, 511, 497, 3, 2, 2, 2, 511, 508, 3, 2, 2, 2, 512, 67, 3, 2, 2, 2, 513, 514, 5, 36, 19, 2, 514, 515, 7, 55, 2, 2, 515, 516, 5, 36, 19, 2, 516, 69, 3, 2, 2, 2, 517, 526, 7, 9, 2, 2, 518, 523, 5, 72, 37, 2, 519, 520, 7, 14, 2, 2, 520, 522, 5, 72, 37, 2, 521, 519, 3, 2, 2, 2, 522, 525, 3, 2, 2, 2, 523, 521, 3, 2, 2, 2, 523, 524, 3, 2, 2, 2, 524, 527, 3, 2, 2, 2, 525, 523, 3, 2, 2, 2, 526, 518, 3, 2, 2, 2, 526, 527, 3, 2, 2, 2, 527, 528, 3, 2, 2, 2, 528, 529, 7, 10, 2, 2, 529, 71, 3, 2, 2, 2, 530, 534, 5, 36, 19, 2, 531, 534, 5, 74, 38, 2, 532, 534, 5, 78, 40, 2, 533, 530, 3, 2, 2, 2, 533, 531, 3, 2, 2, 2, 533, 532, 3, 2, 2, 2, 534, 73, 3, 2, 2, 2, 535, 549, 5, 76, 39, 2, 536, 545, 7, 9, 2, 2, 537, 542, 5, 76, 39, 2, 538, 539, 7, 14, 2, 2, 539, 541, 5, 76, 39, 2, 540, 538, 3, 2, 2, 2, 541, 544, 3, 2, 2, 2, 542, 540, 3, 2, 2, 2, 542, 543, 3, 2, 2, 2, 543, 546, 3, 2, 2, 2, 544, 542, 3, 2, 2, 2, 545, 537, 3, 2, 2, 2, 545, 546, 3, 2, 2, 2, 546, 547, 3, 2, 2, 2, 547, 549, 7, 10, 2, 2, 548, 535, 3, 2, 2, 2, 548, 536, 3, 2, 2, 2, 549, 550, 3, 2, 2, 2, 550, 553, 7, 58, 2, 2, 551, 554, 5, 16, 9, 2, 552, 554, 5, 36, 19, 2, 553, 551, 3, 2, 2, 2, 553, 552, 3, 2, 2, 2, 554, 75, 3, 2, 2, 2, 555, 557, 5, 26, 14, 2, 556, 555, 3, 2, 2, 2, 556, 557, 3, 2, 2, 2, 557, 558, 3, 2, 2, 2, 558, 559, 7, 86, 2, 2, 559, 77, 3, 2, 2, 2, 560, 561, 5, 26, 14, 2, 561, 562, 7, 57, 2, 2, 562, 563, 7, 86, 2, 2, 563, 572, 3, 2, 2, 2, 564, 565, 5, 26, 14, 2, 565, 566, 7, 57, 2, 2, 566, 567, 7, 25, 2, 2, 567, 572, 3, 2, 2, 2, 568, 569, 7, 29, 2, 2, 569, 570, 7, 57, 2, 2, 570, 572, 7, 86, 2, 2, 571, 560, 3, 2, 2, 2, 571, 564, 3, 2, 2, 2, 571, 568, 3, 2, 2, 2, 572, 79, 3, 2, 2, 2, 62, 83, 89, 108, 111, 119, 129, 137, 142, 146, 150, 155, 179, 181, 195, 200, 204, 210, 214, 222, 232, 240, 250, 253, 258, 309, 311, 325, 332, 341, 353, 362, 369, 376, 383, 386, 392, 396, 417, 422, 426, 447, 453, 456, 468, 471, 477, 480, 488, 495, 503, 511, 523, 526, 533, 542, 545, 548, 553, 556, 571] \ No newline at end of file diff --git a/packages/kbn-monaco/src/painless/antlr/painless_parser.tokens b/packages/kbn-monaco/src/painless/antlr/painless_parser.tokens index ff62343c92ba5..3a1af2a5154d3 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_parser.tokens +++ b/packages/kbn-monaco/src/painless/antlr/painless_parser.tokens @@ -6,153 +6,155 @@ LBRACE=5 RBRACE=6 LP=7 RP=8 -DOT=9 -NSDOT=10 -COMMA=11 -SEMICOLON=12 -IF=13 -IN=14 -ELSE=15 -WHILE=16 -DO=17 -FOR=18 -CONTINUE=19 -BREAK=20 -RETURN=21 -NEW=22 -TRY=23 -CATCH=24 -THROW=25 -THIS=26 -INSTANCEOF=27 -BOOLNOT=28 -BWNOT=29 -MUL=30 -DIV=31 -REM=32 -ADD=33 -SUB=34 -LSH=35 -RSH=36 -USH=37 -LT=38 -LTE=39 -GT=40 -GTE=41 -EQ=42 -EQR=43 -NE=44 -NER=45 -BWAND=46 -XOR=47 -BWOR=48 -BOOLAND=49 -BOOLOR=50 -COND=51 -COLON=52 -ELVIS=53 -REF=54 -ARROW=55 -FIND=56 -MATCH=57 -INCR=58 -DECR=59 -ASSIGN=60 -AADD=61 -ASUB=62 -AMUL=63 -ADIV=64 -AREM=65 -AAND=66 -AXOR=67 -AOR=68 -ALSH=69 -ARSH=70 -AUSH=71 -OCTAL=72 -HEX=73 -INTEGER=74 -DECIMAL=75 -STRING=76 -REGEX=77 -TRUE=78 -FALSE=79 -NULL=80 -PRIMITIVE=81 -DEF=82 -ID=83 -DOTINTEGER=84 -DOTID=85 +DOLLAR=9 +DOT=10 +NSDOT=11 +COMMA=12 +SEMICOLON=13 +IF=14 +IN=15 +ELSE=16 +WHILE=17 +DO=18 +FOR=19 +CONTINUE=20 +BREAK=21 +RETURN=22 +NEW=23 +TRY=24 +CATCH=25 +THROW=26 +THIS=27 +INSTANCEOF=28 +BOOLNOT=29 +BWNOT=30 +MUL=31 +DIV=32 +REM=33 +ADD=34 +SUB=35 +LSH=36 +RSH=37 +USH=38 +LT=39 +LTE=40 +GT=41 +GTE=42 +EQ=43 +EQR=44 +NE=45 +NER=46 +BWAND=47 +XOR=48 +BWOR=49 +BOOLAND=50 +BOOLOR=51 +COND=52 +COLON=53 +ELVIS=54 +REF=55 +ARROW=56 +FIND=57 +MATCH=58 +INCR=59 +DECR=60 +ASSIGN=61 +AADD=62 +ASUB=63 +AMUL=64 +ADIV=65 +AREM=66 +AAND=67 +AXOR=68 +AOR=69 +ALSH=70 +ARSH=71 +AUSH=72 +OCTAL=73 +HEX=74 +INTEGER=75 +DECIMAL=76 +STRING=77 +REGEX=78 +TRUE=79 +FALSE=80 +NULL=81 +PRIMITIVE=82 +DEF=83 +ID=84 +DOTINTEGER=85 +DOTID=86 '{'=3 '}'=4 '['=5 ']'=6 '('=7 ')'=8 -'.'=9 -'?.'=10 -','=11 -';'=12 -'if'=13 -'in'=14 -'else'=15 -'while'=16 -'do'=17 -'for'=18 -'continue'=19 -'break'=20 -'return'=21 -'new'=22 -'try'=23 -'catch'=24 -'throw'=25 -'this'=26 -'instanceof'=27 -'!'=28 -'~'=29 -'*'=30 -'/'=31 -'%'=32 -'+'=33 -'-'=34 -'<<'=35 -'>>'=36 -'>>>'=37 -'<'=38 -'<='=39 -'>'=40 -'>='=41 -'=='=42 -'==='=43 -'!='=44 -'!=='=45 -'&'=46 -'^'=47 -'|'=48 -'&&'=49 -'||'=50 -'?'=51 -':'=52 -'?:'=53 -'::'=54 -'->'=55 -'=~'=56 -'==~'=57 -'++'=58 -'--'=59 -'='=60 -'+='=61 -'-='=62 -'*='=63 -'/='=64 -'%='=65 -'&='=66 -'^='=67 -'|='=68 -'<<='=69 -'>>='=70 -'>>>='=71 -'true'=78 -'false'=79 -'null'=80 -'def'=82 +'$'=9 +'.'=10 +'?.'=11 +','=12 +';'=13 +'if'=14 +'in'=15 +'else'=16 +'while'=17 +'do'=18 +'for'=19 +'continue'=20 +'break'=21 +'return'=22 +'new'=23 +'try'=24 +'catch'=25 +'throw'=26 +'this'=27 +'instanceof'=28 +'!'=29 +'~'=30 +'*'=31 +'/'=32 +'%'=33 +'+'=34 +'-'=35 +'<<'=36 +'>>'=37 +'>>>'=38 +'<'=39 +'<='=40 +'>'=41 +'>='=42 +'=='=43 +'==='=44 +'!='=45 +'!=='=46 +'&'=47 +'^'=48 +'|'=49 +'&&'=50 +'||'=51 +'?'=52 +':'=53 +'?:'=54 +'::'=55 +'->'=56 +'=~'=57 +'==~'=58 +'++'=59 +'--'=60 +'='=61 +'+='=62 +'-='=63 +'*='=64 +'/='=65 +'%='=66 +'&='=67 +'^='=68 +'|='=69 +'<<='=70 +'>>='=71 +'>>>='=72 +'true'=79 +'false'=80 +'null'=81 +'def'=83 diff --git a/packages/kbn-monaco/src/painless/antlr/painless_parser.ts b/packages/kbn-monaco/src/painless/antlr/painless_parser.ts index 320e310a0a9a2..760c9b2c7319a 100644 --- a/packages/kbn-monaco/src/painless/antlr/painless_parser.ts +++ b/packages/kbn-monaco/src/painless/antlr/painless_parser.ts @@ -35,83 +35,84 @@ export class painless_parser extends Parser { public static readonly RBRACE = 6; public static readonly LP = 7; public static readonly RP = 8; - public static readonly DOT = 9; - public static readonly NSDOT = 10; - public static readonly COMMA = 11; - public static readonly SEMICOLON = 12; - public static readonly IF = 13; - public static readonly IN = 14; - public static readonly ELSE = 15; - public static readonly WHILE = 16; - public static readonly DO = 17; - public static readonly FOR = 18; - public static readonly CONTINUE = 19; - public static readonly BREAK = 20; - public static readonly RETURN = 21; - public static readonly NEW = 22; - public static readonly TRY = 23; - public static readonly CATCH = 24; - public static readonly THROW = 25; - public static readonly THIS = 26; - public static readonly INSTANCEOF = 27; - public static readonly BOOLNOT = 28; - public static readonly BWNOT = 29; - public static readonly MUL = 30; - public static readonly DIV = 31; - public static readonly REM = 32; - public static readonly ADD = 33; - public static readonly SUB = 34; - public static readonly LSH = 35; - public static readonly RSH = 36; - public static readonly USH = 37; - public static readonly LT = 38; - public static readonly LTE = 39; - public static readonly GT = 40; - public static readonly GTE = 41; - public static readonly EQ = 42; - public static readonly EQR = 43; - public static readonly NE = 44; - public static readonly NER = 45; - public static readonly BWAND = 46; - public static readonly XOR = 47; - public static readonly BWOR = 48; - public static readonly BOOLAND = 49; - public static readonly BOOLOR = 50; - public static readonly COND = 51; - public static readonly COLON = 52; - public static readonly ELVIS = 53; - public static readonly REF = 54; - public static readonly ARROW = 55; - public static readonly FIND = 56; - public static readonly MATCH = 57; - public static readonly INCR = 58; - public static readonly DECR = 59; - public static readonly ASSIGN = 60; - public static readonly AADD = 61; - public static readonly ASUB = 62; - public static readonly AMUL = 63; - public static readonly ADIV = 64; - public static readonly AREM = 65; - public static readonly AAND = 66; - public static readonly AXOR = 67; - public static readonly AOR = 68; - public static readonly ALSH = 69; - public static readonly ARSH = 70; - public static readonly AUSH = 71; - public static readonly OCTAL = 72; - public static readonly HEX = 73; - public static readonly INTEGER = 74; - public static readonly DECIMAL = 75; - public static readonly STRING = 76; - public static readonly REGEX = 77; - public static readonly TRUE = 78; - public static readonly FALSE = 79; - public static readonly NULL = 80; - public static readonly PRIMITIVE = 81; - public static readonly DEF = 82; - public static readonly ID = 83; - public static readonly DOTINTEGER = 84; - public static readonly DOTID = 85; + public static readonly DOLLAR = 9; + public static readonly DOT = 10; + public static readonly NSDOT = 11; + public static readonly COMMA = 12; + public static readonly SEMICOLON = 13; + public static readonly IF = 14; + public static readonly IN = 15; + public static readonly ELSE = 16; + public static readonly WHILE = 17; + public static readonly DO = 18; + public static readonly FOR = 19; + public static readonly CONTINUE = 20; + public static readonly BREAK = 21; + public static readonly RETURN = 22; + public static readonly NEW = 23; + public static readonly TRY = 24; + public static readonly CATCH = 25; + public static readonly THROW = 26; + public static readonly THIS = 27; + public static readonly INSTANCEOF = 28; + public static readonly BOOLNOT = 29; + public static readonly BWNOT = 30; + public static readonly MUL = 31; + public static readonly DIV = 32; + public static readonly REM = 33; + public static readonly ADD = 34; + public static readonly SUB = 35; + public static readonly LSH = 36; + public static readonly RSH = 37; + public static readonly USH = 38; + public static readonly LT = 39; + public static readonly LTE = 40; + public static readonly GT = 41; + public static readonly GTE = 42; + public static readonly EQ = 43; + public static readonly EQR = 44; + public static readonly NE = 45; + public static readonly NER = 46; + public static readonly BWAND = 47; + public static readonly XOR = 48; + public static readonly BWOR = 49; + public static readonly BOOLAND = 50; + public static readonly BOOLOR = 51; + public static readonly COND = 52; + public static readonly COLON = 53; + public static readonly ELVIS = 54; + public static readonly REF = 55; + public static readonly ARROW = 56; + public static readonly FIND = 57; + public static readonly MATCH = 58; + public static readonly INCR = 59; + public static readonly DECR = 60; + public static readonly ASSIGN = 61; + public static readonly AADD = 62; + public static readonly ASUB = 63; + public static readonly AMUL = 64; + public static readonly ADIV = 65; + public static readonly AREM = 66; + public static readonly AAND = 67; + public static readonly AXOR = 68; + public static readonly AOR = 69; + public static readonly ALSH = 70; + public static readonly ARSH = 71; + public static readonly AUSH = 72; + public static readonly OCTAL = 73; + public static readonly HEX = 74; + public static readonly INTEGER = 75; + public static readonly DECIMAL = 76; + public static readonly STRING = 77; + public static readonly REGEX = 78; + public static readonly TRUE = 79; + public static readonly FALSE = 80; + public static readonly NULL = 81; + public static readonly PRIMITIVE = 82; + public static readonly DEF = 83; + public static readonly ID = 84; + public static readonly DOTINTEGER = 85; + public static readonly DOTID = 86; public static readonly RULE_source = 0; public static readonly RULE_function = 1; public static readonly RULE_parameters = 2; @@ -164,27 +165,27 @@ export class painless_parser extends Parser { private static readonly _LITERAL_NAMES: Array = [ undefined, undefined, undefined, "'{'", "'}'", "'['", "']'", "'('", "')'", - "'.'", "'?.'", "','", "';'", "'if'", "'in'", "'else'", "'while'", "'do'", - "'for'", "'continue'", "'break'", "'return'", "'new'", "'try'", "'catch'", - "'throw'", "'this'", "'instanceof'", "'!'", "'~'", "'*'", "'/'", "'%'", - "'+'", "'-'", "'<<'", "'>>'", "'>>>'", "'<'", "'<='", "'>'", "'>='", "'=='", - "'==='", "'!='", "'!=='", "'&'", "'^'", "'|'", "'&&'", "'||'", "'?'", - "':'", "'?:'", "'::'", "'->'", "'=~'", "'==~'", "'++'", "'--'", "'='", - "'+='", "'-='", "'*='", "'/='", "'%='", "'&='", "'^='", "'|='", "'<<='", - "'>>='", "'>>>='", undefined, undefined, undefined, undefined, undefined, - undefined, "'true'", "'false'", "'null'", undefined, "'def'", + "'$'", "'.'", "'?.'", "','", "';'", "'if'", "'in'", "'else'", "'while'", + "'do'", "'for'", "'continue'", "'break'", "'return'", "'new'", "'try'", + "'catch'", "'throw'", "'this'", "'instanceof'", "'!'", "'~'", "'*'", "'/'", + "'%'", "'+'", "'-'", "'<<'", "'>>'", "'>>>'", "'<'", "'<='", "'>'", "'>='", + "'=='", "'==='", "'!='", "'!=='", "'&'", "'^'", "'|'", "'&&'", "'||'", + "'?'", "':'", "'?:'", "'::'", "'->'", "'=~'", "'==~'", "'++'", "'--'", + "'='", "'+='", "'-='", "'*='", "'/='", "'%='", "'&='", "'^='", "'|='", + "'<<='", "'>>='", "'>>>='", undefined, undefined, undefined, undefined, + undefined, undefined, "'true'", "'false'", "'null'", undefined, "'def'", ]; private static readonly _SYMBOLIC_NAMES: Array = [ undefined, "WS", "COMMENT", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "LP", - "RP", "DOT", "NSDOT", "COMMA", "SEMICOLON", "IF", "IN", "ELSE", "WHILE", - "DO", "FOR", "CONTINUE", "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", - "THIS", "INSTANCEOF", "BOOLNOT", "BWNOT", "MUL", "DIV", "REM", "ADD", - "SUB", "LSH", "RSH", "USH", "LT", "LTE", "GT", "GTE", "EQ", "EQR", "NE", - "NER", "BWAND", "XOR", "BWOR", "BOOLAND", "BOOLOR", "COND", "COLON", "ELVIS", - "REF", "ARROW", "FIND", "MATCH", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", - "AMUL", "ADIV", "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", - "OCTAL", "HEX", "INTEGER", "DECIMAL", "STRING", "REGEX", "TRUE", "FALSE", - "NULL", "PRIMITIVE", "DEF", "ID", "DOTINTEGER", "DOTID", + "RP", "DOLLAR", "DOT", "NSDOT", "COMMA", "SEMICOLON", "IF", "IN", "ELSE", + "WHILE", "DO", "FOR", "CONTINUE", "BREAK", "RETURN", "NEW", "TRY", "CATCH", + "THROW", "THIS", "INSTANCEOF", "BOOLNOT", "BWNOT", "MUL", "DIV", "REM", + "ADD", "SUB", "LSH", "RSH", "USH", "LT", "LTE", "GT", "GTE", "EQ", "EQR", + "NE", "NER", "BWAND", "XOR", "BWOR", "BOOLAND", "BOOLOR", "COND", "COLON", + "ELVIS", "REF", "ARROW", "FIND", "MATCH", "INCR", "DECR", "ASSIGN", "AADD", + "ASUB", "AMUL", "ADIV", "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", + "AUSH", "OCTAL", "HEX", "INTEGER", "DECIMAL", "STRING", "REGEX", "TRUE", + "FALSE", "NULL", "PRIMITIVE", "DEF", "ID", "DOTINTEGER", "DOTID", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(painless_parser._LITERAL_NAMES, painless_parser._SYMBOLIC_NAMES, []); @@ -236,7 +237,7 @@ export class painless_parser extends Parser { this.state = 87; this._errHandler.sync(this); _la = this._input.LA(1); - while (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.IF - 5)) | (1 << (painless_parser.WHILE - 5)) | (1 << (painless_parser.DO - 5)) | (1 << (painless_parser.FOR - 5)) | (1 << (painless_parser.CONTINUE - 5)) | (1 << (painless_parser.BREAK - 5)) | (1 << (painless_parser.RETURN - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.TRY - 5)) | (1 << (painless_parser.THROW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.PRIMITIVE - 58)) | (1 << (painless_parser.DEF - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + while (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.IF - 5)) | (1 << (painless_parser.WHILE - 5)) | (1 << (painless_parser.DO - 5)) | (1 << (painless_parser.FOR - 5)) | (1 << (painless_parser.CONTINUE - 5)) | (1 << (painless_parser.BREAK - 5)) | (1 << (painless_parser.RETURN - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.TRY - 5)) | (1 << (painless_parser.THROW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.PRIMITIVE - 59)) | (1 << (painless_parser.DEF - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { { this.state = 84; @@ -309,7 +310,7 @@ export class painless_parser extends Parser { this.state = 109; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 81)) & ~0x1F) === 0 && ((1 << (_la - 81)) & ((1 << (painless_parser.PRIMITIVE - 81)) | (1 << (painless_parser.DEF - 81)) | (1 << (painless_parser.ID - 81)))) !== 0)) { + if (((((_la - 82)) & ~0x1F) === 0 && ((1 << (_la - 82)) & ((1 << (painless_parser.PRIMITIVE - 82)) | (1 << (painless_parser.DEF - 82)) | (1 << (painless_parser.ID - 82)))) !== 0)) { { this.state = 98; this.decltype(); @@ -375,6 +376,7 @@ export class painless_parser extends Parser { break; case painless_parser.LBRACE: case painless_parser.LP: + case painless_parser.DOLLAR: case painless_parser.DO: case painless_parser.CONTINUE: case painless_parser.BREAK: @@ -501,6 +503,7 @@ export class painless_parser extends Parser { case painless_parser.LBRACK: case painless_parser.LBRACE: case painless_parser.LP: + case painless_parser.DOLLAR: case painless_parser.IF: case painless_parser.WHILE: case painless_parser.DO: @@ -557,7 +560,7 @@ export class painless_parser extends Parser { this.state = 140; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.PRIMITIVE - 58)) | (1 << (painless_parser.DEF - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.PRIMITIVE - 59)) | (1 << (painless_parser.DEF - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { this.state = 139; this.initializer(); @@ -569,7 +572,7 @@ export class painless_parser extends Parser { this.state = 144; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { this.state = 143; this.expression(); @@ -581,7 +584,7 @@ export class painless_parser extends Parser { this.state = 148; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { this.state = 147; this.afterthought(); @@ -596,6 +599,7 @@ export class painless_parser extends Parser { case painless_parser.LBRACK: case painless_parser.LBRACE: case painless_parser.LP: + case painless_parser.DOLLAR: case painless_parser.IF: case painless_parser.WHILE: case painless_parser.DO: @@ -795,7 +799,7 @@ export class painless_parser extends Parser { this.state = 193; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { this.state = 192; this.expression(); @@ -857,6 +861,7 @@ export class painless_parser extends Parser { break; case painless_parser.LBRACE: case painless_parser.LP: + case painless_parser.DOLLAR: case painless_parser.IF: case painless_parser.WHILE: case painless_parser.DO: @@ -939,7 +944,7 @@ export class painless_parser extends Parser { this.state = 212; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DO - 5)) | (1 << (painless_parser.CONTINUE - 5)) | (1 << (painless_parser.BREAK - 5)) | (1 << (painless_parser.RETURN - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.THROW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.PRIMITIVE - 58)) | (1 << (painless_parser.DEF - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.DO - 5)) | (1 << (painless_parser.CONTINUE - 5)) | (1 << (painless_parser.BREAK - 5)) | (1 << (painless_parser.RETURN - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.THROW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.PRIMITIVE - 59)) | (1 << (painless_parser.DEF - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { this.state = 211; this.dstatement(); @@ -1332,7 +1337,7 @@ export class painless_parser extends Parser { } this.state = 269; _la = this._input.LA(1); - if (!(((((_la - 30)) & ~0x1F) === 0 && ((1 << (_la - 30)) & ((1 << (painless_parser.MUL - 30)) | (1 << (painless_parser.DIV - 30)) | (1 << (painless_parser.REM - 30)))) !== 0))) { + if (!(((((_la - 31)) & ~0x1F) === 0 && ((1 << (_la - 31)) & ((1 << (painless_parser.MUL - 31)) | (1 << (painless_parser.DIV - 31)) | (1 << (painless_parser.REM - 31)))) !== 0))) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -1407,7 +1412,7 @@ export class painless_parser extends Parser { } this.state = 278; _la = this._input.LA(1); - if (!(((((_la - 35)) & ~0x1F) === 0 && ((1 << (_la - 35)) & ((1 << (painless_parser.LSH - 35)) | (1 << (painless_parser.RSH - 35)) | (1 << (painless_parser.USH - 35)))) !== 0))) { + if (!(((((_la - 36)) & ~0x1F) === 0 && ((1 << (_la - 36)) & ((1 << (painless_parser.LSH - 36)) | (1 << (painless_parser.RSH - 36)) | (1 << (painless_parser.USH - 36)))) !== 0))) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -1432,7 +1437,7 @@ export class painless_parser extends Parser { } this.state = 281; _la = this._input.LA(1); - if (!(((((_la - 38)) & ~0x1F) === 0 && ((1 << (_la - 38)) & ((1 << (painless_parser.LT - 38)) | (1 << (painless_parser.LTE - 38)) | (1 << (painless_parser.GT - 38)) | (1 << (painless_parser.GTE - 38)))) !== 0))) { + if (!(((((_la - 39)) & ~0x1F) === 0 && ((1 << (_la - 39)) & ((1 << (painless_parser.LT - 39)) | (1 << (painless_parser.LTE - 39)) | (1 << (painless_parser.GT - 39)) | (1 << (painless_parser.GTE - 39)))) !== 0))) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -1457,7 +1462,7 @@ export class painless_parser extends Parser { } this.state = 284; _la = this._input.LA(1); - if (!(((((_la - 42)) & ~0x1F) === 0 && ((1 << (_la - 42)) & ((1 << (painless_parser.EQ - 42)) | (1 << (painless_parser.EQR - 42)) | (1 << (painless_parser.NE - 42)) | (1 << (painless_parser.NER - 42)))) !== 0))) { + if (!(((((_la - 43)) & ~0x1F) === 0 && ((1 << (_la - 43)) & ((1 << (painless_parser.EQ - 43)) | (1 << (painless_parser.EQR - 43)) | (1 << (painless_parser.NE - 43)) | (1 << (painless_parser.NER - 43)))) !== 0))) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -1642,7 +1647,7 @@ export class painless_parser extends Parser { this.noncondexpression(0); this.state = 320; _la = this._input.LA(1); - if (!(((((_la - 60)) & ~0x1F) === 0 && ((1 << (_la - 60)) & ((1 << (painless_parser.ASSIGN - 60)) | (1 << (painless_parser.AADD - 60)) | (1 << (painless_parser.ASUB - 60)) | (1 << (painless_parser.AMUL - 60)) | (1 << (painless_parser.ADIV - 60)) | (1 << (painless_parser.AREM - 60)) | (1 << (painless_parser.AAND - 60)) | (1 << (painless_parser.AXOR - 60)) | (1 << (painless_parser.AOR - 60)) | (1 << (painless_parser.ALSH - 60)) | (1 << (painless_parser.ARSH - 60)) | (1 << (painless_parser.AUSH - 60)))) !== 0))) { + if (!(((((_la - 61)) & ~0x1F) === 0 && ((1 << (_la - 61)) & ((1 << (painless_parser.ASSIGN - 61)) | (1 << (painless_parser.AADD - 61)) | (1 << (painless_parser.ASUB - 61)) | (1 << (painless_parser.AMUL - 61)) | (1 << (painless_parser.ADIV - 61)) | (1 << (painless_parser.AREM - 61)) | (1 << (painless_parser.AAND - 61)) | (1 << (painless_parser.AXOR - 61)) | (1 << (painless_parser.AOR - 61)) | (1 << (painless_parser.ALSH - 61)) | (1 << (painless_parser.ARSH - 61)) | (1 << (painless_parser.AUSH - 61)))) !== 0))) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -1725,6 +1730,7 @@ export class painless_parser extends Parser { break; case painless_parser.LBRACE: case painless_parser.LP: + case painless_parser.DOLLAR: case painless_parser.NEW: case painless_parser.BOOLNOT: case painless_parser.BWNOT: @@ -2134,7 +2140,7 @@ export class painless_parser extends Parser { { this.state = 400; _la = this._input.LA(1); - if (!(((((_la - 72)) & ~0x1F) === 0 && ((1 << (_la - 72)) & ((1 << (painless_parser.OCTAL - 72)) | (1 << (painless_parser.HEX - 72)) | (1 << (painless_parser.INTEGER - 72)) | (1 << (painless_parser.DECIMAL - 72)))) !== 0))) { + if (!(((((_la - 73)) & ~0x1F) === 0 && ((1 << (_la - 73)) & ((1 << (painless_parser.OCTAL - 73)) | (1 << (painless_parser.HEX - 73)) | (1 << (painless_parser.INTEGER - 73)) | (1 << (painless_parser.DECIMAL - 73)))) !== 0))) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -2224,7 +2230,17 @@ export class painless_parser extends Parser { this.enterOuterAlt(_localctx, 11); { this.state = 409; - this.match(painless_parser.ID); + _la = this._input.LA(1); + if (!(_la === painless_parser.DOLLAR || _la === painless_parser.ID)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } this.state = 410; this.arguments(); } @@ -2549,7 +2565,7 @@ export class painless_parser extends Parser { this.state = 469; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { this.state = 461; this.expression(); @@ -2777,7 +2793,7 @@ export class painless_parser extends Parser { this.state = 524; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.THIS - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 58)) & ~0x1F) === 0 && ((1 << (_la - 58)) & ((1 << (painless_parser.INCR - 58)) | (1 << (painless_parser.DECR - 58)) | (1 << (painless_parser.OCTAL - 58)) | (1 << (painless_parser.HEX - 58)) | (1 << (painless_parser.INTEGER - 58)) | (1 << (painless_parser.DECIMAL - 58)) | (1 << (painless_parser.STRING - 58)) | (1 << (painless_parser.REGEX - 58)) | (1 << (painless_parser.TRUE - 58)) | (1 << (painless_parser.FALSE - 58)) | (1 << (painless_parser.NULL - 58)) | (1 << (painless_parser.PRIMITIVE - 58)) | (1 << (painless_parser.DEF - 58)) | (1 << (painless_parser.ID - 58)))) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & ((1 << (painless_parser.LBRACE - 5)) | (1 << (painless_parser.LP - 5)) | (1 << (painless_parser.DOLLAR - 5)) | (1 << (painless_parser.NEW - 5)) | (1 << (painless_parser.THIS - 5)) | (1 << (painless_parser.BOOLNOT - 5)) | (1 << (painless_parser.BWNOT - 5)) | (1 << (painless_parser.ADD - 5)) | (1 << (painless_parser.SUB - 5)))) !== 0) || ((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & ((1 << (painless_parser.INCR - 59)) | (1 << (painless_parser.DECR - 59)) | (1 << (painless_parser.OCTAL - 59)) | (1 << (painless_parser.HEX - 59)) | (1 << (painless_parser.INTEGER - 59)) | (1 << (painless_parser.DECIMAL - 59)) | (1 << (painless_parser.STRING - 59)) | (1 << (painless_parser.REGEX - 59)) | (1 << (painless_parser.TRUE - 59)) | (1 << (painless_parser.FALSE - 59)) | (1 << (painless_parser.NULL - 59)) | (1 << (painless_parser.PRIMITIVE - 59)) | (1 << (painless_parser.DEF - 59)) | (1 << (painless_parser.ID - 59)))) !== 0)) { { this.state = 516; this.argument(); @@ -2892,7 +2908,7 @@ export class painless_parser extends Parser { this.state = 543; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 81)) & ~0x1F) === 0 && ((1 << (_la - 81)) & ((1 << (painless_parser.PRIMITIVE - 81)) | (1 << (painless_parser.DEF - 81)) | (1 << (painless_parser.ID - 81)))) !== 0)) { + if (((((_la - 82)) & ~0x1F) === 0 && ((1 << (_la - 82)) & ((1 << (painless_parser.PRIMITIVE - 82)) | (1 << (painless_parser.DEF - 82)) | (1 << (painless_parser.ID - 82)))) !== 0)) { { this.state = 535; this.lamtype(); @@ -2935,6 +2951,7 @@ export class painless_parser extends Parser { break; case painless_parser.LBRACE: case painless_parser.LP: + case painless_parser.DOLLAR: case painless_parser.NEW: case painless_parser.BOOLNOT: case painless_parser.BWNOT: @@ -3137,7 +3154,7 @@ export class painless_parser extends Parser { private static readonly _serializedATNSegments: number = 2; private static readonly _serializedATNSegment0: string = - "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03W\u023E\x04\x02" + + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03X\u023E\x04\x02" + "\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04\x07" + "\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r\t\r\x04" + "\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12\x04" + @@ -3200,63 +3217,63 @@ export class painless_parser extends Parser { "\")\x02\x02\x04\x02\x06\x02\b\x02\n\x02\f\x02\x0E\x02\x10\x02\x12\x02" + "\x14\x02\x16\x02\x18\x02\x1A\x02\x1C\x02\x1E\x02 \x02\"\x02$\x02&\x02" + "(\x02*\x02,\x02.\x020\x022\x024\x026\x028\x02:\x02<\x02>\x02@\x02B\x02" + - "D\x02F\x02H\x02J\x02L\x02N\x02\x02\x10\x03\x03\x0E\x0E\x03\x02 \"\x03" + - "\x02#$\x03\x02:;\x03\x02%\'\x03\x02(+\x03\x02,/\x03\x02>I\x03\x02<=\x03" + - "\x02\x1E\x1F\x03\x02ST\x03\x02JM\x03\x02\v\f\x03\x02VW\x02\u0279\x02S" + - "\x03\x02\x02\x02\x04^\x03\x02\x02\x02\x06c\x03\x02\x02\x02\bw\x03\x02" + - "\x02\x02\n\xB5\x03\x02\x02\x02\f\xC8\x03\x02\x02\x02\x0E\xCC\x03\x02\x02" + - "\x02\x10\xCE\x03\x02\x02\x02\x12\xDA\x03\x02\x02\x02\x14\xDE\x03\x02\x02" + - "\x02\x16\xE0\x03\x02\x02\x02\x18\xE2\x03\x02\x02\x02\x1A\xEB\x03\x02\x02" + - "\x02\x1C\xFD\x03\x02\x02\x02\x1E\xFF\x03\x02\x02\x02 \u0104\x03\x02\x02" + - "\x02\"\u010B\x03\x02\x02\x02$\u0145\x03\x02\x02\x02&\u014C\x03\x02\x02" + - "\x02(\u0155\x03\x02\x02\x02*\u0161\x03\x02\x02\x02,\u0163\x03\x02\x02" + - "\x02.\u0182\x03\x02\x02\x020\u018C\x03\x02\x02\x022\u01A1\x03\x02\x02" + - "\x024\u01A6\x03\x02\x02\x026\u01AA\x03\x02\x02\x028\u01AC\x03\x02\x02" + - "\x02:\u01B0\x03\x02\x02\x02<\u01B3\x03\x02\x02\x02>\u01E0\x03\x02\x02" + - "\x02@\u01EF\x03\x02\x02\x02B\u01FF\x03\x02\x02\x02D\u0201\x03\x02\x02" + - "\x02F\u0205\x03\x02\x02\x02H\u0215\x03\x02\x02\x02J\u0224\x03\x02\x02" + - "\x02L\u022C\x03\x02\x02\x02N\u023B\x03\x02\x02\x02PR\x05\x04\x03\x02Q" + - "P\x03\x02\x02\x02RU\x03\x02\x02\x02SQ\x03\x02\x02\x02ST\x03\x02\x02\x02" + + "D\x02F\x02H\x02J\x02L\x02N\x02\x02\x11\x03\x03\x0F\x0F\x03\x02!#\x03\x02" + + "$%\x03\x02;<\x03\x02&(\x03\x02),\x03\x02-0\x03\x02?J\x03\x02=>\x03\x02" + + "\x1F \x03\x02TU\x03\x02KN\x04\x02\v\vVV\x03\x02\f\r\x03\x02WX\x02\u0279" + + "\x02S\x03\x02\x02\x02\x04^\x03\x02\x02\x02\x06c\x03\x02\x02\x02\bw\x03" + + "\x02\x02\x02\n\xB5\x03\x02\x02\x02\f\xC8\x03\x02\x02\x02\x0E\xCC\x03\x02" + + "\x02\x02\x10\xCE\x03\x02\x02\x02\x12\xDA\x03\x02\x02\x02\x14\xDE\x03\x02" + + "\x02\x02\x16\xE0\x03\x02\x02\x02\x18\xE2\x03\x02\x02\x02\x1A\xEB\x03\x02" + + "\x02\x02\x1C\xFD\x03\x02\x02\x02\x1E\xFF\x03\x02\x02\x02 \u0104\x03\x02" + + "\x02\x02\"\u010B\x03\x02\x02\x02$\u0145\x03\x02\x02\x02&\u014C\x03\x02" + + "\x02\x02(\u0155\x03\x02\x02\x02*\u0161\x03\x02\x02\x02,\u0163\x03\x02" + + "\x02\x02.\u0182\x03\x02\x02\x020\u018C\x03\x02\x02\x022\u01A1\x03\x02" + + "\x02\x024\u01A6\x03\x02\x02\x026\u01AA\x03\x02\x02\x028\u01AC\x03\x02" + + "\x02\x02:\u01B0\x03\x02\x02\x02<\u01B3\x03\x02\x02\x02>\u01E0\x03\x02" + + "\x02\x02@\u01EF\x03\x02\x02\x02B\u01FF\x03\x02\x02\x02D\u0201\x03\x02" + + "\x02\x02F\u0205\x03\x02\x02\x02H\u0215\x03\x02\x02\x02J\u0224\x03\x02" + + "\x02\x02L\u022C\x03\x02\x02\x02N\u023B\x03\x02\x02\x02PR\x05\x04\x03\x02" + + "QP\x03\x02\x02\x02RU\x03\x02\x02\x02SQ\x03\x02\x02\x02ST\x03\x02\x02\x02" + "TY\x03\x02\x02\x02US\x03\x02\x02\x02VX\x05\b\x05\x02WV\x03\x02\x02\x02" + "X[\x03\x02\x02\x02YW\x03\x02\x02\x02YZ\x03\x02\x02\x02Z\\\x03\x02\x02" + "\x02[Y\x03\x02\x02\x02\\]\x07\x02\x02\x03]\x03\x03\x02\x02\x02^_\x05\x1A" + - "\x0E\x02_`\x07U\x02\x02`a\x05\x06\x04\x02ab\x05\x10\t\x02b\x05\x03\x02" + - "\x02\x02co\x07\t\x02\x02de\x05\x1A\x0E\x02el\x07U\x02\x02fg\x07\r\x02" + - "\x02gh\x05\x1A\x0E\x02hi\x07U\x02\x02ik\x03\x02\x02\x02jf\x03\x02\x02" + + "\x0E\x02_`\x07V\x02\x02`a\x05\x06\x04\x02ab\x05\x10\t\x02b\x05\x03\x02" + + "\x02\x02co\x07\t\x02\x02de\x05\x1A\x0E\x02el\x07V\x02\x02fg\x07\x0E\x02" + + "\x02gh\x05\x1A\x0E\x02hi\x07V\x02\x02ik\x03\x02\x02\x02jf\x03\x02\x02" + "\x02kn\x03\x02\x02\x02lj\x03\x02\x02\x02lm\x03\x02\x02\x02mp\x03\x02\x02" + "\x02nl\x03\x02\x02\x02od\x03\x02\x02\x02op\x03\x02\x02\x02pq\x03\x02\x02" + "\x02qr\x07\n\x02\x02r\x07\x03\x02\x02\x02sx\x05\n\x06\x02tu\x05\f\x07" + "\x02uv\t\x02\x02\x02vx\x03\x02\x02\x02ws\x03\x02\x02\x02wt\x03\x02\x02" + - "\x02x\t\x03\x02\x02\x02yz\x07\x0F\x02\x02z{\x07\t\x02\x02{|\x05$\x13\x02" + - "|}\x07\n\x02\x02}\x81\x05\x0E\b\x02~\x7F\x07\x11\x02\x02\x7F\x82\x05\x0E" + + "\x02x\t\x03\x02\x02\x02yz\x07\x10\x02\x02z{\x07\t\x02\x02{|\x05$\x13\x02" + + "|}\x07\n\x02\x02}\x81\x05\x0E\b\x02~\x7F\x07\x12\x02\x02\x7F\x82\x05\x0E" + "\b\x02\x80\x82\x06\x06\x02\x02\x81~\x03\x02\x02\x02\x81\x80\x03\x02\x02" + - "\x02\x82\xB6\x03\x02\x02\x02\x83\x84\x07\x12\x02\x02\x84\x85\x07\t\x02" + + "\x02\x82\xB6\x03\x02\x02\x02\x83\x84\x07\x13\x02\x02\x84\x85\x07\t\x02" + "\x02\x85\x86\x05$\x13\x02\x86\x89\x07\n\x02\x02\x87\x8A\x05\x0E\b\x02" + "\x88\x8A\x05\x12\n\x02\x89\x87\x03\x02\x02\x02\x89\x88\x03\x02\x02\x02" + - "\x8A\xB6\x03\x02\x02\x02\x8B\x8C\x07\x14\x02\x02\x8C\x8E\x07\t\x02\x02" + + "\x8A\xB6\x03\x02\x02\x02\x8B\x8C\x07\x15\x02\x02\x8C\x8E\x07\t\x02\x02" + "\x8D\x8F\x05\x14\v\x02\x8E\x8D\x03\x02\x02\x02\x8E\x8F\x03\x02\x02\x02" + - "\x8F\x90\x03\x02\x02\x02\x90\x92\x07\x0E\x02\x02\x91\x93\x05$\x13\x02" + + "\x8F\x90\x03\x02\x02\x02\x90\x92\x07\x0F\x02\x02\x91\x93\x05$\x13\x02" + "\x92\x91\x03\x02\x02\x02\x92\x93\x03\x02\x02\x02\x93\x94\x03\x02\x02\x02" + - "\x94\x96\x07\x0E\x02\x02\x95\x97\x05\x16\f\x02\x96\x95\x03\x02\x02\x02" + + "\x94\x96\x07\x0F\x02\x02\x95\x97\x05\x16\f\x02\x96\x95\x03\x02\x02\x02" + "\x96\x97\x03\x02\x02\x02\x97\x98\x03\x02\x02\x02\x98\x9B\x07\n\x02\x02" + "\x99\x9C\x05\x0E\b\x02\x9A\x9C\x05\x12\n\x02\x9B\x99\x03\x02\x02\x02\x9B" + - "\x9A\x03\x02\x02\x02\x9C\xB6\x03\x02\x02\x02\x9D\x9E\x07\x14\x02\x02\x9E" + - "\x9F\x07\t\x02\x02\x9F\xA0\x05\x1A\x0E\x02\xA0\xA1\x07U\x02\x02\xA1\xA2" + - "\x076\x02\x02\xA2\xA3\x05$\x13\x02\xA3\xA4\x07\n\x02\x02\xA4\xA5\x05\x0E" + - "\b\x02\xA5\xB6\x03\x02\x02\x02\xA6\xA7\x07\x14\x02\x02\xA7\xA8\x07\t\x02" + - "\x02\xA8\xA9\x07U\x02\x02\xA9\xAA\x07\x10\x02\x02\xAA\xAB\x05$\x13\x02" + + "\x9A\x03\x02\x02\x02\x9C\xB6\x03\x02\x02\x02\x9D\x9E\x07\x15\x02\x02\x9E" + + "\x9F\x07\t\x02\x02\x9F\xA0\x05\x1A\x0E\x02\xA0\xA1\x07V\x02\x02\xA1\xA2" + + "\x077\x02\x02\xA2\xA3\x05$\x13\x02\xA3\xA4\x07\n\x02\x02\xA4\xA5\x05\x0E" + + "\b\x02\xA5\xB6\x03\x02\x02\x02\xA6\xA7\x07\x15\x02\x02\xA7\xA8\x07\t\x02" + + "\x02\xA8\xA9\x07V\x02\x02\xA9\xAA\x07\x11\x02\x02\xAA\xAB\x05$\x13\x02" + "\xAB\xAC\x07\n\x02\x02\xAC\xAD\x05\x0E\b\x02\xAD\xB6\x03\x02\x02\x02\xAE" + - "\xAF\x07\x19\x02\x02\xAF\xB1\x05\x10\t\x02\xB0\xB2\x05 \x11\x02\xB1\xB0" + + "\xAF\x07\x1A\x02\x02\xAF\xB1\x05\x10\t\x02\xB0\xB2\x05 \x11\x02\xB1\xB0" + "\x03\x02\x02\x02\xB2\xB3\x03\x02\x02\x02\xB3\xB1\x03\x02\x02\x02\xB3\xB4" + "\x03\x02\x02\x02\xB4\xB6\x03\x02\x02\x02\xB5y\x03\x02\x02\x02\xB5\x83" + "\x03\x02\x02\x02\xB5\x8B\x03\x02\x02\x02\xB5\x9D\x03\x02\x02\x02\xB5\xA6" + "\x03\x02\x02\x02\xB5\xAE\x03\x02\x02\x02\xB6\v\x03\x02\x02\x02\xB7\xB8" + - "\x07\x13\x02\x02\xB8\xB9\x05\x10\t\x02\xB9\xBA\x07\x12\x02\x02\xBA\xBB" + + "\x07\x14\x02\x02\xB8\xB9\x05\x10\t\x02\xB9\xBA\x07\x13\x02\x02\xBA\xBB" + "\x07\t\x02\x02\xBB\xBC\x05$\x13\x02\xBC\xBD\x07\n\x02\x02\xBD\xC9\x03" + - "\x02\x02\x02\xBE\xC9\x05\x18\r\x02\xBF\xC9\x07\x15\x02\x02\xC0\xC9\x07" + - "\x16\x02\x02\xC1\xC3\x07\x17\x02\x02\xC2\xC4\x05$\x13\x02\xC3\xC2\x03" + + "\x02\x02\x02\xBE\xC9\x05\x18\r\x02\xBF\xC9\x07\x16\x02\x02\xC0\xC9\x07" + + "\x17\x02\x02\xC1\xC3\x07\x18\x02\x02\xC2\xC4\x05$\x13\x02\xC3\xC2\x03" + "\x02\x02\x02\xC3\xC4\x03\x02\x02\x02\xC4\xC9\x03\x02\x02\x02\xC5\xC6\x07" + - "\x1B\x02\x02\xC6\xC9\x05$\x13\x02\xC7\xC9\x05$\x13\x02\xC8\xB7\x03\x02" + + "\x1C\x02\x02\xC6\xC9\x05$\x13\x02\xC7\xC9\x05$\x13\x02\xC8\xB7\x03\x02" + "\x02\x02\xC8\xBE\x03\x02\x02\x02\xC8\xBF\x03\x02\x02\x02\xC8\xC0\x03\x02" + "\x02\x02\xC8\xC1\x03\x02\x02\x02\xC8\xC5\x03\x02\x02\x02\xC8\xC7\x03\x02" + "\x02\x02\xC9\r\x03\x02\x02\x02\xCA\xCD\x05\x10\t\x02\xCB\xCD\x05\b\x05" + @@ -3265,25 +3282,25 @@ export class painless_parser extends Parser { "\x02\xD1\xD4\x03\x02\x02\x02\xD2\xD0\x03\x02\x02\x02\xD2\xD3\x03\x02\x02" + "\x02\xD3\xD6\x03\x02\x02\x02\xD4\xD2\x03\x02\x02\x02\xD5\xD7\x05\f\x07" + "\x02\xD6\xD5\x03\x02\x02\x02\xD6\xD7\x03\x02\x02\x02\xD7\xD8\x03\x02\x02" + - "\x02\xD8\xD9\x07\x06\x02\x02\xD9\x11\x03\x02\x02\x02\xDA\xDB\x07\x0E\x02" + + "\x02\xD8\xD9\x07\x06\x02\x02\xD9\x11\x03\x02\x02\x02\xDA\xDB\x07\x0F\x02" + "\x02\xDB\x13\x03\x02\x02\x02\xDC\xDF\x05\x18\r\x02\xDD\xDF\x05$\x13\x02" + "\xDE\xDC\x03\x02\x02\x02\xDE\xDD\x03\x02\x02\x02\xDF\x15\x03\x02\x02\x02" + "\xE0\xE1\x05$\x13\x02\xE1\x17\x03\x02\x02\x02\xE2\xE3\x05\x1A\x0E\x02" + - "\xE3\xE8\x05\x1E\x10\x02\xE4\xE5\x07\r\x02\x02\xE5\xE7\x05\x1E\x10\x02" + + "\xE3\xE8\x05\x1E\x10\x02\xE4\xE5\x07\x0E\x02\x02\xE5\xE7\x05\x1E\x10\x02" + "\xE6\xE4\x03\x02\x02\x02\xE7\xEA\x03\x02\x02\x02\xE8\xE6\x03\x02\x02\x02" + "\xE8\xE9\x03\x02\x02\x02\xE9\x19\x03\x02\x02\x02\xEA\xE8\x03\x02\x02\x02" + "\xEB\xF0\x05\x1C\x0F\x02\xEC\xED\x07\x07\x02\x02\xED\xEF\x07\b\x02\x02" + "\xEE\xEC\x03\x02\x02\x02\xEF\xF2\x03\x02\x02\x02\xF0\xEE\x03\x02\x02\x02" + "\xF0\xF1\x03\x02\x02\x02\xF1\x1B\x03\x02\x02\x02\xF2\xF0\x03\x02\x02\x02" + - "\xF3\xFE\x07T\x02\x02\xF4\xFE\x07S\x02\x02\xF5\xFA\x07U\x02\x02\xF6\xF7" + - "\x07\v\x02\x02\xF7\xF9\x07W\x02\x02\xF8\xF6\x03\x02\x02\x02\xF9\xFC\x03" + + "\xF3\xFE\x07U\x02\x02\xF4\xFE\x07T\x02\x02\xF5\xFA\x07V\x02\x02\xF6\xF7" + + "\x07\f\x02\x02\xF7\xF9\x07X\x02\x02\xF8\xF6\x03\x02\x02\x02\xF9\xFC\x03" + "\x02\x02\x02\xFA\xF8\x03\x02\x02\x02\xFA\xFB\x03\x02\x02\x02\xFB\xFE\x03" + "\x02\x02\x02\xFC\xFA\x03\x02\x02\x02\xFD\xF3\x03\x02\x02\x02\xFD\xF4\x03" + "\x02\x02\x02\xFD\xF5\x03\x02\x02\x02\xFE\x1D\x03\x02\x02\x02\xFF\u0102" + - "\x07U\x02\x02\u0100\u0101\x07>\x02\x02\u0101\u0103\x05$\x13\x02\u0102" + + "\x07V\x02\x02\u0100\u0101\x07?\x02\x02\u0101\u0103\x05$\x13\x02\u0102" + "\u0100\x03\x02\x02\x02\u0102\u0103\x03\x02\x02\x02\u0103\x1F\x03\x02\x02" + - "\x02\u0104\u0105\x07\x1A\x02\x02\u0105\u0106\x07\t\x02\x02\u0106\u0107" + - "\x05\x1C\x0F\x02\u0107\u0108\x07U\x02\x02\u0108\u0109\x07\n\x02\x02\u0109" + + "\x02\u0104\u0105\x07\x1B\x02\x02\u0105\u0106\x07\t\x02\x02\u0106\u0107" + + "\x05\x1C\x0F\x02\u0107\u0108\x07V\x02\x02\u0108\u0109\x07\n\x02\x02\u0109" + "\u010A\x05\x10\t\x02\u010A!\x03\x02\x02\x02\u010B\u010C\b\x12\x01\x02" + "\u010C\u010D\x05&\x14\x02\u010D\u0137\x03\x02\x02\x02\u010E\u010F\f\x0F" + "\x02\x02\u010F\u0110\t\x03\x02\x02\u0110\u0136\x05\"\x12\x10\u0111\u0112" + @@ -3292,14 +3309,14 @@ export class painless_parser extends Parser { "\u0117\u0118\f\f\x02\x02\u0118\u0119\t\x06\x02\x02\u0119\u0136\x05\"\x12" + "\r\u011A\u011B\f\v\x02\x02\u011B\u011C\t\x07\x02\x02\u011C\u0136\x05\"" + "\x12\f\u011D\u011E\f\t\x02\x02\u011E\u011F\t\b\x02\x02\u011F\u0136\x05" + - "\"\x12\n\u0120\u0121\f\b\x02\x02\u0121\u0122\x070\x02\x02\u0122\u0136" + - "\x05\"\x12\t\u0123\u0124\f\x07\x02\x02\u0124\u0125\x071\x02\x02\u0125" + - "\u0136\x05\"\x12\b\u0126\u0127\f\x06\x02\x02\u0127\u0128\x072\x02\x02" + - "\u0128\u0136\x05\"\x12\x07\u0129\u012A\f\x05\x02\x02\u012A\u012B\x073" + + "\"\x12\n\u0120\u0121\f\b\x02\x02\u0121\u0122\x071\x02\x02\u0122\u0136" + + "\x05\"\x12\t\u0123\u0124\f\x07\x02\x02\u0124\u0125\x072\x02\x02\u0125" + + "\u0136\x05\"\x12\b\u0126\u0127\f\x06\x02\x02\u0127\u0128\x073\x02\x02" + + "\u0128\u0136\x05\"\x12\x07\u0129\u012A\f\x05\x02\x02\u012A\u012B\x074" + "\x02\x02\u012B\u0136\x05\"\x12\x06\u012C\u012D\f\x04\x02\x02\u012D\u012E" + - "\x074\x02\x02\u012E\u0136\x05\"\x12\x05\u012F\u0130\f\x03\x02\x02\u0130" + - "\u0131\x077\x02\x02\u0131\u0136\x05\"\x12\x03\u0132\u0133\f\n\x02\x02" + - "\u0133\u0134\x07\x1D\x02\x02\u0134\u0136\x05\x1A\x0E\x02\u0135\u010E\x03" + + "\x075\x02\x02\u012E\u0136\x05\"\x12\x05\u012F\u0130\f\x03\x02\x02\u0130" + + "\u0131\x078\x02\x02\u0131\u0136\x05\"\x12\x03\u0132\u0133\f\n\x02\x02" + + "\u0133\u0134\x07\x1E\x02\x02\u0134\u0136\x05\x1A\x0E\x02\u0135\u010E\x03" + "\x02\x02\x02\u0135\u0111\x03\x02\x02\x02\u0135\u0114\x03\x02\x02\x02\u0135" + "\u0117\x03\x02\x02\x02\u0135\u011A\x03\x02\x02\x02\u0135\u011D\x03\x02" + "\x02\x02\u0135\u0120\x03\x02\x02\x02\u0135\u0123\x03\x02\x02\x02\u0135" + @@ -3307,8 +3324,8 @@ export class painless_parser extends Parser { "\x02\x02\u0135\u012F\x03\x02\x02\x02\u0135\u0132\x03\x02\x02\x02\u0136" + "\u0139\x03\x02\x02\x02\u0137\u0135\x03\x02\x02\x02\u0137\u0138\x03\x02" + "\x02\x02\u0138#\x03\x02\x02\x02\u0139\u0137\x03\x02\x02\x02\u013A\u0146" + - "\x05\"\x12\x02\u013B\u013C\x05\"\x12\x02\u013C\u013D\x075\x02\x02\u013D" + - "\u013E\x05$\x13\x02\u013E\u013F\x076\x02\x02\u013F\u0140\x05$\x13\x02" + + "\x05\"\x12\x02\u013B\u013C\x05\"\x12\x02\u013C\u013D\x076\x02\x02\u013D" + + "\u013E\x05$\x13\x02\u013E\u013F\x077\x02\x02\u013F\u0140\x05$\x13\x02" + "\u0140\u0146\x03\x02\x02\x02\u0141\u0142\x05\"\x12\x02\u0142\u0143\t\t" + "\x02\x02\u0143\u0144\x05$\x13\x02\u0144\u0146\x03\x02\x02\x02\u0145\u013A" + "\x03\x02\x02\x02\u0145\u013B\x03\x02\x02\x02\u0145\u0141\x03\x02\x02\x02" + @@ -3325,14 +3342,14 @@ export class painless_parser extends Parser { "\t\x02\x02\u015D\u015E\x05.\x18\x02\u015E\u015F\x07\n\x02\x02\u015F\u0160" + "\x05(\x15\x02\u0160\u0162\x03\x02\x02\x02\u0161\u0157\x03\x02\x02\x02" + "\u0161\u015C\x03\x02\x02\x02\u0162+\x03\x02\x02\x02\u0163\u0164\t\f\x02" + - "\x02\u0164-\x03\x02\x02\x02\u0165\u0168\x07T\x02\x02\u0166\u0167\x07\x07" + + "\x02\u0164-\x03\x02\x02\x02\u0165\u0168\x07U\x02\x02\u0166\u0167\x07\x07" + "\x02\x02\u0167\u0169\x07\b\x02\x02\u0168\u0166\x03\x02\x02\x02\u0169\u016A" + "\x03\x02\x02\x02\u016A\u0168\x03\x02\x02\x02\u016A\u016B\x03\x02\x02\x02" + - "\u016B\u0183\x03\x02\x02\x02\u016C\u016F\x07S\x02\x02\u016D\u016E\x07" + + "\u016B\u0183\x03\x02\x02\x02\u016C\u016F\x07T\x02\x02\u016D\u016E\x07" + "\x07\x02\x02\u016E\u0170\x07\b\x02\x02\u016F\u016D\x03\x02\x02\x02\u0170" + "\u0171\x03\x02\x02\x02\u0171\u016F\x03\x02\x02\x02\u0171\u0172\x03\x02" + - "\x02\x02\u0172\u0183\x03\x02\x02\x02\u0173\u0178\x07U\x02\x02\u0174\u0175" + - "\x07\v\x02\x02\u0175\u0177\x07W\x02\x02\u0176\u0174\x03\x02\x02\x02\u0177" + + "\x02\x02\u0172\u0183\x03\x02\x02\x02\u0173\u0178\x07V\x02\x02\u0174\u0175" + + "\x07\f\x02\x02\u0175\u0177\x07X\x02\x02\u0176\u0174\x03\x02\x02\x02\u0177" + "\u017A\x03\x02\x02\x02\u0178\u0176\x03\x02\x02\x02\u0178\u0179\x03\x02" + "\x02\x02\u0179\u017F\x03\x02\x02\x02\u017A\u0178\x03\x02\x02\x02\u017B" + "\u017C\x07\x07\x02\x02\u017C\u017E\x07\b\x02\x02\u017D\u017B\x03\x02\x02" + @@ -3345,11 +3362,11 @@ export class painless_parser extends Parser { "\x02\x02\x02\u018A\u0188\x03\x02\x02\x02\u018B\u018D\x05> \x02\u018C\u0184" + "\x03\x02\x02\x02\u018C\u018B\x03\x02\x02\x02\u018D1\x03\x02\x02\x02\u018E" + "\u018F\x07\t\x02\x02\u018F\u0190\x05$\x13\x02\u0190\u0191\x07\n\x02\x02" + - "\u0191\u01A2\x03\x02\x02\x02\u0192\u01A2\t\r\x02\x02\u0193\u01A2\x07P" + - "\x02\x02\u0194\u01A2\x07Q\x02\x02\u0195\u01A2\x07R\x02\x02\u0196\u01A2" + - "\x07N\x02\x02\u0197\u01A2\x07O\x02\x02\u0198\u01A2\x05@!\x02\u0199\u01A2" + - "\x05B\"\x02\u019A\u01A2\x07U\x02\x02\u019B\u019C\x07U\x02\x02\u019C\u01A2" + - "\x05F$\x02\u019D\u019E\x07\x18\x02\x02\u019E\u019F\x05\x1C\x0F\x02\u019F" + + "\u0191\u01A2\x03\x02\x02\x02\u0192\u01A2\t\r\x02\x02\u0193\u01A2\x07Q" + + "\x02\x02\u0194\u01A2\x07R\x02\x02\u0195\u01A2\x07S\x02\x02\u0196\u01A2" + + "\x07O\x02\x02\u0197\u01A2\x07P\x02\x02\u0198\u01A2\x05@!\x02\u0199\u01A2" + + "\x05B\"\x02\u019A\u01A2\x07V\x02\x02\u019B\u019C\t\x0E\x02\x02\u019C\u01A2" + + "\x05F$\x02\u019D\u019E\x07\x19\x02\x02\u019E\u019F\x05\x1C\x0F\x02\u019F" + "\u01A0\x05F$\x02\u01A0\u01A2\x03\x02\x02\x02\u01A1\u018E\x03\x02\x02\x02" + "\u01A1\u0192\x03\x02\x02\x02\u01A1\u0193\x03\x02\x02\x02\u01A1\u0194\x03" + "\x02\x02\x02\u01A1\u0195\x03\x02\x02\x02\u01A1\u0196\x03\x02\x02\x02\u01A1" + @@ -3360,11 +3377,11 @@ export class painless_parser extends Parser { "\x02\x02\u01A6\u01A4\x03\x02\x02\x02\u01A6\u01A5\x03\x02\x02\x02\u01A7" + "5\x03\x02\x02\x02\u01A8\u01AB\x058\x1D\x02\u01A9\u01AB\x05:\x1E\x02\u01AA" + "\u01A8\x03\x02\x02\x02\u01AA\u01A9\x03\x02\x02\x02\u01AB7\x03\x02\x02" + - "\x02\u01AC\u01AD\t\x0E\x02\x02\u01AD\u01AE\x07W\x02\x02\u01AE\u01AF\x05" + - "F$\x02\u01AF9\x03\x02\x02\x02\u01B0\u01B1\t\x0E\x02\x02\u01B1\u01B2\t" + - "\x0F\x02\x02\u01B2;\x03\x02\x02\x02\u01B3\u01B4\x07\x07\x02\x02\u01B4" + + "\x02\u01AC\u01AD\t\x0F\x02\x02\u01AD\u01AE\x07X\x02\x02\u01AE\u01AF\x05" + + "F$\x02\u01AF9\x03\x02\x02\x02\u01B0\u01B1\t\x0F\x02\x02\u01B1\u01B2\t" + + "\x10\x02\x02\u01B2;\x03\x02\x02\x02\u01B3\u01B4\x07\x07\x02\x02\u01B4" + "\u01B5\x05$\x13\x02\u01B5\u01B6\x07\b\x02\x02\u01B6=\x03\x02\x02\x02\u01B7" + - "\u01B8\x07\x18\x02\x02\u01B8\u01BD\x05\x1C\x0F\x02\u01B9\u01BA\x07\x07" + + "\u01B8\x07\x19\x02\x02\u01B8\u01BD\x05\x1C\x0F\x02\u01B9\u01BA\x07\x07" + "\x02\x02\u01BA\u01BB\x05$\x13\x02\u01BB\u01BC\x07\b\x02\x02\u01BC\u01BE" + "\x03\x02\x02\x02\u01BD\u01B9\x03\x02\x02\x02\u01BE\u01BF\x03\x02\x02\x02" + "\u01BF\u01BD\x03\x02\x02\x02\u01BF\u01C0\x03\x02\x02\x02\u01C0\u01C8\x03" + @@ -3372,63 +3389,63 @@ export class painless_parser extends Parser { "\x03\x02\x02\x02\u01C4\u01C7\x03\x02\x02\x02\u01C5\u01C3\x03\x02\x02\x02" + "\u01C5\u01C6\x03\x02\x02\x02\u01C6\u01C9\x03\x02\x02\x02\u01C7\u01C5\x03" + "\x02\x02\x02\u01C8\u01C1\x03\x02\x02\x02\u01C8\u01C9\x03\x02\x02\x02\u01C9" + - "\u01E1\x03\x02\x02\x02\u01CA\u01CB\x07\x18\x02\x02\u01CB\u01CC\x05\x1C" + + "\u01E1\x03\x02\x02\x02\u01CA\u01CB\x07\x19\x02\x02\u01CB\u01CC\x05\x1C" + "\x0F\x02\u01CC\u01CD\x07\x07\x02\x02\u01CD\u01CE\x07\b\x02\x02\u01CE\u01D7" + - "\x07\x05\x02\x02\u01CF\u01D4\x05$\x13\x02\u01D0\u01D1\x07\r\x02\x02\u01D1" + - "\u01D3\x05$\x13\x02\u01D2\u01D0\x03\x02\x02\x02\u01D3\u01D6\x03\x02\x02" + - "\x02\u01D4\u01D2\x03\x02\x02\x02\u01D4\u01D5\x03\x02\x02\x02\u01D5\u01D8" + - "\x03\x02\x02\x02\u01D6\u01D4\x03\x02\x02\x02\u01D7\u01CF\x03\x02\x02\x02" + - "\u01D7\u01D8\x03\x02\x02\x02\u01D8\u01D9\x03\x02\x02\x02\u01D9\u01DD\x07" + - "\x06\x02\x02\u01DA\u01DC\x054\x1B\x02\u01DB\u01DA\x03\x02\x02\x02\u01DC" + - "\u01DF\x03\x02\x02\x02\u01DD\u01DB\x03\x02\x02\x02\u01DD\u01DE\x03\x02" + - "\x02\x02\u01DE\u01E1\x03\x02\x02\x02\u01DF\u01DD\x03\x02\x02\x02\u01E0" + - "\u01B7\x03\x02\x02\x02\u01E0\u01CA\x03\x02\x02\x02\u01E1?\x03\x02\x02" + - "\x02\u01E2\u01E3\x07\x07\x02\x02\u01E3\u01E8\x05$\x13\x02\u01E4\u01E5" + - "\x07\r\x02\x02\u01E5\u01E7\x05$\x13\x02\u01E6\u01E4\x03\x02\x02\x02\u01E7" + - "\u01EA\x03\x02\x02\x02\u01E8\u01E6\x03\x02\x02\x02\u01E8\u01E9\x03\x02" + - "\x02\x02\u01E9\u01EB\x03\x02\x02\x02\u01EA\u01E8\x03\x02\x02\x02\u01EB" + + "\x07\x05\x02\x02\u01CF\u01D4\x05$\x13\x02\u01D0\u01D1\x07\x0E\x02\x02" + + "\u01D1\u01D3\x05$\x13\x02\u01D2\u01D0\x03\x02\x02\x02\u01D3\u01D6\x03" + + "\x02\x02\x02\u01D4\u01D2\x03\x02\x02\x02\u01D4\u01D5\x03\x02\x02\x02\u01D5" + + "\u01D8\x03\x02\x02\x02\u01D6\u01D4\x03\x02\x02\x02\u01D7\u01CF\x03\x02" + + "\x02\x02\u01D7\u01D8\x03\x02\x02\x02\u01D8\u01D9\x03\x02\x02\x02\u01D9" + + "\u01DD\x07\x06\x02\x02\u01DA\u01DC\x054\x1B\x02\u01DB\u01DA\x03\x02\x02" + + "\x02\u01DC\u01DF\x03\x02\x02\x02\u01DD\u01DB\x03\x02\x02\x02\u01DD\u01DE" + + "\x03\x02\x02\x02\u01DE\u01E1\x03\x02\x02\x02\u01DF\u01DD\x03\x02\x02\x02" + + "\u01E0\u01B7\x03\x02\x02\x02\u01E0\u01CA\x03\x02\x02\x02\u01E1?\x03\x02" + + "\x02\x02\u01E2\u01E3\x07\x07\x02\x02\u01E3\u01E8\x05$\x13\x02\u01E4\u01E5" + + "\x07\x0E\x02\x02\u01E5\u01E7\x05$\x13\x02\u01E6\u01E4\x03\x02\x02\x02" + + "\u01E7\u01EA\x03\x02\x02\x02\u01E8\u01E6\x03\x02\x02\x02\u01E8\u01E9\x03" + + "\x02\x02\x02\u01E9\u01EB\x03\x02\x02\x02\u01EA\u01E8\x03\x02\x02\x02\u01EB" + "\u01EC\x07\b\x02\x02\u01EC\u01F0\x03\x02\x02\x02\u01ED\u01EE\x07\x07\x02" + "\x02\u01EE\u01F0\x07\b\x02\x02\u01EF\u01E2\x03\x02\x02\x02\u01EF\u01ED" + "\x03\x02\x02\x02\u01F0A\x03\x02\x02\x02\u01F1\u01F2\x07\x07\x02\x02\u01F2" + - "\u01F7\x05D#\x02\u01F3\u01F4\x07\r\x02\x02\u01F4\u01F6\x05D#\x02\u01F5" + + "\u01F7\x05D#\x02\u01F3\u01F4\x07\x0E\x02\x02\u01F4\u01F6\x05D#\x02\u01F5" + "\u01F3\x03\x02\x02\x02\u01F6\u01F9\x03\x02\x02\x02\u01F7\u01F5\x03\x02" + "\x02\x02\u01F7\u01F8\x03\x02\x02\x02\u01F8\u01FA\x03\x02\x02\x02\u01F9" + "\u01F7\x03\x02\x02\x02\u01FA\u01FB\x07\b\x02\x02\u01FB\u0200\x03\x02\x02" + - "\x02\u01FC\u01FD\x07\x07\x02\x02\u01FD\u01FE\x076\x02\x02\u01FE\u0200" + + "\x02\u01FC\u01FD\x07\x07\x02\x02\u01FD\u01FE\x077\x02\x02\u01FE\u0200" + "\x07\b\x02\x02\u01FF\u01F1\x03\x02\x02\x02\u01FF\u01FC\x03\x02\x02\x02" + - "\u0200C\x03\x02\x02\x02\u0201\u0202\x05$\x13\x02\u0202\u0203\x076\x02" + + "\u0200C\x03\x02\x02\x02\u0201\u0202\x05$\x13\x02\u0202\u0203\x077\x02" + "\x02\u0203\u0204\x05$\x13\x02\u0204E\x03\x02\x02\x02\u0205\u020E\x07\t" + - "\x02\x02\u0206\u020B\x05H%\x02\u0207\u0208\x07\r\x02\x02\u0208\u020A\x05" + - "H%\x02\u0209\u0207\x03\x02\x02\x02\u020A\u020D\x03\x02\x02\x02\u020B\u0209" + - "\x03\x02\x02\x02\u020B\u020C\x03\x02\x02\x02\u020C\u020F\x03\x02\x02\x02" + - "\u020D\u020B\x03\x02\x02\x02\u020E\u0206\x03\x02\x02\x02\u020E\u020F\x03" + - "\x02\x02\x02\u020F\u0210\x03\x02\x02\x02\u0210\u0211\x07\n\x02\x02\u0211" + - "G\x03\x02\x02\x02\u0212\u0216\x05$\x13\x02\u0213\u0216\x05J&\x02\u0214" + - "\u0216\x05N(\x02\u0215\u0212\x03\x02\x02\x02\u0215\u0213\x03\x02\x02\x02" + - "\u0215\u0214\x03\x02\x02\x02\u0216I\x03\x02\x02\x02\u0217\u0225\x05L\'" + - "\x02\u0218\u0221\x07\t\x02\x02\u0219\u021E\x05L\'\x02\u021A\u021B\x07" + - "\r\x02\x02\u021B\u021D\x05L\'\x02\u021C\u021A\x03\x02\x02\x02\u021D\u0220" + - "\x03\x02\x02\x02\u021E\u021C\x03\x02\x02\x02\u021E\u021F\x03\x02\x02\x02" + - "\u021F\u0222\x03\x02\x02\x02\u0220\u021E\x03\x02\x02\x02\u0221\u0219\x03" + - "\x02\x02\x02\u0221\u0222\x03\x02\x02\x02\u0222\u0223\x03\x02\x02\x02\u0223" + - "\u0225\x07\n\x02\x02\u0224\u0217\x03\x02\x02\x02\u0224\u0218\x03\x02\x02" + - "\x02\u0225\u0226"; + "\x02\x02\u0206\u020B\x05H%\x02\u0207\u0208\x07\x0E\x02\x02\u0208\u020A" + + "\x05H%\x02\u0209\u0207\x03\x02\x02\x02\u020A\u020D\x03\x02\x02\x02\u020B" + + "\u0209\x03\x02\x02\x02\u020B\u020C\x03\x02\x02\x02\u020C\u020F\x03\x02" + + "\x02\x02\u020D\u020B\x03\x02\x02\x02\u020E\u0206\x03\x02\x02\x02\u020E" + + "\u020F\x03\x02\x02\x02\u020F\u0210\x03\x02\x02\x02\u0210\u0211\x07\n\x02" + + "\x02\u0211G\x03\x02\x02\x02\u0212\u0216\x05$\x13\x02\u0213\u0216\x05J" + + "&\x02\u0214\u0216\x05N(\x02\u0215\u0212\x03\x02\x02\x02\u0215\u0213\x03" + + "\x02\x02\x02\u0215\u0214\x03\x02\x02\x02\u0216I\x03\x02\x02\x02\u0217" + + "\u0225\x05L\'\x02\u0218\u0221\x07\t\x02\x02\u0219\u021E\x05L\'\x02\u021A" + + "\u021B\x07\x0E\x02\x02\u021B\u021D\x05L\'\x02\u021C\u021A\x03\x02\x02" + + "\x02\u021D\u0220\x03\x02\x02\x02\u021E\u021C\x03\x02\x02\x02\u021E\u021F" + + "\x03\x02\x02\x02\u021F\u0222\x03\x02\x02\x02\u0220\u021E\x03\x02\x02\x02" + + "\u0221\u0219\x03\x02\x02\x02\u0221\u0222\x03\x02\x02\x02\u0222\u0223\x03" + + "\x02\x02\x02\u0223\u0225\x07\n\x02\x02\u0224\u0217\x03\x02\x02\x02\u0224" + + "\u0218"; private static readonly _serializedATNSegment1: string = - "\x03\x02\x02\x02\u0226\u0229\x079\x02\x02\u0227\u022A\x05\x10\t\x02\u0228" + - "\u022A\x05$\x13\x02\u0229\u0227\x03\x02\x02\x02\u0229\u0228\x03\x02\x02" + - "\x02\u022AK\x03\x02\x02\x02\u022B\u022D\x05\x1A\x0E\x02\u022C\u022B\x03" + - "\x02\x02\x02\u022C\u022D\x03\x02\x02\x02\u022D\u022E\x03\x02\x02\x02\u022E" + - "\u022F\x07U\x02\x02\u022FM\x03\x02\x02\x02\u0230\u0231\x05\x1A\x0E\x02" + - "\u0231\u0232\x078\x02\x02\u0232\u0233\x07U\x02\x02\u0233\u023C\x03\x02" + - "\x02\x02\u0234\u0235\x05\x1A\x0E\x02\u0235\u0236\x078\x02\x02\u0236\u0237" + - "\x07\x18\x02\x02\u0237\u023C\x03\x02\x02\x02\u0238\u0239\x07\x1C\x02\x02" + - "\u0239\u023A\x078\x02\x02\u023A\u023C\x07U\x02\x02\u023B\u0230\x03\x02" + - "\x02\x02\u023B\u0234\x03\x02\x02\x02\u023B\u0238\x03\x02\x02\x02\u023C" + - "O\x03\x02\x02\x02>SYlow\x81\x89\x8E\x92\x96\x9B\xB3\xB5\xC3\xC8\xCC\xD2" + - "\xD6\xDE\xE8\xF0\xFA\xFD\u0102\u0135\u0137\u0145\u014C\u0155\u0161\u016A" + - "\u0171\u0178\u017F\u0182\u0188\u018C\u01A1\u01A6\u01AA\u01BF\u01C5\u01C8" + - "\u01D4\u01D7\u01DD\u01E0\u01E8\u01EF\u01F7\u01FF\u020B\u020E\u0215\u021E" + - "\u0221\u0224\u0229\u022C\u023B"; + "\x03\x02\x02\x02\u0225\u0226\x03\x02\x02\x02\u0226\u0229\x07:\x02\x02" + + "\u0227\u022A\x05\x10\t\x02\u0228\u022A\x05$\x13\x02\u0229\u0227\x03\x02" + + "\x02\x02\u0229\u0228\x03\x02\x02\x02\u022AK\x03\x02\x02\x02\u022B\u022D" + + "\x05\x1A\x0E\x02\u022C\u022B\x03\x02\x02\x02\u022C\u022D\x03\x02\x02\x02" + + "\u022D\u022E\x03\x02\x02\x02\u022E\u022F\x07V\x02\x02\u022FM\x03\x02\x02" + + "\x02\u0230\u0231\x05\x1A\x0E\x02\u0231\u0232\x079\x02\x02\u0232\u0233" + + "\x07V\x02\x02\u0233\u023C\x03\x02\x02\x02\u0234\u0235\x05\x1A\x0E\x02" + + "\u0235\u0236\x079\x02\x02\u0236\u0237\x07\x19\x02\x02\u0237\u023C\x03" + + "\x02\x02\x02\u0238\u0239\x07\x1D\x02\x02\u0239\u023A\x079\x02\x02\u023A" + + "\u023C\x07V\x02\x02\u023B\u0230\x03\x02\x02\x02\u023B\u0234\x03\x02\x02" + + "\x02\u023B\u0238\x03\x02\x02\x02\u023CO\x03\x02\x02\x02>SYlow\x81\x89" + + "\x8E\x92\x96\x9B\xB3\xB5\xC3\xC8\xCC\xD2\xD6\xDE\xE8\xF0\xFA\xFD\u0102" + + "\u0135\u0137\u0145\u014C\u0155\u0161\u016A\u0171\u0178\u017F\u0182\u0188" + + "\u018C\u01A1\u01A6\u01AA\u01BF\u01C5\u01C8\u01D4\u01D7\u01DD\u01E0\u01E8" + + "\u01EF\u01F7\u01FF\u020B\u020E\u0215\u021E\u0221\u0224\u0229\u022C\u023B"; public static readonly _serializedATN: string = Utils.join( [ painless_parser._serializedATNSegment0, @@ -5184,10 +5201,11 @@ export class VariableContext extends PrimaryContext { } } export class CalllocalContext extends PrimaryContext { - public ID(): TerminalNode { return this.getToken(painless_parser.ID, 0); } public arguments(): ArgumentsContext { return this.getRuleContext(0, ArgumentsContext); } + public ID(): TerminalNode | undefined { return this.tryGetToken(painless_parser.ID, 0); } + public DOLLAR(): TerminalNode | undefined { return this.tryGetToken(painless_parser.DOLLAR, 0); } constructor(ctx: PrimaryContext) { super(ctx.parent, ctx.invokingState); this.copyFrom(ctx); diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 607afa266da83..647d421819597 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -8987,13 +8987,6 @@ const BootstrapCommand = { }, { prefix: '[vscode]', debug: false - }); - await Object(_utils_child_process__WEBPACK_IMPORTED_MODULE_3__["spawnStreaming"])(process.execPath, ['scripts/build_ts_refs', '--ignore-type-failures'], { - cwd: kbn.getAbsolute(), - env: process.env - }, { - prefix: '[ts refs]', - debug: false }); // send timings await reporter.timings({ @@ -10420,50 +10413,44 @@ const mimicFn = __webpack_require__(153); const calledFunctions = new WeakMap(); -const oneTime = (fn, options = {}) => { - if (typeof fn !== 'function') { +const onetime = (function_, options = {}) => { + if (typeof function_ !== 'function') { throw new TypeError('Expected a function'); } - let ret; - let isCalled = false; + let returnValue; let callCount = 0; - const functionName = fn.displayName || fn.name || ''; + const functionName = function_.displayName || function_.name || ''; - const onetime = function (...args) { + const onetime = function (...arguments_) { calledFunctions.set(onetime, ++callCount); - if (isCalled) { - if (options.throw === true) { - throw new Error(`Function \`${functionName}\` can only be called once`); - } - - return ret; + if (callCount === 1) { + returnValue = function_.apply(this, arguments_); + function_ = null; + } else if (options.throw === true) { + throw new Error(`Function \`${functionName}\` can only be called once`); } - isCalled = true; - ret = fn.apply(this, args); - fn = null; - - return ret; + return returnValue; }; - mimicFn(onetime, fn); + mimicFn(onetime, function_); calledFunctions.set(onetime, callCount); return onetime; }; -module.exports = oneTime; +module.exports = onetime; // TODO: Remove this for the next major release -module.exports.default = oneTime; +module.exports.default = onetime; -module.exports.callCount = fn => { - if (!calledFunctions.has(fn)) { - throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`); +module.exports.callCount = function_ => { + if (!calledFunctions.has(function_)) { + throw new Error(`The given function \`${function_.name}\` is not wrapped by the \`onetime\` package`); } - return calledFunctions.get(fn); + return calledFunctions.get(function_); }; @@ -11187,158 +11174,203 @@ module.exports = { // Note: since nyc uses this module to output coverage, any lines // that are in the direct sync flow of nyc's outputCoverage are // ignored, since we can never get coverage for them. -var assert = __webpack_require__(162) -var signals = __webpack_require__(163) - -var EE = __webpack_require__(164) +// grab a reference to node's real process object right away +var process = global.process + +const processOk = function (process) { + return process && + typeof process === 'object' && + typeof process.removeListener === 'function' && + typeof process.emit === 'function' && + typeof process.reallyExit === 'function' && + typeof process.listeners === 'function' && + typeof process.kill === 'function' && + typeof process.pid === 'number' && + typeof process.on === 'function' +} + +// some kind of non-node environment, just no-op /* istanbul ignore if */ -if (typeof EE !== 'function') { - EE = EE.EventEmitter -} - -var emitter -if (process.__signal_exit_emitter__) { - emitter = process.__signal_exit_emitter__ +if (!processOk(process)) { + module.exports = function () { + return function () {} + } } else { - emitter = process.__signal_exit_emitter__ = new EE() - emitter.count = 0 - emitter.emitted = {} -} + var assert = __webpack_require__(162) + var signals = __webpack_require__(163) + var isWin = /^win/i.test(process.platform) -// Because this emitter is a global, we have to check to see if a -// previous version of this library failed to enable infinite listeners. -// I know what you're about to say. But literally everything about -// signal-exit is a compromise with evil. Get used to it. -if (!emitter.infinite) { - emitter.setMaxListeners(Infinity) - emitter.infinite = true -} - -module.exports = function (cb, opts) { - assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler') + var EE = __webpack_require__(164) + /* istanbul ignore if */ + if (typeof EE !== 'function') { + EE = EE.EventEmitter + } - if (loaded === false) { - load() + var emitter + if (process.__signal_exit_emitter__) { + emitter = process.__signal_exit_emitter__ + } else { + emitter = process.__signal_exit_emitter__ = new EE() + emitter.count = 0 + emitter.emitted = {} } - var ev = 'exit' - if (opts && opts.alwaysLast) { - ev = 'afterexit' + // Because this emitter is a global, we have to check to see if a + // previous version of this library failed to enable infinite listeners. + // I know what you're about to say. But literally everything about + // signal-exit is a compromise with evil. Get used to it. + if (!emitter.infinite) { + emitter.setMaxListeners(Infinity) + emitter.infinite = true } - var remove = function () { - emitter.removeListener(ev, cb) - if (emitter.listeners('exit').length === 0 && - emitter.listeners('afterexit').length === 0) { - unload() + module.exports = function (cb, opts) { + /* istanbul ignore if */ + if (!processOk(global.process)) { + return function () {} } - } - emitter.on(ev, cb) + assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler') - return remove -} + if (loaded === false) { + load() + } -module.exports.unload = unload -function unload () { - if (!loaded) { - return - } - loaded = false + var ev = 'exit' + if (opts && opts.alwaysLast) { + ev = 'afterexit' + } - signals.forEach(function (sig) { - try { - process.removeListener(sig, sigListeners[sig]) - } catch (er) {} - }) - process.emit = originalProcessEmit - process.reallyExit = originalProcessReallyExit - emitter.count -= 1 -} + var remove = function () { + emitter.removeListener(ev, cb) + if (emitter.listeners('exit').length === 0 && + emitter.listeners('afterexit').length === 0) { + unload() + } + } + emitter.on(ev, cb) -function emit (event, code, signal) { - if (emitter.emitted[event]) { - return + return remove } - emitter.emitted[event] = true - emitter.emit(event, code, signal) -} - -// { : , ... } -var sigListeners = {} -signals.forEach(function (sig) { - sigListeners[sig] = function listener () { - // If there are no other listeners, an exit is coming! - // Simplest way: remove us and then re-send the signal. - // We know that this will kill the process, so we can - // safely emit now. - var listeners = process.listeners(sig) - if (listeners.length === emitter.count) { - unload() - emit('exit', null, sig) - /* istanbul ignore next */ - emit('afterexit', null, sig) - /* istanbul ignore next */ - process.kill(process.pid, sig) + + var unload = function unload () { + if (!loaded || !processOk(global.process)) { + return } - } -}) + loaded = false -module.exports.signals = function () { - return signals -} + signals.forEach(function (sig) { + try { + process.removeListener(sig, sigListeners[sig]) + } catch (er) {} + }) + process.emit = originalProcessEmit + process.reallyExit = originalProcessReallyExit + emitter.count -= 1 + } + module.exports.unload = unload -module.exports.load = load + var emit = function emit (event, code, signal) { + /* istanbul ignore if */ + if (emitter.emitted[event]) { + return + } + emitter.emitted[event] = true + emitter.emit(event, code, signal) + } -var loaded = false + // { : , ... } + var sigListeners = {} + signals.forEach(function (sig) { + sigListeners[sig] = function listener () { + /* istanbul ignore if */ + if (!processOk(global.process)) { + return + } + // If there are no other listeners, an exit is coming! + // Simplest way: remove us and then re-send the signal. + // We know that this will kill the process, so we can + // safely emit now. + var listeners = process.listeners(sig) + if (listeners.length === emitter.count) { + unload() + emit('exit', null, sig) + /* istanbul ignore next */ + emit('afterexit', null, sig) + /* istanbul ignore next */ + if (isWin && sig === 'SIGHUP') { + // "SIGHUP" throws an `ENOSYS` error on Windows, + // so use a supported signal instead + sig = 'SIGINT' + } + /* istanbul ignore next */ + process.kill(process.pid, sig) + } + } + }) -function load () { - if (loaded) { - return + module.exports.signals = function () { + return signals } - loaded = true - // This is the number of onSignalExit's that are in play. - // It's important so that we can count the correct number of - // listeners on signals, and don't wait for the other one to - // handle it instead of us. - emitter.count += 1 + var loaded = false - signals = signals.filter(function (sig) { - try { - process.on(sig, sigListeners[sig]) - return true - } catch (er) { - return false + var load = function load () { + if (loaded || !processOk(global.process)) { + return } - }) + loaded = true - process.emit = processEmit - process.reallyExit = processReallyExit -} + // This is the number of onSignalExit's that are in play. + // It's important so that we can count the correct number of + // listeners on signals, and don't wait for the other one to + // handle it instead of us. + emitter.count += 1 -var originalProcessReallyExit = process.reallyExit -function processReallyExit (code) { - process.exitCode = code || 0 - emit('exit', process.exitCode, null) - /* istanbul ignore next */ - emit('afterexit', process.exitCode, null) - /* istanbul ignore next */ - originalProcessReallyExit.call(process, process.exitCode) -} + signals = signals.filter(function (sig) { + try { + process.on(sig, sigListeners[sig]) + return true + } catch (er) { + return false + } + }) -var originalProcessEmit = process.emit -function processEmit (ev, arg) { - if (ev === 'exit') { - if (arg !== undefined) { - process.exitCode = arg + process.emit = processEmit + process.reallyExit = processReallyExit + } + module.exports.load = load + + var originalProcessReallyExit = process.reallyExit + var processReallyExit = function processReallyExit (code) { + /* istanbul ignore if */ + if (!processOk(global.process)) { + return } - var ret = originalProcessEmit.apply(this, arguments) + process.exitCode = code || /* istanbul ignore next */ 0 emit('exit', process.exitCode, null) /* istanbul ignore next */ emit('afterexit', process.exitCode, null) - return ret - } else { - return originalProcessEmit.apply(this, arguments) + /* istanbul ignore next */ + originalProcessReallyExit.call(process, process.exitCode) + } + + var originalProcessEmit = process.emit + var processEmit = function processEmit (ev, arg) { + if (ev === 'exit' && processOk(global.process)) { + /* istanbul ignore else */ + if (arg !== undefined) { + process.exitCode = arg + } + var ret = originalProcessEmit.apply(this, arguments) + /* istanbul ignore next */ + emit('exit', process.exitCode, null) + /* istanbul ignore next */ + emit('afterexit', process.exitCode, null) + /* istanbul ignore next */ + return ret + } else { + return originalProcessEmit.apply(this, arguments) + } } } @@ -13928,8 +13960,9 @@ var assert = __webpack_require__(162); var debug = __webpack_require__(204); // Create handlers that pass events from native requests +var events = ["abort", "aborted", "connect", "error", "socket", "timeout"]; var eventHandlers = Object.create(null); -["abort", "aborted", "connect", "error", "socket", "timeout"].forEach(function (event) { +events.forEach(function (event) { eventHandlers[event] = function (arg1, arg2, arg3) { this._redirectable.emit(event, arg1, arg2, arg3); }; @@ -13938,7 +13971,7 @@ var eventHandlers = Object.create(null); // Error types with codes var RedirectionError = createErrorType( "ERR_FR_REDIRECTION_FAILURE", - "" + "Redirected request failed" ); var TooManyRedirectsError = createErrorType( "ERR_FR_TOO_MANY_REDIRECTS", @@ -13982,6 +14015,11 @@ function RedirectableRequest(options, responseCallback) { } RedirectableRequest.prototype = Object.create(Writable.prototype); +RedirectableRequest.prototype.abort = function () { + abortRequest(this._currentRequest); + this.emit("abort"); +}; + // Writes buffered data to the current native request RedirectableRequest.prototype.write = function (data, encoding, callback) { // Writing is not allowed if end has been called @@ -14061,40 +14099,72 @@ RedirectableRequest.prototype.removeHeader = function (name) { // Global timeout for all underlying requests RedirectableRequest.prototype.setTimeout = function (msecs, callback) { + var self = this; + + // Destroys the socket on timeout + function destroyOnTimeout(socket) { + socket.setTimeout(msecs); + socket.removeListener("timeout", socket.destroy); + socket.addListener("timeout", socket.destroy); + } + + // Sets up a timer to trigger a timeout event + function startTimer(socket) { + if (self._timeout) { + clearTimeout(self._timeout); + } + self._timeout = setTimeout(function () { + self.emit("timeout"); + clearTimer(); + }, msecs); + destroyOnTimeout(socket); + } + + // Stops a timeout from triggering + function clearTimer() { + // Clear the timeout + if (self._timeout) { + clearTimeout(self._timeout); + self._timeout = null; + } + + // Clean up all attached listeners + self.removeListener("abort", clearTimer); + self.removeListener("error", clearTimer); + self.removeListener("response", clearTimer); + if (callback) { + self.removeListener("timeout", callback); + } + if (!self.socket) { + self._currentRequest.removeListener("socket", startTimer); + } + } + + // Attach callback if passed if (callback) { - this.once("timeout", callback); + this.on("timeout", callback); } + // Start the timer if or when the socket is opened if (this.socket) { - startTimer(this, msecs); + startTimer(this.socket); } else { - var self = this; - this._currentRequest.once("socket", function () { - startTimer(self, msecs); - }); + this._currentRequest.once("socket", startTimer); } - this.once("response", clearTimer); - this.once("error", clearTimer); + // Clean up on events + this.on("socket", destroyOnTimeout); + this.on("abort", clearTimer); + this.on("error", clearTimer); + this.on("response", clearTimer); return this; }; -function startTimer(request, msecs) { - clearTimeout(request._timeout); - request._timeout = setTimeout(function () { - request.emit("timeout"); - }, msecs); -} - -function clearTimer() { - clearTimeout(this._timeout); -} - // Proxy all other public ClientRequest methods [ - "abort", "flushHeaders", "getHeader", + "flushHeaders", "getHeader", "setNoDelay", "setSocketKeepAlive", ].forEach(function (method) { RedirectableRequest.prototype[method] = function (a, b) { @@ -14164,11 +14234,8 @@ RedirectableRequest.prototype._performRequest = function () { // Set up event handlers request._redirectable = this; - for (var event in eventHandlers) { - /* istanbul ignore else */ - if (event) { - request.on(event, eventHandlers[event]); - } + for (var e = 0; e < events.length; e++) { + request.on(events[e], eventHandlers[events[e]]); } // End a redirected request @@ -14222,86 +14289,101 @@ RedirectableRequest.prototype._processResponse = function (response) { // the user agent MAY automatically redirect its request to the URI // referenced by the Location field value, // even if the specific status code is not understood. + + // If the response is not a redirect; return it as-is var location = response.headers.location; - if (location && this._options.followRedirects !== false && - statusCode >= 300 && statusCode < 400) { - // Abort the current request - this._currentRequest.removeAllListeners(); - this._currentRequest.on("error", noop); - this._currentRequest.abort(); - // Discard the remainder of the response to avoid waiting for data - response.destroy(); - - // RFC7231§6.4: A client SHOULD detect and intervene - // in cyclical redirections (i.e., "infinite" redirection loops). - if (++this._redirectCount > this._options.maxRedirects) { - this.emit("error", new TooManyRedirectsError()); - return; - } + if (!location || this._options.followRedirects === false || + statusCode < 300 || statusCode >= 400) { + response.responseUrl = this._currentUrl; + response.redirects = this._redirects; + this.emit("response", response); - // RFC7231§6.4: Automatic redirection needs to done with - // care for methods not known to be safe, […] - // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change - // the request method from POST to GET for the subsequent request. - if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || - // RFC7231§6.4.4: The 303 (See Other) status code indicates that - // the server is redirecting the user agent to a different resource […] - // A user agent can perform a retrieval request targeting that URI - // (a GET or HEAD request if using HTTP) […] - (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) { - this._options.method = "GET"; - // Drop a possible entity and headers related to it - this._requestBodyBuffers = []; - removeMatchingHeaders(/^content-/i, this._options.headers); - } - - // Drop the Host header, as the redirect might lead to a different host - var previousHostName = removeMatchingHeaders(/^host$/i, this._options.headers) || - url.parse(this._currentUrl).hostname; - - // Create the redirected request - var redirectUrl = url.resolve(this._currentUrl, location); - debug("redirecting to", redirectUrl); - this._isRedirect = true; - var redirectUrlParts = url.parse(redirectUrl); - Object.assign(this._options, redirectUrlParts); - - // Drop the Authorization header if redirecting to another host - if (redirectUrlParts.hostname !== previousHostName) { - removeMatchingHeaders(/^authorization$/i, this._options.headers); - } - - // Evaluate the beforeRedirect callback - if (typeof this._options.beforeRedirect === "function") { - var responseDetails = { headers: response.headers }; - try { - this._options.beforeRedirect.call(null, this._options, responseDetails); - } - catch (err) { - this.emit("error", err); - return; - } - this._sanitizeOptions(this._options); - } + // Clean up + this._requestBodyBuffers = []; + return; + } - // Perform the redirected request + // The response is a redirect, so abort the current request + abortRequest(this._currentRequest); + // Discard the remainder of the response to avoid waiting for data + response.destroy(); + + // RFC7231§6.4: A client SHOULD detect and intervene + // in cyclical redirections (i.e., "infinite" redirection loops). + if (++this._redirectCount > this._options.maxRedirects) { + this.emit("error", new TooManyRedirectsError()); + return; + } + + // RFC7231§6.4: Automatic redirection needs to done with + // care for methods not known to be safe, […] + // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change + // the request method from POST to GET for the subsequent request. + if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || + // RFC7231§6.4.4: The 303 (See Other) status code indicates that + // the server is redirecting the user agent to a different resource […] + // A user agent can perform a retrieval request targeting that URI + // (a GET or HEAD request if using HTTP) […] + (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) { + this._options.method = "GET"; + // Drop a possible entity and headers related to it + this._requestBodyBuffers = []; + removeMatchingHeaders(/^content-/i, this._options.headers); + } + + // Drop the Host header, as the redirect might lead to a different host + var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers); + + // If the redirect is relative, carry over the host of the last request + var currentUrlParts = url.parse(this._currentUrl); + var currentHost = currentHostHeader || currentUrlParts.host; + var currentUrl = /^\w+:/.test(location) ? this._currentUrl : + url.format(Object.assign(currentUrlParts, { host: currentHost })); + + // Determine the URL of the redirection + var redirectUrl; + try { + redirectUrl = url.resolve(currentUrl, location); + } + catch (cause) { + this.emit("error", new RedirectionError(cause)); + return; + } + + // Create the redirected request + debug("redirecting to", redirectUrl); + this._isRedirect = true; + var redirectUrlParts = url.parse(redirectUrl); + Object.assign(this._options, redirectUrlParts); + + // Drop confidential headers when redirecting to a less secure protocol + // or to a different domain that is not a superdomain + if (redirectUrlParts.protocol !== currentUrlParts.protocol && + redirectUrlParts.protocol !== "https:" || + redirectUrlParts.host !== currentHost && + !isSubdomain(redirectUrlParts.host, currentHost)) { + removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers); + } + + // Evaluate the beforeRedirect callback + if (typeof this._options.beforeRedirect === "function") { + var responseDetails = { headers: response.headers }; try { - this._performRequest(); + this._options.beforeRedirect.call(null, this._options, responseDetails); } - catch (cause) { - var error = new RedirectionError("Redirected request failed: " + cause.message); - error.cause = cause; - this.emit("error", error); + catch (err) { + this.emit("error", err); + return; } + this._sanitizeOptions(this._options); } - else { - // The response is not a redirect; return it as-is - response.responseUrl = this._currentUrl; - response.redirects = this._redirects; - this.emit("response", response); - // Clean up - this._requestBodyBuffers = []; + // Perform the redirected request + try { + this._performRequest(); + } + catch (cause) { + this.emit("error", new RedirectionError(cause)); } }; @@ -14321,7 +14403,7 @@ function wrap(protocols) { var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol); // Executes a request, following redirects - wrappedProtocol.request = function (input, options, callback) { + function request(input, options, callback) { // Parse parameters if (typeof input === "string") { var urlStr = input; @@ -14356,14 +14438,20 @@ function wrap(protocols) { assert.equal(options.protocol, protocol, "protocol mismatch"); debug("options", options); return new RedirectableRequest(options, callback); - }; + } // Executes a GET request, following redirects - wrappedProtocol.get = function (input, options, callback) { - var request = wrappedProtocol.request(input, options, callback); - request.end(); - return request; - }; + function get(input, options, callback) { + var wrappedRequest = wrappedProtocol.request(input, options, callback); + wrappedRequest.end(); + return wrappedRequest; + } + + // Expose the properties on the wrapped protocol + Object.defineProperties(wrappedProtocol, { + request: { value: request, configurable: true, enumerable: true, writable: true }, + get: { value: get, configurable: true, enumerable: true, writable: true }, + }); }); return exports; } @@ -14399,13 +14487,20 @@ function removeMatchingHeaders(regex, headers) { delete headers[header]; } } - return lastValue; + return (lastValue === null || typeof lastValue === "undefined") ? + undefined : String(lastValue).trim(); } function createErrorType(code, defaultMessage) { - function CustomError(message) { + function CustomError(cause) { Error.captureStackTrace(this, this.constructor); - this.message = message || defaultMessage; + if (!cause) { + this.message = defaultMessage; + } + else { + this.message = defaultMessage + ": " + cause.message; + this.cause = cause; + } } CustomError.prototype = new Error(); CustomError.prototype.constructor = CustomError; @@ -14414,6 +14509,19 @@ function createErrorType(code, defaultMessage) { return CustomError; } +function abortRequest(request) { + for (var e = 0; e < events.length; e++) { + request.removeListener(events[e], eventHandlers[events[e]]); + } + request.on("error", noop); + request.abort(); +} + +function isSubdomain(subdomain, domain) { + const dot = subdomain.length - domain.length - 1; + return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain); +} + // Exports module.exports = wrap({ http: http, https: https }); module.exports.wrap = wrap; @@ -14430,14 +14538,20 @@ module.exports = require("url"); /***/ (function(module, exports, __webpack_require__) { var debug; -try { - /* eslint global-require: off */ - debug = __webpack_require__(205)("follow-redirects"); -} -catch (error) { - debug = function () { /* */ }; -} -module.exports = debug; + +module.exports = function () { + if (!debug) { + try { + /* eslint global-require: off */ + debug = __webpack_require__(205)("follow-redirects"); + } + catch (error) { /* */ } + if (typeof debug !== "function") { + debug = function () { /* */ }; + } + } + debug.apply(null, arguments); +}; /***/ }), @@ -18521,7 +18635,6 @@ function pauseStreams (streams, options) { module.exports = glob -var fs = __webpack_require__(132) var rp = __webpack_require__(245) var minimatch = __webpack_require__(247) var Minimatch = minimatch.Minimatch @@ -18532,8 +18645,6 @@ var assert = __webpack_require__(162) var isAbsolute = __webpack_require__(253) var globSync = __webpack_require__(254) var common = __webpack_require__(255) -var alphasort = common.alphasort -var alphasorti = common.alphasorti var setopts = common.setopts var ownProp = common.ownProp var inflight = __webpack_require__(256) @@ -18984,7 +19095,7 @@ Glob.prototype._readdirInGlobStar = function (abs, cb) { var lstatcb = inflight(lstatkey, lstatcb_) if (lstatcb) - fs.lstat(abs, lstatcb) + self.fs.lstat(abs, lstatcb) function lstatcb_ (er, lstat) { if (er && er.code === 'ENOENT') @@ -19025,7 +19136,7 @@ Glob.prototype._readdir = function (abs, inGlobStar, cb) { } var self = this - fs.readdir(abs, readdirCb(this, abs, cb)) + self.fs.readdir(abs, readdirCb(this, abs, cb)) } function readdirCb (self, abs, cb) { @@ -19229,13 +19340,13 @@ Glob.prototype._stat = function (f, cb) { var self = this var statcb = inflight('stat\0' + abs, lstatcb_) if (statcb) - fs.lstat(abs, statcb) + self.fs.lstat(abs, statcb) function lstatcb_ (er, lstat) { if (lstat && lstat.isSymbolicLink()) { // If it's a symlink, then treat it as the target, unless // the target does not exist, then treat it as a file. - return fs.stat(abs, function (er, stat) { + return self.fs.stat(abs, function (er, stat) { if (er) self._stat2(f, abs, null, lstat, cb) else @@ -20955,7 +21066,6 @@ module.exports.win32 = win32; module.exports = globSync globSync.GlobSync = GlobSync -var fs = __webpack_require__(132) var rp = __webpack_require__(245) var minimatch = __webpack_require__(247) var Minimatch = minimatch.Minimatch @@ -20965,8 +21075,6 @@ var path = __webpack_require__(4) var assert = __webpack_require__(162) var isAbsolute = __webpack_require__(253) var common = __webpack_require__(255) -var alphasort = common.alphasort -var alphasorti = common.alphasorti var setopts = common.setopts var ownProp = common.ownProp var childrenIgnored = common.childrenIgnored @@ -21202,7 +21310,7 @@ GlobSync.prototype._readdirInGlobStar = function (abs) { var lstat var stat try { - lstat = fs.lstatSync(abs) + lstat = this.fs.lstatSync(abs) } catch (er) { if (er.code === 'ENOENT') { // lstat failed, doesn't exist @@ -21239,7 +21347,7 @@ GlobSync.prototype._readdir = function (abs, inGlobStar) { } try { - return this._readdirEntries(abs, fs.readdirSync(abs)) + return this._readdirEntries(abs, this.fs.readdirSync(abs)) } catch (er) { this._readdirError(abs, er) return null @@ -21398,7 +21506,7 @@ GlobSync.prototype._stat = function (f) { if (!stat) { var lstat try { - lstat = fs.lstatSync(abs) + lstat = this.fs.lstatSync(abs) } catch (er) { if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { this.statCache[abs] = false @@ -21408,7 +21516,7 @@ GlobSync.prototype._stat = function (f) { if (lstat && lstat.isSymbolicLink()) { try { - stat = fs.statSync(abs) + stat = this.fs.statSync(abs) } catch (er) { stat = lstat } @@ -21444,8 +21552,6 @@ GlobSync.prototype._makeAbs = function (f) { /* 255 */ /***/ (function(module, exports, __webpack_require__) { -exports.alphasort = alphasort -exports.alphasorti = alphasorti exports.setopts = setopts exports.ownProp = ownProp exports.makeAbs = makeAbs @@ -21458,17 +21564,14 @@ function ownProp (obj, field) { return Object.prototype.hasOwnProperty.call(obj, field) } +var fs = __webpack_require__(132) var path = __webpack_require__(4) var minimatch = __webpack_require__(247) var isAbsolute = __webpack_require__(253) var Minimatch = minimatch.Minimatch -function alphasorti (a, b) { - return a.toLowerCase().localeCompare(b.toLowerCase()) -} - function alphasort (a, b) { - return a.localeCompare(b) + return a.localeCompare(b, 'en') } function setupIgnores (self, options) { @@ -21527,6 +21630,7 @@ function setopts (self, pattern, options) { self.stat = !!options.stat self.noprocess = !!options.noprocess self.absolute = !!options.absolute + self.fs = options.fs || fs self.maxLength = options.maxLength || Infinity self.cache = options.cache || Object.create(null) @@ -21596,7 +21700,7 @@ function finish (self) { all = Object.keys(all) if (!self.nosort) - all = all.sort(self.nocase ? alphasorti : alphasort) + all = all.sort(alphasort) // at *some* point we statted all of these if (self.mark) { diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index e7b8cad7ebc16..0b3141ab9a5a9 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -137,16 +137,6 @@ export const BootstrapCommand: ICommand = { { prefix: '[vscode]', debug: false } ); - await spawnStreaming( - process.execPath, - ['scripts/build_ts_refs', '--ignore-type-failures'], - { - cwd: kbn.getAbsolute(), - env: process.env, - }, - { prefix: '[ts refs]', debug: false } - ); - // send timings await reporter.timings({ upstreamBranch: kbn.kibanaProject.json.branch, diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/default_import_comments_array/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_import_comments_array/index.test.ts new file mode 100644 index 0000000000000..8584edfabeebd --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_import_comments_array/index.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { pipe } from 'fp-ts/lib/pipeable'; +import { left } from 'fp-ts/lib/Either'; +import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { ImportCommentsArray } from '../import_comment'; +import { DefaultImportCommentsArray } from '../default_import_comments_array'; +import { getCommentsArrayMock } from '../comment/index.mock'; +import { getCreateCommentsArrayMock } from '../create_comment/index.mock'; + +describe('default_import_comments_array', () => { + test('it should pass validation when supplied an empty array', () => { + const payload: ImportCommentsArray = []; + const decoded = DefaultImportCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should pass validation when supplied an array of comments', () => { + const payload: ImportCommentsArray = getCommentsArrayMock(); + const decoded = DefaultImportCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should pass validation when supplied an array of new comments', () => { + const payload: ImportCommentsArray = getCreateCommentsArrayMock(); + const decoded = DefaultImportCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should pass validation when supplied an array of new and existing comments', () => { + const payload: ImportCommentsArray = [ + ...getCommentsArrayMock(), + ...getCreateCommentsArrayMock(), + ]; + const decoded = DefaultImportCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when supplied an array of numbers', () => { + const payload = [1]; + const decoded = DefaultImportCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1" supplied to "DefaultImportComments"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when supplied an array of strings', () => { + const payload = ['some string']; + const decoded = DefaultImportCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "some string" supplied to "DefaultImportComments"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should return a default array entry', () => { + const payload = null; + const decoded = DefaultImportCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual([]); + }); +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/default_import_comments_array/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_import_comments_array/index.ts new file mode 100644 index 0000000000000..2e3ec1e00375a --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_import_comments_array/index.ts @@ -0,0 +1,27 @@ +/* + * 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 * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; +import { importComment, ImportCommentsArray } from '../import_comment'; + +/** + * Types the DefaultImportCommentsArray as: + * - If null or undefined, then a default array of type ImportCommentsArray will be set + */ +export const DefaultImportCommentsArray = new t.Type< + ImportCommentsArray, + ImportCommentsArray, + unknown +>( + 'DefaultImportComments', + t.array(importComment).is, + (input, context): Either => + input == null ? t.success([]) : t.array(importComment).validate(input, context), + t.identity +); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.test.ts index fa6613538b18e..05c4e91355da4 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.test.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.test.ts @@ -38,7 +38,7 @@ describe('default_update_comments_array', () => { const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "1" supplied to "DefaultUpdateComments"', ]); expect(message.schema).toEqual({}); }); @@ -49,7 +49,7 @@ describe('default_update_comments_array', () => { const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "some string" supplied to "DefaultUpdateComments"', ]); expect(message.schema).toEqual({}); }); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.ts index 26bfdad597100..cc70ba1d095b8 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/default_update_comments_array/index.ts @@ -11,17 +11,17 @@ import { Either } from 'fp-ts/lib/Either'; import { updateCommentsArray, UpdateCommentsArray } from '../update_comment'; /** - * Types the DefaultCommentsUpdate as: - * - If null or undefined, then a default array of type entry will be set + * Types the DefaultUpdateComments as: + * - If null or undefined, then a default array of type UpdateCommentsArray will be set */ export const DefaultUpdateCommentsArray = new t.Type< UpdateCommentsArray, UpdateCommentsArray, unknown >( - 'DefaultCreateComments', + 'DefaultUpdateComments', updateCommentsArray.is, - (input): Either => - input == null ? t.success([]) : updateCommentsArray.decode(input), + (input, context): Either => + input == null ? t.success([]) : updateCommentsArray.validate(input, context), t.identity ); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/import_comment/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/import_comment/index.test.ts new file mode 100644 index 0000000000000..53383d80f441d --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/import_comment/index.test.ts @@ -0,0 +1,135 @@ +/* + * 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 { pipe } from 'fp-ts/lib/pipeable'; +import { left } from 'fp-ts/lib/Either'; +import { getCommentsArrayMock, getCommentsMock } from '../comment/index.mock'; +import { getCreateCommentsArrayMock } from '../create_comment/index.mock'; +import { + importComment, + ImportCommentsArray, + importCommentsArray, + ImportCommentsArrayOrUndefined, + importCommentsArrayOrUndefined, +} from '.'; +import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +describe('ImportComment', () => { + describe('importComment', () => { + test('it passes validation with a typical comment', () => { + const payload = getCommentsMock(); + const decoded = importComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it passes validation with a new comment', () => { + const payload = { comment: 'new comment' }; + const decoded = importComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it fails validation when undefined', () => { + const payload = undefined; + const decoded = importComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})"', + ]); + expect(message.schema).toEqual({}); + }); + }); + + describe('importCommentsArray', () => { + test('it passes validation an array of Comment', () => { + const payload = getCommentsArrayMock(); + const decoded = importCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it passes validation an array of CreateComment', () => { + const payload = getCreateCommentsArrayMock(); + const decoded = importCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it passes validation an array of Comment and CreateComment', () => { + const payload = [...getCommentsArrayMock(), ...getCreateCommentsArrayMock()]; + const decoded = importCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it fails validation when undefined', () => { + const payload = undefined; + const decoded = importCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "Array<(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})>"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it fails validation when array includes non ImportComment types', () => { + const payload = [1] as unknown as ImportCommentsArray; + const decoded = importCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1" supplied to "Array<(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})>"', + ]); + expect(message.schema).toEqual({}); + }); + }); + + describe('importCommentsArrayOrUndefined', () => { + test('it passes validation an array of ImportComment', () => { + const payload = [...getCommentsArrayMock(), ...getCreateCommentsArrayMock()]; + const decoded = importCommentsArrayOrUndefined.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it passes validation when undefined', () => { + const payload = undefined; + const decoded = importCommentsArrayOrUndefined.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it fails validation when array includes non ImportComment types', () => { + const payload = [1] as unknown as ImportCommentsArrayOrUndefined; + const decoded = importCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1" supplied to "Array<(({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: NonEmptyString |})>"', + ]); + expect(message.schema).toEqual({}); + }); + }); +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/import_comment/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/import_comment/index.ts new file mode 100644 index 0000000000000..43a0755bb5b51 --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/import_comment/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 * as t from 'io-ts'; +import { createComment } from '../create_comment'; +import { comment } from '../comment'; + +export const importComment = t.union([comment, createComment]); + +export type ImportComment = t.TypeOf; +export const importCommentsArray = t.array(importComment); +export type ImportCommentsArray = t.TypeOf; +export const importCommentsArrayOrUndefined = t.union([importCommentsArray, t.undefined]); +export type ImportCommentsArrayOrUndefined = t.TypeOf; diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/index.ts index 98c160e2c4302..fadf00fa7a40b 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/common/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/index.ts @@ -13,6 +13,7 @@ export * from './created_by'; export * from './cursor'; export * from './default_namespace'; export * from './default_namespace_array'; +export * from './default_import_comments_array'; export * from './description'; export * from './deserializer'; export * from './endpoint'; @@ -29,6 +30,7 @@ export * from './exception_list_item_type'; export * from './filter'; export * from './id'; export * from './immutable'; +export * from './import_comment'; export * from './item_id'; export * from './list_id'; export * from './list_operator'; diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/update_comment/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/update_comment/index.test.ts index 7ddbdf31ca317..04a0d49306b36 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/common/update_comment/index.test.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/update_comment/index.test.ts @@ -19,7 +19,7 @@ import { } from '.'; import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -describe('CommentsUpdate', () => { +describe('UpdateComment', () => { describe('updateComment', () => { test('it should pass validation when supplied typical comment update', () => { const payload = getUpdateCommentMock(); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.test.ts index d202f65b57ab5..a5cb57dfb56a8 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.test.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.test.ts @@ -15,6 +15,7 @@ import { getImportExceptionsListItemSchemaDecodedMock, getImportExceptionsListItemSchemaMock, } from './index.mock'; +import { getCommentsArrayMock } from '../../common/comment/index.mock'; describe('import_list_item_schema', () => { test('it should validate a typical item request', () => { @@ -27,6 +28,35 @@ describe('import_list_item_schema', () => { expect(message.schema).toEqual(getImportExceptionsListItemSchemaDecodedMock()); }); + test('it should validate a typical item request with comments', () => { + const payload = { + ...getImportExceptionsListItemSchemaMock(), + comments: getCommentsArrayMock(), + }; + const decoded = importExceptionListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual({ + ...getImportExceptionsListItemSchemaDecodedMock(), + comments: [ + { + comment: 'some old comment', + created_at: '2020-04-20T15:25:31.830Z', + created_by: 'some user', + id: 'uuid_here', + }, + { + comment: 'some old comment', + created_at: '2020-04-20T15:25:31.830Z', + created_by: 'some user', + id: 'uuid_here', + }, + ], + }); + }); + test('it should NOT accept an undefined for "item_id"', () => { const payload: Partial> = getImportExceptionsListItemSchemaMock(); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.ts index 3da30a21a0115..dc5a48597211e 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/import_exception_item_schema/index.ts @@ -29,8 +29,8 @@ import { nonEmptyEntriesArray } from '../../common/non_empty_entries_array'; import { exceptionListItemType } from '../../common/exception_list_item_type'; import { ItemId } from '../../common/item_id'; import { EntriesArray } from '../../common/entries'; -import { CreateCommentsArray } from '../../common/create_comment'; -import { DefaultCreateCommentsArray } from '../../common/default_create_comments_array'; +import { DefaultImportCommentsArray } from '../../common/default_import_comments_array'; +import { ImportCommentsArray } from '../../common'; /** * Differences from this and the createExceptionsListItemSchema are @@ -56,7 +56,7 @@ export const importExceptionListItemSchema = t.intersection([ t.exact( t.partial({ id, // defaults to undefined if not set during decode - comments: DefaultCreateCommentsArray, // defaults to empty array if not set during decode + comments: DefaultImportCommentsArray, // defaults to empty array if not set during decode created_at, // defaults undefined if not set during decode updated_at, // defaults undefined if not set during decode created_by, // defaults undefined if not set during decode @@ -78,7 +78,7 @@ export type ImportExceptionListItemSchemaDecoded = Omit< ImportExceptionListItemSchema, 'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' > & { - comments: CreateCommentsArray; + comments: ImportCommentsArray; tags: Tags; item_id: ItemId; entries: EntriesArray; diff --git a/src/core/public/notifications/toasts/toasts_api.test.ts b/src/core/public/notifications/toasts/toasts_api.test.ts index 87d703d5b6814..35f141a995d06 100644 --- a/src/core/public/notifications/toasts/toasts_api.test.ts +++ b/src/core/public/notifications/toasts/toasts_api.test.ts @@ -19,13 +19,13 @@ async function getCurrentToasts(toasts: ToastsApi) { function uiSettingsMock() { const mock = uiSettingsServiceMock.createSetupContract(); - mock.get.mockImplementation(() => (config: string) => { + mock.get.mockImplementation((config: string) => { switch (config) { case 'notifications:lifetime:info': return 5000; case 'notifications:lifetime:warning': return 10000; - case 'notification:lifetime:error': + case 'notifications:lifetime:error': return 30000; default: throw new Error(`Accessing ${config} is not supported in the mock.`); @@ -113,6 +113,12 @@ describe('#add()', () => { const toasts = new ToastsApi(toastDeps()); expect(toasts.add('foo')).toHaveProperty('title', 'foo'); }); + + it('fallbacks to default values for undefined properties', async () => { + const toasts = new ToastsApi(toastDeps()); + const toast = toasts.add({ title: 'foo', toastLifeTimeMs: undefined }); + expect(toast.toastLifeTimeMs).toEqual(5000); + }); }); describe('#remove()', () => { @@ -145,6 +151,12 @@ describe('#addInfo()', () => { expect(currentToasts[0].toastLifeTimeMs).toBe(1); expect(currentToasts[0]).toBe(toast); }); + + it('fallbacks to default values for undefined properties', async () => { + const toasts = new ToastsApi(toastDeps()); + const toast = toasts.addInfo({ title: 'foo', toastLifeTimeMs: undefined }); + expect(toast.toastLifeTimeMs).toEqual(5000); + }); }); describe('#addSuccess()', () => { @@ -159,6 +171,12 @@ describe('#addSuccess()', () => { const currentToasts = await getCurrentToasts(toasts); expect(currentToasts[0]).toBe(toast); }); + + it('fallbacks to default values for undefined properties', async () => { + const toasts = new ToastsApi(toastDeps()); + const toast = toasts.addSuccess({ title: 'foo', toastLifeTimeMs: undefined }); + expect(toast.toastLifeTimeMs).toEqual(5000); + }); }); describe('#addWarning()', () => { @@ -173,6 +191,12 @@ describe('#addWarning()', () => { const currentToasts = await getCurrentToasts(toasts); expect(currentToasts[0]).toBe(toast); }); + + it('fallbacks to default values for undefined properties', async () => { + const toasts = new ToastsApi(toastDeps()); + const toast = toasts.addWarning({ title: 'foo', toastLifeTimeMs: undefined }); + expect(toast.toastLifeTimeMs).toEqual(10000); + }); }); describe('#addDanger()', () => { @@ -187,6 +211,12 @@ describe('#addDanger()', () => { const currentToasts = await getCurrentToasts(toasts); expect(currentToasts[0]).toBe(toast); }); + + it('fallbacks to default values for undefined properties', async () => { + const toasts = new ToastsApi(toastDeps()); + const toast = toasts.addDanger({ title: 'foo', toastLifeTimeMs: undefined }); + expect(toast.toastLifeTimeMs).toEqual(10000); + }); }); describe('#addError', () => { diff --git a/src/core/public/notifications/toasts/toasts_api.tsx b/src/core/public/notifications/toasts/toasts_api.tsx index 5e5d9d5615fd4..5aaea1ca90a56 100644 --- a/src/core/public/notifications/toasts/toasts_api.tsx +++ b/src/core/public/notifications/toasts/toasts_api.tsx @@ -9,6 +9,7 @@ import { EuiGlobalToastListToast as EuiToast } from '@elastic/eui'; import React from 'react'; import * as Rx from 'rxjs'; +import { omitBy, isUndefined } from 'lodash'; import { ErrorToast } from './error_toast'; import { MountPoint } from '../../types'; @@ -75,7 +76,7 @@ const normalizeToast = (toastOrTitle: ToastInput): ToastInputFields => { title: toastOrTitle, }; } - return toastOrTitle; + return omitBy(toastOrTitle, isUndefined); }; /** diff --git a/src/core/server/deprecations/deprecations_registry.test.ts b/src/core/server/deprecations/deprecations_registry.test.ts index 0e4f48b18a0a9..0f7e2704d934a 100644 --- a/src/core/server/deprecations/deprecations_registry.test.ts +++ b/src/core/server/deprecations/deprecations_registry.test.ts @@ -65,6 +65,25 @@ describe('DeprecationsRegistry', () => { ]); }); + it('rejects deprecations when reaching the timeout', async () => { + const deprecationsRegistry = new DeprecationsRegistry({ timeout: 100 }); + const mockContext = {} as unknown as GetDeprecationsContext; + const deprecationsConfigA = { + getDeprecations: jest.fn().mockReturnValue(new Promise(() => {})), + }; + deprecationsRegistry.registerDeprecations(deprecationsConfigA); + const deprecations = await deprecationsRegistry.getDeprecations(mockContext); + expect(deprecations).toStrictEqual([ + { + status: 'rejected', + reason: expect.any(Error), + }, + ]); + expect((deprecations[0] as PromiseRejectedResult).reason.message).toEqual( + 'Deprecations did not resolve in 10sec.' + ); + }); + it('passes dependencies to registered getDeprecations function', async () => { const deprecationsRegistry = new DeprecationsRegistry(); const mockContext = {} as unknown as GetDeprecationsContext; diff --git a/src/core/server/deprecations/deprecations_registry.ts b/src/core/server/deprecations/deprecations_registry.ts index cc05473923ac8..e979bb94712e6 100644 --- a/src/core/server/deprecations/deprecations_registry.ts +++ b/src/core/server/deprecations/deprecations_registry.ts @@ -6,15 +6,23 @@ * Side Public License, v 1. */ +import { withTimeout, isPromise } from '@kbn/std'; import type { DeprecationsDetails, RegisterDeprecationsConfig, GetDeprecationsContext, } from './types'; +const MsInSec = 1000; + export class DeprecationsRegistry { + private readonly timeout: number; private readonly deprecationContexts: RegisterDeprecationsConfig[] = []; + constructor({ timeout = 10 * MsInSec }: { timeout?: number } = {}) { + this.timeout = timeout; + } + public registerDeprecations = (deprecationContext: RegisterDeprecationsConfig) => { if (typeof deprecationContext.getDeprecations !== 'function') { throw new Error(`getDeprecations must be a function in registerDeprecations(context)`); @@ -27,9 +35,21 @@ export class DeprecationsRegistry { dependencies: GetDeprecationsContext ): Promise>> => { return await Promise.allSettled( - this.deprecationContexts.map( - async (deprecationContext) => await deprecationContext.getDeprecations(dependencies) - ) + this.deprecationContexts.map(async (deprecationContext) => { + const maybePromise = deprecationContext.getDeprecations(dependencies); + if (isPromise(maybePromise)) { + const resultOrTimeout = await withTimeout({ + promise: maybePromise, + timeoutMs: this.timeout, + }); + if (resultOrTimeout.timedout) { + throw new Error('Deprecations did not resolve in 10sec.'); + } + return resultOrTimeout.value; + } else { + return maybePromise; + } + }) ); }; } diff --git a/src/core/server/execution_context/execution_context_container.test.ts b/src/core/server/execution_context/execution_context_container.test.ts index 8e9f46ee78a68..678c2e7134eca 100644 --- a/src/core/server/execution_context/execution_context_container.test.ts +++ b/src/core/server/execution_context/execution_context_container.test.ts @@ -91,16 +91,40 @@ describe('KibanaExecutionContext', () => { expect(value).toBe('type:name:41;child-test-type:child-test-name:42'); }); - it('returns an escaped string representation of provided execution contextStringified', () => { + it('returns an escaped string representation of provided execution context', () => { const context: KibanaExecutionContext = { id: 'Visualization☺漢字', + type: 'test☺type', + name: 'test漢name', + description: 'test字description', + }; + + const value = new ExecutionContextContainer(context).toString(); + expect(value).toBe( + 'test%E2%98%BAtype:test%E6%BC%A2name:Visualization%E2%98%BA%E6%BC%A2%E5%AD%97' + ); + }); + + it('returns an escaped string representation of provided execution context parent', () => { + const parentContext: KibanaExecutionContext = { + id: 'Dashboard☺漢字', + type: 'test☺type', + name: 'test漢name', + description: 'parent-descripton', + }; + const parentContainer = new ExecutionContextContainer(parentContext); + + const context: KibanaExecutionContext = { + id: 'Visualization', type: 'test-type', name: 'test-name', description: 'test-description', }; - const value = new ExecutionContextContainer(context).toString(); - expect(value).toBe('test-type:test-name:Visualization%E2%98%BA%E6%BC%A2%E5%AD%97'); + const value = new ExecutionContextContainer(context, parentContainer).toString(); + expect(value).toBe( + 'test%E2%98%BAtype:test%E6%BC%A2name:Dashboard%E2%98%BA%E6%BC%A2%E5%AD%97;test-type:test-name:Visualization' + ); }); it('trims a string representation of provided execution context if it is bigger max allowed size', () => { diff --git a/src/core/server/execution_context/execution_context_container.ts b/src/core/server/execution_context/execution_context_container.ts index 066248a26ad7b..de895bcff5ecb 100644 --- a/src/core/server/execution_context/execution_context_container.ts +++ b/src/core/server/execution_context/execution_context_container.ts @@ -50,18 +50,23 @@ export interface IExecutionContextContainer { } function stringify(ctx: KibanaExecutionContext): string { - const stringifiedCtx = `${ctx.type}:${ctx.name}:${encodeURIComponent(ctx.id!)}`; + const stringifiedCtx = `${encodeURIComponent(ctx.type)}:${encodeURIComponent( + ctx.name + )}:${encodeURIComponent(ctx.id!)}`; return ctx.child ? `${stringifiedCtx};${stringify(ctx.child)}` : stringifiedCtx; } export class ExecutionContextContainer implements IExecutionContextContainer { readonly #context: Readonly; + constructor(context: KibanaExecutionContext, parent?: IExecutionContextContainer) { this.#context = parent ? { ...parent.toJSON(), child: context } : context; } + toString(): string { return enforceMaxLength(stringify(this.#context)); } + toJSON() { return this.#context; } diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index c4fc88dd04dc9..15813529c358b 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -18,6 +18,13 @@ const parentContext = { description: 'test-description', }; +const withUtf8CharsContext = { + type: 'test字type', + name: 'test漢字name', + id: '9000☺', + description: 'test-description', +}; + describe('trace', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: ReturnType; @@ -384,6 +391,35 @@ describe('trace', () => { expect(response.body).toEqual(parentContext); }); + it('supports UTF-8 characters', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping( + {}, + { meta: true } + ); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', 'utf-test') + .set(new ExecutionContextContainer(withUtf8CharsContext).toHeader()) + .expect(200); + + const rawOpaqueId = response.body['x-opaque-id']; + expect(rawOpaqueId).toEqual( + 'utf-test;kibana:test%E5%AD%97type:test%E6%BC%A2%E5%AD%97name:9000%E2%98%BA' + ); + expect(decodeURIComponent(rawOpaqueId)).toEqual( + 'utf-test;kibana:test字type:test漢字name:9000☺' + ); + }); + it('execution context is the same for all the lifecycle events', async () => { const { executionContext, http } = await root.setup(); const { diff --git a/src/dev/build/lib/download.ts b/src/dev/build/lib/download.ts index c67407095b37e..cb569e8e738ae 100644 --- a/src/dev/build/lib/download.ts +++ b/src/dev/build/lib/download.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { openSync, writeSync, unlinkSync, closeSync } from 'fs'; +import { openSync, writeSync, unlinkSync, closeSync, statSync } from 'fs'; import { dirname } from 'path'; import { setTimeout } from 'timers/promises'; @@ -39,6 +39,7 @@ interface DownloadToDiskOptions { shaAlgorithm: string; maxAttempts?: number; retryDelaySecMultiplier?: number; + skipChecksumCheck?: boolean; } export async function downloadToDisk({ log, @@ -48,8 +49,9 @@ export async function downloadToDisk({ shaAlgorithm, maxAttempts = 1, retryDelaySecMultiplier = 5, + skipChecksumCheck = false, }: DownloadToDiskOptions) { - if (!shaChecksum) { + if (!shaChecksum && !skipChecksumCheck) { throw new Error(`${shaAlgorithm} checksum of ${url} not provided, refusing to download.`); } @@ -69,7 +71,7 @@ export async function downloadToDisk({ try { log.debug( `[${attempt}/${maxAttempts}] Attempting download of ${url}`, - chalk.dim(shaAlgorithm) + skipChecksumCheck ? '' : chalk.dim(shaAlgorithm) ); const response = await Axios.request({ @@ -83,30 +85,47 @@ export async function downloadToDisk({ } const hash = createHash(shaAlgorithm); - await new Promise((resolve, reject) => { + let bytesWritten = 0; + + await new Promise((resolve, reject) => { response.data.on('data', (chunk: Buffer) => { - hash.update(chunk); - writeSync(fileHandle, chunk); + if (!skipChecksumCheck) { + hash.update(chunk); + } + + const bytes = writeSync(fileHandle, chunk); + bytesWritten += bytes; }); response.data.on('error', reject); - response.data.on('end', resolve); + response.data.on('end', () => { + if (bytesWritten === 0) { + return reject(new Error(`No bytes written when downloading ${url}`)); + } + + return resolve(); + }); }); - const downloadedSha = hash.digest('hex'); - if (downloadedSha !== shaChecksum) { - throw new Error( - `Downloaded checksum ${downloadedSha} does not match the expected ${shaAlgorithm} checksum.` - ); + if (!skipChecksumCheck) { + const downloadedSha = hash.digest('hex'); + if (downloadedSha !== shaChecksum) { + throw new Error( + `Downloaded checksum ${downloadedSha} does not match the expected ${shaAlgorithm} checksum.` + ); + } } } catch (_error) { error = _error; } finally { closeSync(fileHandle); + + const fileStats = statSync(destination); + log.debug(`Downloaded ${fileStats.size} bytes to ${destination}`); } if (!error) { - log.debug(`Downloaded ${url} and verified checksum`); + log.debug(`Downloaded ${url} ${skipChecksumCheck ? '' : 'and verified checksum'}`); return; } diff --git a/src/dev/build/lib/integration_tests/download.test.ts b/src/dev/build/lib/integration_tests/download.test.ts index 7f885fdc02d28..26e374fa102db 100644 --- a/src/dev/build/lib/integration_tests/download.test.ts +++ b/src/dev/build/lib/integration_tests/download.test.ts @@ -146,10 +146,12 @@ describe('downloadToDisk', () => { expect(logWritter.messages).toMatchInlineSnapshot(` Array [ " debg [1/2] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 0 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Request failed with status code 500", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", " info Retrying in 0.1 seconds", " debg [2/2] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 3 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Downloaded TEST_SERVER_URL and verified checksum", ] `); @@ -171,14 +173,17 @@ describe('downloadToDisk', () => { expect(logWritter.messages).toMatchInlineSnapshot(` Array [ " debg [1/3] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 0 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Request failed with status code 500", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", " info Retrying in 0.1 seconds", " debg [2/3] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 3 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Downloaded checksum fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9 does not match the expected sha256 checksum.", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", " info Retrying in 0.2 seconds", " debg [3/3] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 3 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Downloaded TEST_SERVER_URL and verified checksum", ] `); @@ -200,22 +205,27 @@ describe('downloadToDisk', () => { expect(logWritter.messages).toMatchInlineSnapshot(` Array [ " debg [1/5] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 0 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Request failed with status code 500", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", " info Retrying in 0.1 seconds", " debg [2/5] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 0 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Request failed with status code 500", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", " info Retrying in 0.2 seconds", " debg [3/5] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 0 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Request failed with status code 500", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", " info Retrying in 0.30000000000000004 seconds", " debg [4/5] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 0 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Request failed with status code 500", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", " info Retrying in 0.4 seconds", " debg [5/5] Attempting download of TEST_SERVER_URL sha256", + " debg Downloaded 0 bytes to TMP_DIR/__tmp_download_js_test_file__", " debg Download failed: Request failed with status code 500", " debg Deleting downloaded data at TMP_DIR/__tmp_download_js_test_file__", ] diff --git a/src/dev/build/tasks/bundle_fleet_packages.ts b/src/dev/build/tasks/bundle_fleet_packages.ts index 0fd584354494f..cda4f47d1210e 100644 --- a/src/dev/build/tasks/bundle_fleet_packages.ts +++ b/src/dev/build/tasks/bundle_fleet_packages.ts @@ -6,18 +6,10 @@ * Side Public License, v 1. */ -import axios from 'axios'; import JSON5 from 'json5'; -// @ts-expect-error untyped internal module used to prevent axios from using xhr adapter in tests -import AxiosHttpAdapter from 'axios/lib/adapters/http'; - -import { ToolingLog } from '@kbn/dev-utils'; -import { closeSync, openSync, writeSync } from 'fs'; -import { dirname } from 'path'; import { readCliArgs } from '../args'; - -import { Task, read, mkdirp } from '../lib'; +import { Task, read, downloadToDisk } from '../lib'; const BUNDLED_PACKAGES_DIR = 'x-pack/plugins/fleet/server/bundled_packages'; @@ -43,15 +35,31 @@ export const BundleFleetPackages: Task = { const configFilePath = config.resolveFromRepo('fleet_packages.json'); const fleetPackages = (await read(configFilePath)) || '[]'; + const parsedFleetPackages: FleetPackage[] = JSON5.parse(fleetPackages); + + log.debug( + `Found configured bundled packages: ${parsedFleetPackages + .map((fleetPackage) => `${fleetPackage.name}-${fleetPackage.version}`) + .join(', ')}` + ); + await Promise.all( - JSON5.parse(fleetPackages).map(async (fleetPackage: FleetPackage) => { + parsedFleetPackages.map(async (fleetPackage) => { const archivePath = `${fleetPackage.name}-${fleetPackage.version}.zip`; const archiveUrl = `${eprUrl}/epr/${fleetPackage.name}/${fleetPackage.name}-${fleetPackage.version}.zip`; const destination = build.resolvePath(BUNDLED_PACKAGES_DIR, archivePath); try { - await downloadPackageArchive({ log, url: archiveUrl, destination }); + await downloadToDisk({ + log, + url: archiveUrl, + destination, + shaChecksum: '', + shaAlgorithm: 'sha512', + skipChecksumCheck: true, + maxAttempts: 3, + }); } catch (error) { log.warning(`Failed to download bundled package archive ${archivePath}`); log.warning(error); @@ -60,45 +68,3 @@ export const BundleFleetPackages: Task = { ); }, }; - -/** - * We need to skip the checksum process on Fleet's bundled packages for now because we can't reliably generate - * a consistent checksum for the `.zip` file returned from the EPR service. This download process should be updated - * to verify packages using the proposed package signature field provided in https://github.com/elastic/elastic-package/issues/583 - */ -async function downloadPackageArchive({ - log, - url, - destination, -}: { - log: ToolingLog; - url: string; - destination: string; -}) { - log.info(`Downloading bundled package from ${url}`); - - await mkdirp(dirname(destination)); - const file = openSync(destination, 'w'); - - try { - const response = await axios.request({ - url, - responseType: 'stream', - adapter: AxiosHttpAdapter, - }); - - await new Promise((resolve, reject) => { - response.data.on('data', (chunk: Buffer) => { - writeSync(file, chunk); - }); - - response.data.on('error', reject); - response.data.on('end', resolve); - }); - } catch (error) { - log.warning(`Error downloading bundled package from ${url}`); - log.warning(error); - } finally { - closeSync(file); - } -} diff --git a/src/plugins/chart_expressions/expression_gauge/common/constants.ts b/src/plugins/chart_expressions/expression_gauge/common/constants.ts index a55d8f90f014e..484e2864d3c46 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/constants.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/constants.ts @@ -12,9 +12,12 @@ export const GAUGE_FUNCTION_RENDERER_NAME = 'gauge_renderer'; export const GaugeShapes = { HORIZONTAL_BULLET: 'horizontalBullet', VERTICAL_BULLET: 'verticalBullet', + ARC: 'arc', + CIRCLE: 'circle', } as const; export const GaugeTicksPositions = { + HIDDEN: 'hidden', AUTO: 'auto', BANDS: 'bands', } as const; @@ -25,6 +28,12 @@ export const GaugeLabelMajorModes = { NONE: 'none', } as const; +export const GaugeCentralMajorModes = { + AUTO: 'auto', + CUSTOM: 'custom', + NONE: 'none', +} as const; + export const GaugeColorModes = { PALETTE: 'palette', NONE: 'none', diff --git a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap index 3e26e5d8b95b4..56e22aedf42ed 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap @@ -1,12 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`interpreter/functions#gauge returns an object with the correct structure 1`] = ` +exports[`interpreter/functions#gauge returns an object with the correct structure for the arc if centralMajor and centralMajorMode are passed 1`] = ` Object { "as": "gauge", "type": "render", "value": Object { "args": Object { "ariaLabel": undefined, + "centralMajor": "Some label", + "centralMajorMode": "custom", "colorMode": "none", "goal": undefined, "labelMajor": "title", @@ -16,6 +18,576 @@ Object { "metric": "col-0-1", "min": "col-1-2", "palette": undefined, + "shape": "arc", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the arc shape 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "centralMajor": undefined, + "centralMajorMode": "auto", + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "arc", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the auto labelMajorMode 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "auto", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the auto ticksPosition 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the bands ticksPosition 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "bands", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the circle if centralMajor and centralMajorMode are passed 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "centralMajor": "Some label", + "centralMajorMode": "custom", + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "circle", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the circle shape 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "centralMajor": undefined, + "centralMajorMode": "auto", + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "circle", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the custom labelMajorMode 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the hidden ticksPosition 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "hidden", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the horizontalBullet shape 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the none colorMode 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the none labelMajorMode 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "none", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "horizontalBullet", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the palette colorMode 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "palette", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, "shape": "horizontalBullet", "ticksPosition": "auto", }, @@ -46,3 +618,62 @@ Object { }, } `; + +exports[`interpreter/functions#gauge returns an object with the correct structure for the verticalBullet shape 1`] = ` +Object { + "as": "gauge", + "type": "render", + "value": Object { + "args": Object { + "ariaLabel": undefined, + "colorMode": "none", + "goal": undefined, + "labelMajor": "title", + "labelMajorMode": "custom", + "labelMinor": undefined, + "max": undefined, + "metric": "col-0-1", + "min": "col-1-2", + "palette": undefined, + "shape": "verticalBullet", + "ticksPosition": "auto", + }, + "data": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + Object { + "id": "col-1-2", + "meta": Object { + "type": "string", + }, + "name": "Dest", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + }, +} +`; + +exports[`interpreter/functions#gauge throws error if centralMajor or centralMajorMode are provided for the horizontalBullet shape 1`] = `"Fields \\"centralMajor\\" and \\"centralMajorMode\\" are not supported by the shape \\"horizontalBullet\\""`; + +exports[`interpreter/functions#gauge throws error if centralMajor or centralMajorMode are provided for the vertical shape 1`] = `"Fields \\"centralMajor\\" and \\"centralMajorMode\\" are not supported by the shape \\"verticalBullet\\""`; + +exports[`interpreter/functions#gauge throws error on wrong colorMode type 1`] = `"Invalid color mode is specified. Supported color modes: palette, none"`; + +exports[`interpreter/functions#gauge throws error on wrong labelMajorMode type 1`] = `"Invalid label major mode is specified. Supported label major modes: auto, custom, none"`; + +exports[`interpreter/functions#gauge throws error on wrong shape type 1`] = `"Invalid shape is specified. Supported shapes: horizontalBullet, verticalBullet, arc, circle"`; + +exports[`interpreter/functions#gauge throws error on wrong ticksPosition type 1`] = `"Invalid ticks position is specified. Supported ticks positions: hidden, auto, bands"`; diff --git a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.test.ts b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.test.ts index 0a18fc68c253b..0060cc267bf72 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.test.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.test.ts @@ -10,7 +10,12 @@ import { gaugeFunction } from './gauge_function'; import { GaugeArguments, GaugeShapes } from '..'; import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils'; import { Datatable } from '../../../../expressions/common/expression_types/specs'; -import { GaugeColorModes, GaugeLabelMajorModes, GaugeTicksPositions } from '../constants'; +import { + GaugeCentralMajorModes, + GaugeColorModes, + GaugeLabelMajorModes, + GaugeTicksPositions, +} from '../constants'; describe('interpreter/functions#gauge', () => { const fn = functionWrapper(gaugeFunction()); @@ -31,9 +36,79 @@ describe('interpreter/functions#gauge', () => { min: 'col-1-2', metric: 'col-0-1', }; + const checkArg = ( + arg: keyof GaugeArguments, + options: Record, + invalidValue: string + ) => { + Object.values(options).forEach((option) => { + it(`returns an object with the correct structure for the ${option} ${arg}`, () => { + const actual = fn(context, { ...args, [arg]: option }, undefined); + expect(actual).toMatchSnapshot(); + }); + }); - it('returns an object with the correct structure', () => { - const actual = fn(context, args, undefined); + it(`throws error on wrong ${arg} type`, () => { + const actual = () => fn(context, { ...args, [arg]: invalidValue as any }, undefined); + expect(actual).toThrowErrorMatchingSnapshot(); + }); + }; + + checkArg('shape', GaugeShapes, 'invalid_shape'); + checkArg('colorMode', GaugeColorModes, 'invalid_color_mode'); + checkArg('ticksPosition', GaugeTicksPositions, 'invalid_ticks_position'); + checkArg('labelMajorMode', GaugeLabelMajorModes, 'invalid_label_major_mode'); + + it(`returns an object with the correct structure for the circle if centralMajor and centralMajorMode are passed`, () => { + const actual = fn( + context, + { + ...args, + shape: GaugeShapes.CIRCLE, + centralMajor: 'Some label', + centralMajorMode: GaugeCentralMajorModes.CUSTOM, + }, + undefined + ); + expect(actual).toMatchSnapshot(); + }); + + it(`returns an object with the correct structure for the arc if centralMajor and centralMajorMode are passed`, () => { + const actual = fn( + context, + { + ...args, + shape: GaugeShapes.ARC, + centralMajor: 'Some label', + centralMajorMode: GaugeCentralMajorModes.CUSTOM, + }, + undefined + ); expect(actual).toMatchSnapshot(); }); + + it(`throws error if centralMajor or centralMajorMode are provided for the horizontalBullet shape`, () => { + const actual = () => + fn( + context, + { ...args, centralMajor: 'Some label', centralMajorMode: GaugeCentralMajorModes.CUSTOM }, + undefined + ); + expect(actual).toThrowErrorMatchingSnapshot(); + }); + + it(`throws error if centralMajor or centralMajorMode are provided for the vertical shape`, () => { + const actual = () => + fn( + context, + { + ...args, + shape: GaugeShapes.VERTICAL_BULLET, + centralMajor: 'Some label', + centralMajorMode: GaugeCentralMajorModes.CUSTOM, + }, + undefined + ); + expect(actual).toThrowErrorMatchingSnapshot(); + }); }); diff --git a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts index e710c739b21eb..14e1bdef6e654 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts @@ -13,11 +13,13 @@ import type { DatatableColumn } from '../../../../expressions'; import { GaugeExpressionFunctionDefinition } from '../types'; import { EXPRESSION_GAUGE_NAME, + GaugeCentralMajorModes, GaugeColorModes, GaugeLabelMajorModes, GaugeShapes, GaugeTicksPositions, } from '../constants'; +import { isRoundShape } from '../utils'; export const errors = { invalidShapeError: () => @@ -40,6 +42,12 @@ export const errors = { defaultMessage: `Invalid label major mode is specified. Supported label major modes: {labelMajorModes}`, values: { labelMajorModes: Object.values(GaugeLabelMajorModes).join(', ') }, }), + centralMajorNotSupportedForShapeError: (shape: string) => + i18n.translate('expressionGauge.functions.gauge.errors.centralMajorNotSupportedForShapeError', { + defaultMessage: + 'Fields "centralMajor" and "centralMajorMode" are not supported by the shape "{shape}"', + values: { shape }, + }), }; const validateAccessor = ( @@ -71,7 +79,12 @@ export const gaugeFunction = (): GaugeExpressionFunctionDefinition => ({ args: { shape: { types: ['string'], - options: [GaugeShapes.HORIZONTAL_BULLET, GaugeShapes.VERTICAL_BULLET], + options: [ + GaugeShapes.HORIZONTAL_BULLET, + GaugeShapes.VERTICAL_BULLET, + GaugeShapes.ARC, + GaugeShapes.CIRCLE, + ], help: i18n.translate('expressionGauge.functions.gauge.args.shape.help', { defaultMessage: 'Type of gauge chart', }), @@ -118,7 +131,7 @@ export const gaugeFunction = (): GaugeExpressionFunctionDefinition => ({ ticksPosition: { types: ['string'], default: GaugeTicksPositions.AUTO, - options: [GaugeTicksPositions.AUTO, GaugeTicksPositions.BANDS], + options: [GaugeTicksPositions.HIDDEN, GaugeTicksPositions.AUTO, GaugeTicksPositions.BANDS], help: i18n.translate('expressionGauge.functions.gauge.args.ticksPosition.help', { defaultMessage: 'Specifies the placement of ticks', }), @@ -143,6 +156,19 @@ export const gaugeFunction = (): GaugeExpressionFunctionDefinition => ({ defaultMessage: 'Specifies the labelMinor of the gauge chart', }), }, + centralMajor: { + types: ['string'], + help: i18n.translate('expressionGauge.functions.gauge.args.centralMajor.help', { + defaultMessage: 'Specifies the centralMajor of the gauge chart displayed inside the chart.', + }), + }, + centralMajorMode: { + types: ['string'], + options: [GaugeLabelMajorModes.NONE, GaugeLabelMajorModes.AUTO, GaugeLabelMajorModes.CUSTOM], + help: i18n.translate('expressionGauge.functions.gauge.args.centralMajorMode.help', { + defaultMessage: 'Specifies the mode of centralMajor', + }), + }, ariaLabel: { types: ['string'], help: i18n.translate('expressionGauge.functions.gaugeChart.config.ariaLabel.help', { @@ -162,13 +188,27 @@ export const gaugeFunction = (): GaugeExpressionFunctionDefinition => ({ validateAccessor(args.max, data.columns); validateAccessor(args.goal, data.columns); + const { centralMajor, centralMajorMode, ...restArgs } = args; + + if (!isRoundShape(args.shape) && (centralMajorMode || centralMajor)) { + throw new Error(errors.centralMajorNotSupportedForShapeError(args.shape)); + } + + const centralMajorArgs = isRoundShape(args.shape) + ? { + centralMajorMode: !centralMajorMode ? GaugeCentralMajorModes.AUTO : centralMajorMode, + centralMajor, + } + : {}; + return { type: 'render', as: EXPRESSION_GAUGE_NAME, value: { data, args: { - ...args, + ...restArgs, + ...centralMajorArgs, ariaLabel: args.ariaLabel ?? (handlers.variables?.embeddableTitle as string) ?? diff --git a/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts index 16dfab96c82c6..e71b5b28c7dcf 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts @@ -21,12 +21,14 @@ import { GaugeTicksPositions, GaugeLabelMajorModes, GaugeColorModes, + GaugeCentralMajorModes, } from '../constants'; import { CustomPaletteParams } from '.'; export type GaugeColorMode = $Values; export type GaugeShape = $Values; export type GaugeLabelMajorMode = $Values; +export type GaugeCentralMajorMode = $Values; export type GaugeTicksPosition = $Values; export interface GaugeState { @@ -38,6 +40,8 @@ export interface GaugeState { labelMajorMode: GaugeLabelMajorMode; labelMajor?: string; labelMinor?: string; + centralMajorMode?: GaugeCentralMajorMode; + centralMajor?: string; colorMode?: GaugeColorMode; palette?: PaletteOutput; shape: GaugeShape; diff --git a/src/plugins/share/public/url_generators/url_generator_contract.ts b/src/plugins/chart_expressions/expression_gauge/common/utils/index.ts similarity index 56% rename from src/plugins/share/public/url_generators/url_generator_contract.ts rename to src/plugins/chart_expressions/expression_gauge/common/utils/index.ts index 22ccae8909a69..32726edcafc8c 100644 --- a/src/plugins/share/public/url_generators/url_generator_contract.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/utils/index.ts @@ -6,10 +6,4 @@ * Side Public License, v 1. */ -import { UrlGeneratorId, UrlGeneratorStateMapping } from './url_generator_definition'; - -export interface UrlGeneratorContract { - id: Id; - createUrl(state: UrlGeneratorStateMapping[Id]['State']): Promise; - isDeprecated: boolean; -} +export { isBulletShape, isRoundShape } from './shapes'; diff --git a/src/plugins/chart_expressions/expression_gauge/common/utils/shapes.ts b/src/plugins/chart_expressions/expression_gauge/common/utils/shapes.ts new file mode 100644 index 0000000000000..adddd1c990c74 --- /dev/null +++ b/src/plugins/chart_expressions/expression_gauge/common/utils/shapes.ts @@ -0,0 +1,20 @@ +/* + * 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 { GaugeShapes } from '../constants'; +import { GaugeShape } from '../types'; + +export const isRoundShape = (shape: GaugeShape) => { + const roundShapes: string[] = [GaugeShapes.ARC, GaugeShapes.CIRCLE]; + return roundShapes.includes(shape); +}; + +export const isBulletShape = (shape: GaugeShape) => { + const bulletShapes: string[] = [GaugeShapes.HORIZONTAL_BULLET, GaugeShapes.VERTICAL_BULLET]; + return bulletShapes.includes(shape); +}; diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx index f5dd8ccd3522d..488d5f57a6458 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx @@ -65,6 +65,7 @@ const args: GaugeArguments = { colorMode: GaugeColorModes.NONE, ticksPosition: GaugeTicksPositions.AUTO, labelMajorMode: GaugeLabelMajorModes.AUTO, + centralMajorMode: GaugeLabelMajorModes.NONE, }; const createData = ( diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx index 8d81380fcfe7b..9af143d34b6f9 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx @@ -16,11 +16,22 @@ import { GaugeTicksPosition, GaugeLabelMajorModes, GaugeColorModes, + GaugeShapes, } from '../../common'; -import { GaugeShapes, GaugeTicksPositions } from '../../common'; -import { GaugeIconVertical, GaugeIconHorizontal } from './gauge_icon'; -import { getAccessorsFromArgs, getMaxValue, getMinValue, getValueFromAccessor } from './utils'; +import { GaugeTicksPositions } from '../../common'; +import { + getAccessorsFromArgs, + getIcons, + getMaxValue, + getMinValue, + getValueFromAccessor, + getSubtypeByGaugeType, + getGoalConfig, +} from './utils'; import './index.scss'; +import { GaugeCentralMajorMode } from '../../common/types'; +import { isBulletShape, isRoundShape } from '../../common/utils'; + declare global { interface Window { /** @@ -100,18 +111,18 @@ function normalizeBands( } function getTitle( - labelMajorMode: GaugeLabelMajorMode, - labelMajor?: string, + majorMode?: GaugeLabelMajorMode | GaugeCentralMajorMode, + major?: string, fallbackTitle?: string ) { - if (labelMajorMode === GaugeLabelMajorModes.NONE) { + if (majorMode === GaugeLabelMajorModes.NONE) { return ''; } - if (labelMajorMode === GaugeLabelMajorModes.AUTO) { - return `${fallbackTitle || ''} `; // added extra space for nice rendering + if (majorMode === GaugeLabelMajorModes.AUTO) { + return fallbackTitle || ''; } - return `${labelMajor || fallbackTitle || ''} `; // added extra space for nice rendering + return major || fallbackTitle || ''; } // TODO: once charts handle not displaying labels when there's no space for them, it's safe to remove this @@ -134,9 +145,14 @@ function getTicks( range: [number, number], colorBands?: number[] ) { + if (ticksPosition === GaugeTicksPositions.HIDDEN) { + return []; + } + if (ticksPosition === GaugeTicksPositions.BANDS && colorBands) { return colorBands && getTicksLabels(colorBands); } + const TICKS_NO = 3; const min = Math.min(...(colorBands || []), ...range); const max = Math.max(...(colorBands || []), ...range); @@ -152,12 +168,14 @@ function getTicks( export const GaugeComponent: FC = memo( ({ data, args, formatFactory, chartsThemeService }) => { const { - shape: subtype, + shape: gaugeType, palette, colorMode, labelMinor, labelMajor, labelMajorMode, + centralMajor, + centralMajorMode, ticksPosition, } = args; const table = data; @@ -179,8 +197,7 @@ export const GaugeComponent: FC = memo( const metricValue = args.metric ? getValueFromAccessor(accessors.metric, row) : undefined; - const icon = - subtype === GaugeShapes.HORIZONTAL_BULLET ? GaugeIconHorizontal : GaugeIconVertical; + const icon = getIcons(gaugeType); if (typeof metricValue !== 'number') { return ; @@ -235,6 +252,23 @@ export const GaugeComponent: FC = memo( // TODO: format in charts const formattedActual = Math.round(Math.min(Math.max(metricValue, min), max) * 1000) / 1000; + const goalConfig = getGoalConfig(gaugeType); + const totalTicks = getTicks(ticksPosition, [min, max], bands); + const ticks = + gaugeType === GaugeShapes.CIRCLE ? totalTicks.slice(0, totalTicks.length - 1) : totalTicks; + + const labelMajorTitle = getTitle(labelMajorMode, labelMajor, metricColumn?.name); + + // added extra space for nice rendering + const majorExtraSpaces = isBulletShape(gaugeType) ? ' ' : ''; + const minorExtraSpaces = isBulletShape(gaugeType) ? ' ' : ''; + + const extraTitles = isRoundShape(gaugeType) + ? { + centralMinor: tickFormatter.convert(metricValue), + centralMajor: getTitle(centralMajorMode, centralMajor, metricColumn?.name), + } + : {}; return ( @@ -246,13 +280,13 @@ export const GaugeComponent: FC = memo( /> = bands[0] && goal <= bands[bands.length - 1] ? goal : undefined} actual={formattedActual} tickValueFormatter={({ value: tickValue }) => tickFormatter.convert(tickValue)} bands={bands} - ticks={getTicks(ticksPosition, [min, max], bands)} + ticks={ticks} bandFillColor={ colorMode === GaugeColorModes.PALETTE && colors ? (val) => { @@ -265,8 +299,10 @@ export const GaugeComponent: FC = memo( } : () => `rgba(255,255,255,0)` } - labelMajor={getTitle(labelMajorMode, labelMajor, metricColumn?.name)} - labelMinor={labelMinor ? labelMinor + ' ' : ''} // added extra space for nice rendering + labelMajor={labelMajorTitle ? `${labelMajorTitle}${majorExtraSpaces}` : labelMajorTitle} + labelMinor={labelMinor ? `${labelMinor}${minorExtraSpaces}` : ''} + {...extraTitles} + {...goalConfig} /> ); diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/icons/horizontal_bullet_icon.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/icons/horizontal_bullet_icon.tsx new file mode 100644 index 0000000000000..93bd51e8b2028 --- /dev/null +++ b/src/plugins/chart_expressions/expression_gauge/public/components/icons/horizontal_bullet_icon.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 from 'react'; +import type { EuiIconProps } from '@elastic/eui'; + +export const HorizontalBulletIcon = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/src/plugins/share/public/url_generators/index.ts b/src/plugins/chart_expressions/expression_gauge/public/components/icons/index.ts similarity index 73% rename from src/plugins/share/public/url_generators/index.ts rename to src/plugins/chart_expressions/expression_gauge/public/components/icons/index.ts index 78ded06622429..79ef0dcad9d7a 100644 --- a/src/plugins/share/public/url_generators/index.ts +++ b/src/plugins/chart_expressions/expression_gauge/public/components/icons/index.ts @@ -6,8 +6,5 @@ * Side Public License, v 1. */ -export * from './url_generator_service'; - -export * from './url_generator_definition'; - -export * from './url_generator_contract'; +export { HorizontalBulletIcon } from './horizontal_bullet_icon'; +export { VerticalBulletIcon } from './vertical_bullet_icon'; diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_icon.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/icons/vertical_bullet_icon.tsx similarity index 56% rename from src/plugins/chart_expressions/expression_gauge/public/components/gauge_icon.tsx rename to src/plugins/chart_expressions/expression_gauge/public/components/icons/vertical_bullet_icon.tsx index 750129ca471a2..99f4976d5f38f 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_icon.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/icons/vertical_bullet_icon.tsx @@ -9,29 +9,7 @@ import React from 'react'; import type { EuiIconProps } from '@elastic/eui'; -export const GaugeIconHorizontal = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - -); - -export const GaugeIconVertical = ({ title, titleId, ...props }: Omit) => ( +export const VerticalBulletIcon = ({ title, titleId, ...props }: Omit) => ( { describe('getValueFromAccessor', () => { diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/utils.ts b/src/plugins/chart_expressions/expression_gauge/public/components/utils/accessors.ts similarity index 96% rename from src/plugins/chart_expressions/expression_gauge/public/components/utils.ts rename to src/plugins/chart_expressions/expression_gauge/public/components/utils/accessors.ts index f756f8b12f8da..8848c7646a5f0 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/utils.ts +++ b/src/plugins/chart_expressions/expression_gauge/public/components/utils/accessors.ts @@ -7,8 +7,8 @@ */ import type { DatatableColumn, DatatableRow } from 'src/plugins/expressions'; -import { getAccessorByDimension } from '../../../../visualizations/common/utils'; -import { Accessors, GaugeArguments } from '../../common'; +import { getAccessorByDimension } from '../../../../../visualizations/common/utils'; +import { Accessors, GaugeArguments } from '../../../common'; export const getValueFromAccessor = ( accessor: string, diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/utils/gauge_types.ts b/src/plugins/chart_expressions/expression_gauge/public/components/utils/gauge_types.ts new file mode 100644 index 0000000000000..f3fd0bec250f9 --- /dev/null +++ b/src/plugins/chart_expressions/expression_gauge/public/components/utils/gauge_types.ts @@ -0,0 +1,20 @@ +/* + * 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 { GoalProps } from '@elastic/charts'; +import { GaugeShape, GaugeShapes } from '../../../common'; + +export const getSubtypeByGaugeType = (type: GaugeShape): GoalProps['subtype'] => + (( + { + [GaugeShapes.HORIZONTAL_BULLET]: 'horizontalBullet', + [GaugeShapes.VERTICAL_BULLET]: 'verticalBullet', + [GaugeShapes.ARC]: 'goal', + [GaugeShapes.CIRCLE]: 'goal', + } as const + )[type]); diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/utils/goal_config.ts b/src/plugins/chart_expressions/expression_gauge/public/components/utils/goal_config.ts new file mode 100644 index 0000000000000..bba9f0d5b489b --- /dev/null +++ b/src/plugins/chart_expressions/expression_gauge/public/components/utils/goal_config.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { GoalProps } from '@elastic/charts'; +import { GaugeShape, GaugeShapes } from '../../../common'; + +const getCicleConfig = () => ({ + angleStart: Math.PI / 2, + angleEnd: -(Math.PI + Math.PI / 2), +}); + +const getArcConfig = () => ({ + angleStart: Math.PI + (Math.PI - (2 * Math.PI) / 2.5) / 2, + angleEnd: -(Math.PI - (2 * Math.PI) / 2.5) / 2, +}); + +const empty = () => ({}); + +export const getGoalConfig = (type: GaugeShape): Partial => + ({ + [GaugeShapes.HORIZONTAL_BULLET]: empty, + [GaugeShapes.VERTICAL_BULLET]: empty, + [GaugeShapes.ARC]: getArcConfig, + [GaugeShapes.CIRCLE]: getCicleConfig, + }[type]()); diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/utils/icons.ts b/src/plugins/chart_expressions/expression_gauge/public/components/utils/icons.ts new file mode 100644 index 0000000000000..734c64d579da3 --- /dev/null +++ b/src/plugins/chart_expressions/expression_gauge/public/components/utils/icons.ts @@ -0,0 +1,18 @@ +/* + * 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 { GaugeShape, GaugeShapes } from '../../../common'; +import { HorizontalBulletIcon } from '../icons'; + +export const getIcons = (type: GaugeShape) => + ({ + [GaugeShapes.HORIZONTAL_BULLET]: HorizontalBulletIcon, + [GaugeShapes.VERTICAL_BULLET]: HorizontalBulletIcon, + [GaugeShapes.ARC]: 'visGoal', + [GaugeShapes.CIRCLE]: 'visGoal', + }[type]); diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/utils/index.ts b/src/plugins/chart_expressions/expression_gauge/public/components/utils/index.ts new file mode 100644 index 0000000000000..8892863d625d7 --- /dev/null +++ b/src/plugins/chart_expressions/expression_gauge/public/components/utils/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 * from './accessors'; +export * from './icons'; +export * from './gauge_types'; +export * from './goal_config'; diff --git a/src/plugins/chart_expressions/expression_gauge/public/index.ts b/src/plugins/chart_expressions/expression_gauge/public/index.ts index 62e287520b45a..6f9924759e4c4 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/index.ts +++ b/src/plugins/chart_expressions/expression_gauge/public/index.ts @@ -13,4 +13,4 @@ export function plugin() { } export { getGoalValue, getMaxValue, getMinValue, getValueFromAccessor } from './components/utils'; -export { GaugeIconVertical, GaugeIconHorizontal } from './components/gauge_icon'; +export { VerticalBulletIcon, HorizontalBulletIcon } from './components/icons'; diff --git a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx index c1e026064fdfb..0adb06d91d268 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx @@ -202,9 +202,6 @@ export const HeatmapComponent: FC = memo( const cell = e[0][0]; const { x, y } = cell.datum; - const xAxisFieldName = xAxisColumn?.meta?.field; - const timeFieldName = isTimeBasedSwimLane ? xAxisFieldName : ''; - const points = [ { row: table.rows.findIndex((r) => r[xAxisColumn.id] === x), @@ -229,35 +226,21 @@ export const HeatmapComponent: FC = memo( value: point.value, table, })), - timeFieldName, }; onClickValue(context); }, - [ - isTimeBasedSwimLane, - onClickValue, - table, - xAxisColumn?.id, - xAxisColumn?.meta?.field, - xAxisColumnIndex, - yAxisColumn, - yAxisColumnIndex, - ] + [onClickValue, table, xAxisColumn?.id, xAxisColumnIndex, yAxisColumn, yAxisColumnIndex] ); const onBrushEnd = useCallback( (e: HeatmapBrushEvent) => { const { x, y } = e; - const xAxisFieldName = xAxisColumn?.meta?.field; - const timeFieldName = isTimeBasedSwimLane ? xAxisFieldName : ''; - if (isTimeBasedSwimLane) { const context: BrushEvent['data'] = { range: x as number[], table, column: xAxisColumnIndex, - timeFieldName, }; onSelectRange(context); } else { @@ -289,7 +272,6 @@ export const HeatmapComponent: FC = memo( value: point.value, table, })), - timeFieldName, }; onClickValue(context); } diff --git a/src/plugins/data/common/es_query/index.ts b/src/plugins/data/common/es_query/index.ts index 28361114be6e1..fa9b7ac86a7fa 100644 --- a/src/plugins/data/common/es_query/index.ts +++ b/src/plugins/data/common/es_query/index.ts @@ -54,7 +54,6 @@ import { KueryNode as oldKueryNode, FilterMeta as oldFilterMeta, FILTERS as oldFILTERS, - IFieldSubType as oldIFieldSubType, EsQueryConfig as oldEsQueryConfig, compareFilters as oldCompareFilters, COMPARE_ALL_OPTIONS as OLD_COMPARE_ALL_OPTIONS, @@ -356,12 +355,6 @@ type KueryNode = oldKueryNode; */ type FilterMeta = oldFilterMeta; -/** - * @deprecated Import from the "@kbn/es-query" package directly instead. - * @removeBy 8.1 - */ -type IFieldSubType = oldIFieldSubType; - /** * @deprecated Import from the "@kbn/es-query" package directly instead. * @removeBy 8.1 @@ -385,7 +378,6 @@ export type { RangeFilter, KueryNode, FilterMeta, - IFieldSubType, EsQueryConfig, }; export { diff --git a/src/plugins/data/common/kbn_field_types/index.ts b/src/plugins/data/common/kbn_field_types/index.ts index f01401948dec8..5c0c2102f804c 100644 --- a/src/plugins/data/common/kbn_field_types/index.ts +++ b/src/plugins/data/common/kbn_field_types/index.ts @@ -7,29 +7,6 @@ */ // NOTE: trick to mark exports as deprecated (only for constants and types, but not for interfaces, classes or enums) -import { - getFilterableKbnTypeNames as oldGetFilterableKbnTypeNames, - getKbnFieldType as oldGetKbnFieldType, - getKbnTypeNames as oldGetKbnTypeNames, - KbnFieldType, -} from '@kbn/field-types'; +import { KbnFieldType } from '@kbn/field-types'; -/** - * @deprecated Import from the "@kbn/field-types" package directly instead. - * @removeBy 8.1 - */ -const getFilterableKbnTypeNames = oldGetFilterableKbnTypeNames; - -/** - * @deprecated Import from the "@kbn/field-types" package directly instead. - * @removeBy 8.1 - */ -const getKbnFieldType = oldGetKbnFieldType; - -/** - * @deprecated Import from the "@kbn/field-types" package directly instead. - * @removeBy 8.1 - */ -const getKbnTypeNames = oldGetKbnTypeNames; - -export { getKbnFieldType, getKbnTypeNames, getFilterableKbnTypeNames, KbnFieldType }; +export { KbnFieldType }; diff --git a/src/plugins/data/public/deprecated.ts b/src/plugins/data/public/deprecated.ts index 8b90f92b932e0..0458a940482de 100644 --- a/src/plugins/data/public/deprecated.ts +++ b/src/plugins/data/public/deprecated.ts @@ -47,7 +47,6 @@ import { PhraseFilter, CustomFilter, MatchAllFilter, - IFieldSubType, EsQueryConfig, FilterStateStore, compareFilters, @@ -147,7 +146,6 @@ export type { PhraseFilter, CustomFilter, MatchAllFilter, - IFieldSubType, EsQueryConfig, }; export { isFilter, isFilters }; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 4b7b447d2c8be..ce6f2e03744fa 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -292,7 +292,7 @@ export type { export type { AggsStart } from './search/aggs'; -export { getTime, getKbnTypeNames } from '../common'; +export { getTime } from '../common'; export { isTimeRange, isQuery } from '../common'; diff --git a/src/plugins/data/server/deprecated.ts b/src/plugins/data/server/deprecated.ts index f9f77ee0ca12f..98db107f32a11 100644 --- a/src/plugins/data/server/deprecated.ts +++ b/src/plugins/data/server/deprecated.ts @@ -67,4 +67,4 @@ export const esQuery = { buildEsQuery, }; -export type { Filter, Query, EsQueryConfig, KueryNode, IFieldSubType } from '../common'; +export type { Filter, Query, EsQueryConfig, KueryNode } from '../common'; diff --git a/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx b/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx index e6e48c477ebc9..4bee00f3c4b2a 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx @@ -8,6 +8,7 @@ import React, { createContext, useContext, FunctionComponent, useMemo } from 'react'; import { NotificationsStart, CoreStart } from 'src/core/public'; +import { FieldFormatsStart } from '../shared_imports'; import type { DataView, DataPublicPluginStart } from '../shared_imports'; import { ApiService } from '../lib/api'; import type { InternalFieldType, PluginStart } from '../types'; @@ -25,7 +26,7 @@ export interface Context { notifications: NotificationsStart; }; fieldFormatEditors: PluginStart['fieldFormatEditors']; - fieldFormats: DataPublicPluginStart['fieldFormats']; + fieldFormats: FieldFormatsStart; /** * An array of field names not allowed. * e.g we probably don't want a user to give a name of an existing diff --git a/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx b/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx index 5b431424c1b44..bd4f62b2c55f3 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx @@ -11,6 +11,7 @@ import { DocLinksStart, NotificationsStart, CoreStart } from 'src/core/public'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; +import { FieldFormatsStart } from 'src/plugins/field_formats/public'; import { DataViewField, DataView, @@ -51,7 +52,7 @@ export interface Props { apiService: ApiService; /** Field format */ fieldFormatEditors: PluginStart['fieldFormatEditors']; - fieldFormats: DataPublicPluginStart['fieldFormats']; + fieldFormats: FieldFormatsStart; uiSettings: CoreStart['uiSettings']; } diff --git a/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx b/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx index c55385e152bcf..e921d0beafce1 100644 --- a/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx @@ -11,24 +11,21 @@ import { EuiCode, EuiFormRow, EuiSelect } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import { - IndexPattern, - KBN_FIELD_TYPES, - ES_FIELD_TYPES, - DataPublicPluginStart, -} from 'src/plugins/data/public'; +import { KBN_FIELD_TYPES, ES_FIELD_TYPES } from 'src/plugins/data/public'; import type { FieldFormatInstanceType } from 'src/plugins/field_formats/common'; import { CoreStart } from 'src/core/public'; import { castEsToKbnFieldTypeName } from '@kbn/field-types'; +import { FieldFormatsStart } from 'src/plugins/field_formats/public'; +import { DataView } from 'src/plugins/data_views/public'; import { FormatEditor } from './format_editor'; import { FormatEditorServiceStart } from '../../service'; import { FieldFormatConfig } from '../../types'; export interface FormatSelectEditorProps { esTypes: ES_FIELD_TYPES[]; - indexPattern: IndexPattern; + indexPattern: DataView; fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors']; - fieldFormats: DataPublicPluginStart['fieldFormats']; + fieldFormats: FieldFormatsStart; uiSettings: CoreStart['uiSettings']; onChange: (change?: FieldFormatConfig) => void; onError: (error?: string) => void; @@ -54,7 +51,7 @@ interface InitialFieldTypeFormat extends FieldTypeFormat { const getFieldTypeFormatsList = ( fieldType: KBN_FIELD_TYPES, defaultFieldFormat: FieldFormatInstanceType, - fieldFormats: DataPublicPluginStart['fieldFormats'] + fieldFormats: FieldFormatsStart ) => { const formatsByType = fieldFormats.getByFieldType(fieldType).map(({ id, title }) => ({ id, diff --git a/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx b/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx index a4a09562c300f..981654ac52d91 100644 --- a/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx +++ b/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx @@ -16,6 +16,7 @@ import React, { useRef, FunctionComponent, } from 'react'; +import { renderToString } from 'react-dom/server'; import useDebounce from 'react-use/lib/useDebounce'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; @@ -45,8 +46,10 @@ const defaultParams: Params = { format: null, }; -export const defaultValueFormatter = (value: unknown) => - `${typeof value === 'object' ? JSON.stringify(value) : value ?? '-'}`; +export const defaultValueFormatter = (value: unknown) => { + const content = typeof value === 'object' ? JSON.stringify(value) : String(value) ?? '-'; + return renderToString(<>{content}); +}; export const FieldPreviewProvider: FunctionComponent = ({ children }) => { const previewCount = useRef(0); diff --git a/src/plugins/data_view_field_editor/public/plugin.test.tsx b/src/plugins/data_view_field_editor/public/plugin.test.tsx index fe7e8c57cd4ec..eba7d19a5a0d0 100644 --- a/src/plugins/data_view_field_editor/public/plugin.test.tsx +++ b/src/plugins/data_view_field_editor/public/plugin.test.tsx @@ -20,6 +20,7 @@ jest.mock('../../kibana_react/public', () => { import { CoreStart } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; +import { fieldFormatsServiceMock } from '../../field_formats/public/mocks'; import { usageCollectionPluginMock } from '../../usage_collection/public/mocks'; import { FieldEditorLoader } from './components/field_editor_loader'; @@ -35,7 +36,7 @@ describe('DataViewFieldEditorPlugin', () => { data: dataPluginMock.createStartContract(), usageCollection: usageCollectionPluginMock.createSetupContract(), dataViews: dataPluginMock.createStartContract().dataViews, - fieldFormats: dataPluginMock.createStartContract().fieldFormats, + fieldFormats: fieldFormatsServiceMock.createStartContract(), }; let plugin: IndexPatternFieldEditorPlugin; diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx index 1be37081a01b5..f54c631689099 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx @@ -7,9 +7,11 @@ */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { Subject, BehaviorSubject } from 'rxjs'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { setHeaderActionMenuMounter } from '../../../../kibana_services'; +import type { DataView } from '../../../../../../data/common'; +import { setHeaderActionMenuMounter, setUiActions } from '../../../../kibana_services'; import { esHits } from '../../../../__mocks__/es_hits'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { createSearchSourceMock } from '../../../../../../data/common/search/search_source/mocks'; @@ -21,10 +23,12 @@ import { Chart } from './point_series'; import { DiscoverChart } from './discover_chart'; import { VIEW_MODE } from '../../../../components/view_mode_toggle'; import { KibanaContextProvider } from '../../../../../../kibana_react/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { ReactWrapper } from 'enzyme'; setHeaderActionMenuMounter(jest.fn()); -function mountComponent(isTimeBased: boolean = false) { +async function mountComponent(isTimeBased: boolean = false) { const searchSourceMock = createSearchSourceMock({}); const services = discoverServiceMock; services.data.query.timefilter.timefilter.getAbsoluteTime = () => { @@ -86,7 +90,12 @@ function mountComponent(isTimeBased: boolean = false) { }) as DataCharts$; const props = { - isTimeBased, + indexPattern: { + isTimeBased: () => isTimeBased, + id: '123', + getFieldByName: () => ({ type: 'date', name: 'timefield', visualizable: true }), + timeFieldName: 'timefield', + } as unknown as DataView, resetSavedSearch: jest.fn(), savedSearch: savedSearchMock, savedSearchDataChart$: charts$, @@ -99,20 +108,64 @@ function mountComponent(isTimeBased: boolean = false) { setDiscoverViewMode: jest.fn(), }; - return mountWithIntl( - - - - ); + let instance: ReactWrapper = {} as ReactWrapper; + await act(async () => { + instance = mountWithIntl( + + + + ); + // wait for initial async loading to complete + await new Promise((r) => setTimeout(r, 0)); + await instance.update(); + }); + return instance; } describe('Discover chart', () => { - test('render without timefield', () => { - const component = mountComponent(); + let triggerActions: unknown[] = []; + beforeEach(() => { + setUiActions({ + getTriggerCompatibleActions: () => { + return triggerActions; + }, + } as unknown as UiActionsStart); + }); + test('render without timefield', async () => { + const component = await mountComponent(); expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeFalsy(); }); - test('render with filefield', () => { - const component = mountComponent(true); + + test('render with timefield without visualize permissions', async () => { + const component = await mountComponent(true); + expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="discoverEditVisualization"]').exists()).toBeFalsy(); + }); + + test('render with timefield with visualize permissions', async () => { + triggerActions = [{}]; + const component = await mountComponent(true); expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="discoverEditVisualization"]').exists()).toBeTruthy(); + }); + + test('triggers ui action on click', async () => { + const fn = jest.fn(); + setUiActions({ + getTrigger: () => ({ + exec: fn, + }), + getTriggerCompatibleActions: () => { + return [{}]; + }, + } as unknown as UiActionsStart); + const component = await mountComponent(true); + component.find('[data-test-subj="discoverEditVisualization"]').first().simulate('click'); + expect(fn).toHaveBeenCalledWith( + expect.objectContaining({ + indexPatternId: '123', + fieldName: 'timefield', + }) + ); }); }); diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx index ab9478a0f334c..636770b6965b7 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx @@ -8,14 +8,16 @@ import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; import moment from 'moment'; import { - EuiButtonEmpty, + EuiButtonIcon, EuiContextMenu, EuiFlexGroup, EuiFlexItem, EuiPopover, + EuiToolTip, EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import type { DataView } from '../../../../../../data/common'; import { HitsCounter } from '../hits_counter'; import { SavedSearch } from '../../../../services/saved_searches'; import { GetStateReturn } from '../../services/discover_state'; @@ -25,6 +27,10 @@ import { useChartPanels } from './use_chart_panels'; import { VIEW_MODE, DocumentViewModeToggle } from '../../../../components/view_mode_toggle'; import { SHOW_FIELD_STATISTICS } from '../../../../../common'; import { useDiscoverServices } from '../../../../utils/use_discover_services'; +import { + getVisualizeInformation, + triggerVisualizeActions, +} from '../sidebar/lib/visualize_trigger_utils'; const DiscoverHistogramMemoized = memo(DiscoverHistogram); export const CHART_HIDDEN_KEY = 'discover:chartHidden'; @@ -35,7 +41,7 @@ export function DiscoverChart({ savedSearchDataChart$, savedSearchDataTotalHits$, stateContainer, - isTimeBased, + indexPattern, viewMode, setDiscoverViewMode, hideChart, @@ -46,12 +52,13 @@ export function DiscoverChart({ savedSearchDataChart$: DataCharts$; savedSearchDataTotalHits$: DataTotalHits$; stateContainer: GetStateReturn; - isTimeBased: boolean; + indexPattern: DataView; viewMode: VIEW_MODE; setDiscoverViewMode: (viewMode: VIEW_MODE) => void; hideChart?: boolean; interval?: string; }) { + const isTimeBased = indexPattern.isTimeBased(); const { uiSettings, data, storage } = useDiscoverServices(); const [showChartOptionsPopover, setShowChartOptionsPopover] = useState(false); const showViewModeToggle = uiSettings.get(SHOW_FIELD_STATISTICS) ?? false; @@ -61,6 +68,24 @@ export function DiscoverChart({ moveFocus: false, }); + const timeField = + indexPattern.timeFieldName && indexPattern.getFieldByName(indexPattern.timeFieldName); + const [canVisualize, setCanVisualize] = useState(false); + + useEffect(() => { + if (!timeField) return; + getVisualizeInformation(timeField, indexPattern.id, savedSearch.columns || []).then((info) => { + setCanVisualize(Boolean(info)); + }); + }, [indexPattern, savedSearch.columns, timeField]); + + const onEditVisualization = useCallback(() => { + if (!timeField) { + return; + } + triggerVisualizeActions(timeField, indexPattern.id, savedSearch.columns || []); + }, [indexPattern.id, savedSearch, timeField]); + const onShowChartOptions = useCallback(() => { setShowChartOptionsPopover(!showChartOptionsPopover); }, [showChartOptionsPopover]); @@ -124,27 +149,55 @@ export function DiscoverChart({ )} {isTimeBased && ( - + {canVisualize && ( + + + + + + )} + + + + + } + isOpen={showChartOptionsPopover} + closePopover={closeChartOptions} + panelPaddingSize="none" + anchorPosition="downLeft" > - {i18n.translate('discover.chartOptionsButton', { - defaultMessage: 'Chart options', - })} - - } - isOpen={showChartOptionsPopover} - closePopover={closeChartOptions} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - + + + + )} diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 9a930f247a5ac..aa2da1b2605fc 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -318,7 +318,7 @@ export function DiscoverLayout({ savedSearchDataChart$={charts$} savedSearchDataTotalHits$={totalHits$} stateContainer={stateContainer} - isTimeBased={isTimeBased} + indexPattern={indexPattern} viewMode={viewMode} setDiscoverViewMode={setDiscoverViewMode} hideChart={state.hideChart} diff --git a/src/plugins/discover/public/application/main/services/discover_search_session.ts b/src/plugins/discover/public/application/main/services/discover_search_session.ts index c864c06e4003c..98376d1e1391a 100644 --- a/src/plugins/discover/public/application/main/services/discover_search_session.ts +++ b/src/plugins/discover/public/application/main/services/discover_search_session.ts @@ -15,7 +15,7 @@ import { getQueryParams, removeQueryParam, } from '../../../../../kibana_utils/public'; -import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../../url_generator'; +import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../../constants'; export interface DiscoverSearchSessionManagerDeps { history: History; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 92ca4ce96bf72..34673310f2c6e 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -408,7 +408,14 @@ export const DiscoverGrid = ({ if (!rowCount) { return ( -
+
diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx index 371eb014eab8f..bdf9268c73060 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx @@ -15,6 +15,7 @@ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, + EuiIconTip, EuiTitle, EuiButtonEmpty, EuiText, @@ -147,7 +148,8 @@ export function DiscoverGridFlyout({ {indexPattern.isTimeBased() && indexPattern.id && ( - - - {i18n.translate('discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple', { - defaultMessage: 'Surrounding documents', - })} - - + + + + {i18n.translate( + 'discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple', + { + defaultMessage: 'Surrounding documents', + } + )} + + + + + + )} {activePage !== -1 && ( diff --git a/src/plugins/discover/public/constants.ts b/src/plugins/discover/public/constants.ts new file mode 100644 index 0000000000000..1a36ca6972a51 --- /dev/null +++ b/src/plugins/discover/public/constants.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 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 SEARCH_SESSION_ID_QUERY_PARAM = 'searchSessionId'; diff --git a/src/plugins/discover/public/embeddable/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/saved_search_grid.tsx index ca750ca6fb976..ff72ed378aff3 100644 --- a/src/plugins/discover/public/embeddable/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_grid.tsx @@ -21,7 +21,13 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { const [expandedDoc, setExpandedDoc] = useState(undefined); return ( - + {props.totalHitCount !== 0 && ( diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 15707e4cda284..0ce755006b692 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -27,11 +27,5 @@ export type { ISearchEmbeddable, SearchInput } from './embeddable'; export { SEARCH_EMBEDDABLE_TYPE } from './embeddable'; export { loadSharingDataHelpers } from './utils'; -export type { DiscoverUrlGeneratorState } from './url_generator'; - -/** - * @deprecated - */ -export { DISCOVER_APP_URL_GENERATOR } from './url_generator'; export { DISCOVER_APP_LOCATOR } from './locator'; export type { DiscoverAppLocator, DiscoverAppLocatorParams } from './locator'; diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index 192c473f391a5..8e109c44864ac 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -24,9 +24,6 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { - urlGenerator: { - createUrl: jest.fn(), - } as unknown as DiscoverStart['urlGenerator'], locator: sharePluginMock.createLocator(), }; return startContract; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index e55158b0dad5e..aacbc1b58f3e9 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -22,7 +22,7 @@ import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; import { ChartsPluginStart } from 'src/plugins/charts/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; -import { SharePluginStart, SharePluginSetup, UrlGeneratorContract } from 'src/plugins/share/public'; +import { SharePluginStart, SharePluginSetup } from 'src/plugins/share/public'; 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'; @@ -31,7 +31,6 @@ import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../d import { SavedObjectsStart } from '../../saved_objects/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; -import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './services/doc_views/doc_views_types'; import { DocViewsRegistry } from './services/doc_views/doc_views_registry'; import { @@ -45,12 +44,6 @@ import { } from './kibana_services'; import { registerFeature } from './register_feature'; import { buildServices } from './build_services'; -import { - DiscoverUrlGeneratorState, - DISCOVER_APP_URL_GENERATOR, - DiscoverUrlGenerator, - SEARCH_SESSION_ID_QUERY_PARAM, -} from './url_generator'; import { DiscoverAppLocatorDefinition, DiscoverAppLocator } from './locator'; import { SearchEmbeddableFactory } from './embeddable'; import { UsageCollectionSetup } from '../../usage_collection/public'; @@ -64,12 +57,7 @@ import { injectTruncateStyles } from './utils/truncate_styles'; import { DOC_TABLE_LEGACY, TRUNCATE_MAX_HEIGHT } from '../common'; import { DataViewEditorStart } from '../../../plugins/data_view_editor/public'; import { useDiscoverServices } from './utils/use_discover_services'; - -declare module '../../share/public' { - export interface UrlGeneratorStateMapping { - [DISCOVER_APP_URL_GENERATOR]: UrlGeneratorState; - } -} +import { SEARCH_SESSION_ID_QUERY_PARAM } from './constants'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -123,11 +111,6 @@ export interface DiscoverSetup { } export interface DiscoverStart { - /** - * @deprecated Use URL locator instead. URL generator will be removed. - */ - readonly urlGenerator: undefined | UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>; - /** * `share` plugin URL locator for Discover app. Use it to generate links into * Discover application, for example, navigate: @@ -205,25 +188,11 @@ export class DiscoverPlugin private appStateUpdater = new BehaviorSubject(() => ({})); private docViewsRegistry: DocViewsRegistry | null = null; private stopUrlTracking: (() => void) | undefined = undefined; - - /** - * @deprecated - */ - private urlGenerator?: DiscoverStart['urlGenerator']; private locator?: DiscoverAppLocator; setup(core: CoreSetup, plugins: DiscoverSetupPlugins) { const baseUrl = core.http.basePath.prepend('/app/discover'); - if (plugins.share) { - this.urlGenerator = plugins.share.urlGenerators.registerUrlGenerator( - new DiscoverUrlGenerator({ - appBasePath: baseUrl, - useHash: core.uiSettings.get('state:storeInSessionStorage'), - }) - ); - } - if (plugins.share) { this.locator = plugins.share.url.locators.create( new DiscoverAppLocatorDefinition({ @@ -420,7 +389,6 @@ export class DiscoverPlugin injectTruncateStyles(core.uiSettings.get(TRUNCATE_MAX_HEIGHT)); return { - urlGenerator: this.urlGenerator, locator: this.locator, }; } diff --git a/src/plugins/discover/public/url_generator.test.ts b/src/plugins/discover/public/url_generator.test.ts deleted file mode 100644 index 765e8b36cc1ea..0000000000000 --- a/src/plugins/discover/public/url_generator.test.ts +++ /dev/null @@ -1,269 +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 { DiscoverUrlGenerator } from './url_generator'; -import { hashedItemStore, getStatesFromKbnUrl } from '../../kibana_utils/public'; -import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; -import { FilterStateStore } from '../../data/common'; - -const appBasePath: string = 'xyz/app/discover'; -const indexPatternId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; -const savedSearchId: string = '571aaf70-4c88-11e8-b3d7-01146121b73d'; - -interface SetupParams { - useHash?: boolean; -} - -const setup = async ({ useHash = false }: SetupParams = {}) => { - const generator = new DiscoverUrlGenerator({ - appBasePath, - useHash, - }); - - return { - generator, - }; -}; - -beforeEach(() => { - // @ts-expect-error - hashedItemStore.storage = mockStorage; -}); - -describe('Discover url generator', () => { - test('can create a link to Discover with no state and no saved search', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({}); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(url.startsWith(appBasePath)).toBe(true); - expect(_a).toEqual({}); - expect(_g).toEqual({}); - }); - - test('can create a link to a saved search in Discover', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ savedSearchId }); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(url.startsWith(`${appBasePath}#/view/${savedSearchId}`)).toBe(true); - expect(_a).toEqual({}); - expect(_g).toEqual({}); - }); - - test('can specify specific index pattern', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - indexPatternId, - }); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(_a).toEqual({ - index: indexPatternId, - }); - expect(_g).toEqual({}); - }); - - test('can specify specific time range', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - }); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(_a).toEqual({}); - expect(_g).toEqual({ - time: { - from: 'now-15m', - mode: 'relative', - to: 'now', - }, - }); - }); - - test('can specify query', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - query: { - language: 'kuery', - query: 'foo', - }, - }); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(_a).toEqual({ - query: { - language: 'kuery', - query: 'foo', - }, - }); - expect(_g).toEqual({}); - }); - - test('can specify local and global filters', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - filters: [ - { - meta: { - alias: 'foo', - disabled: false, - negate: false, - }, - $state: { - store: FilterStateStore.APP_STATE, - }, - }, - { - meta: { - alias: 'bar', - disabled: false, - negate: false, - }, - $state: { - store: FilterStateStore.GLOBAL_STATE, - }, - }, - ], - }); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(_a).toEqual({ - filters: [ - { - $state: { - store: 'appState', - }, - meta: { - alias: 'foo', - disabled: false, - negate: false, - }, - }, - ], - }); - expect(_g).toEqual({ - filters: [ - { - $state: { - store: 'globalState', - }, - meta: { - alias: 'bar', - disabled: false, - negate: false, - }, - }, - ], - }); - }); - - test('can set refresh interval', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - refreshInterval: { - pause: false, - value: 666, - }, - }); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(_a).toEqual({}); - expect(_g).toEqual({ - refreshInterval: { - pause: false, - value: 666, - }, - }); - }); - - test('can set time range', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - timeRange: { - from: 'now-3h', - to: 'now', - }, - }); - const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); - - expect(_a).toEqual({}); - expect(_g).toEqual({ - time: { - from: 'now-3h', - to: 'now', - }, - }); - }); - - test('can specify a search session id', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - searchSessionId: '__test__', - }); - expect(url).toMatchInlineSnapshot(`"xyz/app/discover#/?_g=()&_a=()&searchSessionId=__test__"`); - expect(url).toContain('__test__'); - }); - - test('can specify columns, interval, sort and savedQuery', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - columns: ['_source'], - interval: 'auto', - sort: [['timestamp, asc']], - savedQuery: '__savedQueryId__', - }); - expect(url).toMatchInlineSnapshot( - `"xyz/app/discover#/?_g=()&_a=(columns:!(_source),interval:auto,savedQuery:__savedQueryId__,sort:!(!('timestamp,%20asc')))"` - ); - }); - - describe('useHash property', () => { - describe('when default useHash is set to false', () => { - test('when using default, sets index pattern ID in the generated URL', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - indexPatternId, - }); - - expect(url.indexOf(indexPatternId) > -1).toBe(true); - }); - - test('when enabling useHash, does not set index pattern ID in the generated URL', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - useHash: true, - indexPatternId, - }); - - expect(url.indexOf(indexPatternId) > -1).toBe(false); - }); - }); - - describe('when default useHash is set to true', () => { - test('when using default, does not set index pattern ID in the generated URL', async () => { - const { generator } = await setup({ useHash: true }); - const url = await generator.createUrl({ - indexPatternId, - }); - - expect(url.indexOf(indexPatternId) > -1).toBe(false); - }); - - test('when disabling useHash, sets index pattern ID in the generated URL', async () => { - const { generator } = await setup(); - const url = await generator.createUrl({ - useHash: false, - indexPatternId, - }); - - expect(url.indexOf(indexPatternId) > -1).toBe(true); - }); - }); - }); -}); diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts deleted file mode 100644 index 450ad15e3598b..0000000000000 --- a/src/plugins/discover/public/url_generator.ts +++ /dev/null @@ -1,162 +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 type { UrlGeneratorsDefinition } from '../../share/public'; -import type { TimeRange, Filter, Query, QueryState, RefreshInterval } from '../../data/public'; -import { esFilters } from '../../data/public'; -import { setStateToKbnUrl } from '../../kibana_utils/public'; -import { VIEW_MODE } from './components/view_mode_toggle'; - -/** - * @deprecated - */ -export const DISCOVER_APP_URL_GENERATOR = 'DISCOVER_APP_URL_GENERATOR'; - -/** - * @deprecated - */ -export interface DiscoverUrlGeneratorState { - /** - * Optionally set saved search ID. - */ - savedSearchId?: string; - - /** - * Optionally set index pattern ID. - */ - indexPatternId?: string; - - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - - /** - * Optionally set the refresh interval. - */ - refreshInterval?: RefreshInterval; - - /** - * Optionally apply filters. - */ - filters?: Filter[]; - - /** - * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the - * saved dashboard has a query saved with it, this will _replace_ that query. - */ - query?: Query; - - /** - * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines - * whether to hash the data in the url to avoid url length issues. - */ - useHash?: boolean; - - /** - * Background search session id - */ - searchSessionId?: string; - - /** - * Columns displayed in the table - */ - columns?: string[]; - - /** - * Used interval of the histogram - */ - interval?: string; - - /** - * Array of the used sorting [[field,direction],...] - */ - sort?: string[][]; - - /** - * id of the used saved query - */ - savedQuery?: string; - viewMode?: VIEW_MODE; - hideAggregatedPreview?: boolean; -} - -interface Params { - appBasePath: string; - useHash: boolean; -} - -export const SEARCH_SESSION_ID_QUERY_PARAM = 'searchSessionId'; - -/** - * @deprecated - */ -export class DiscoverUrlGenerator - implements UrlGeneratorsDefinition -{ - constructor(private readonly params: Params) {} - - public readonly id = DISCOVER_APP_URL_GENERATOR; - - public readonly createUrl = async ({ - useHash = this.params.useHash, - filters, - indexPatternId, - query, - refreshInterval, - savedSearchId, - timeRange, - searchSessionId, - columns, - savedQuery, - sort, - interval, - viewMode, - hideAggregatedPreview, - }: DiscoverUrlGeneratorState): Promise => { - const savedSearchPath = savedSearchId ? `view/${encodeURIComponent(savedSearchId)}` : ''; - const appState: { - query?: Query; - filters?: Filter[]; - index?: string; - columns?: string[]; - interval?: string; - sort?: string[][]; - savedQuery?: string; - viewMode?: VIEW_MODE; - hideAggregatedPreview?: boolean; - } = {}; - const queryState: QueryState = {}; - - if (query) appState.query = query; - if (filters && filters.length) - appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); - if (indexPatternId) appState.index = indexPatternId; - if (columns) appState.columns = columns; - if (savedQuery) appState.savedQuery = savedQuery; - if (sort) appState.sort = sort; - if (interval) appState.interval = interval; - - if (timeRange) queryState.time = timeRange; - if (filters && filters.length) - queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); - if (refreshInterval) queryState.refreshInterval = refreshInterval; - if (viewMode) appState.viewMode = viewMode; - if (hideAggregatedPreview) appState.hideAggregatedPreview = hideAggregatedPreview; - - let url = `${this.params.appBasePath}#/${savedSearchPath}`; - url = setStateToKbnUrl('_g', queryState, { useHash }, url); - url = setStateToKbnUrl('_a', appState, { useHash }, url); - - if (searchSessionId) { - url = `${url}&${SEARCH_SESSION_ID_QUERY_PARAM}=${searchSessionId}`; - } - - return url; - }; -} diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index c9c9692e6986b..afd70cc5bbee7 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -164,7 +164,7 @@ export const getUiSettings: (docLinks: DocLinksServiceSetup) => Record { } } +/** @deprecated Use `ExitFullScreenButton` from `src/plugins/shared_ux/public`. */ export const ExitFullScreenButton = ExitFullScreenButtonUi; diff --git a/src/plugins/share/common/url_service/locators/types.ts b/src/plugins/share/common/url_service/locators/types.ts index c8cfcc503ce35..641906a8b9191 100644 --- a/src/plugins/share/common/url_service/locators/types.ts +++ b/src/plugins/share/common/url_service/locators/types.ts @@ -23,7 +23,7 @@ export interface ILocatorClient extends PersistableStateService { /** * Create and register a new locator. * - * @param urlGenerator Definition of the new locator. + * @param locatorDefinition Definition of the new locator. */ create

(locatorDefinition: LocatorDefinition

): LocatorPublic

; diff --git a/src/plugins/share/public/index.ts b/src/plugins/share/public/index.ts index 86d29f6ad84cf..c4c097f992538 100644 --- a/src/plugins/share/public/index.ts +++ b/src/plugins/share/public/index.ts @@ -12,8 +12,6 @@ export { CSV_QUOTE_VALUES_SETTING, CSV_SEPARATOR_SETTING } from '../common/const export type { LocatorDefinition, LocatorPublic, KibanaLocation } from '../common/url_service'; -export type { UrlGeneratorStateMapping } from './url_generators/url_generator_definition'; - export type { SharePluginSetup, SharePluginStart } from './plugin'; export type { @@ -25,14 +23,6 @@ export type { BrowserUrlService, } from './types'; -export type { - UrlGeneratorId, - UrlGeneratorState, - UrlGeneratorsDefinition, - UrlGeneratorContract, -} from './url_generators'; -export { UrlGeneratorsService } from './url_generators'; - export type { RedirectOptions } from '../common/url_service'; export { useLocatorUrl } from '../common/url_service/locators/use_locator_url'; diff --git a/src/plugins/share/public/mocks.ts b/src/plugins/share/public/mocks.ts index 066767b87fcea..7406dbb12f883 100644 --- a/src/plugins/share/public/mocks.ts +++ b/src/plugins/share/public/mocks.ts @@ -39,9 +39,6 @@ const url = new UrlService { const setupContract: Setup = { register: jest.fn(), - urlGenerators: { - registerUrlGenerator: jest.fn(), - }, url, navigate: jest.fn(), setAnonymousAccessServiceProvider: jest.fn(), @@ -52,9 +49,6 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { url, - urlGenerators: { - getUrlGenerator: jest.fn(), - }, toggleShareContextMenu: jest.fn(), navigate: jest.fn(), }; diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index ce9eeb5c2da33..5a7ff7b12cdd2 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -11,11 +11,6 @@ import './index.scss'; import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { ShareMenuManager, ShareMenuManagerStart } from './services'; import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services'; -import { - UrlGeneratorsService, - UrlGeneratorsSetup, - UrlGeneratorsStart, -} from './url_generators/url_generator_service'; import { UrlService } from '../common/url_service'; import { RedirectManager } from './url_service'; import type { RedirectOptions } from '../common/url_service/locators/redirect'; @@ -31,13 +26,6 @@ import type { BrowserUrlService } from './types'; /** @public */ export type SharePluginSetup = ShareMenuRegistrySetup & { - /** - * @deprecated - * - * URL Generators are deprecated use UrlService instead. - */ - urlGenerators: UrlGeneratorsSetup; - /** * Utilities to work with URL locators and short URLs. */ @@ -57,13 +45,6 @@ export type SharePluginSetup = ShareMenuRegistrySetup & { /** @public */ export type SharePluginStart = ShareMenuManagerStart & { - /** - * @deprecated - * - * URL Generators are deprecated use UrlService instead. - */ - urlGenerators: UrlGeneratorsStart; - /** * Utilities to work with URL locators and short URLs. */ @@ -79,7 +60,6 @@ export type SharePluginStart = ShareMenuManagerStart & { export class SharePlugin implements Plugin { private readonly shareMenuRegistry = new ShareMenuRegistry(); private readonly shareContextMenu = new ShareMenuManager(); - private readonly urlGeneratorsService = new UrlGeneratorsService(); private redirectManager?: RedirectManager; private url?: BrowserUrlService; @@ -128,7 +108,6 @@ export class SharePlugin implements Plugin { return { ...this.shareMenuRegistry.setup(), - urlGenerators: this.urlGeneratorsService.setup(core), url: this.url, navigate: (options: RedirectOptions) => this.redirectManager!.navigate(options), setAnonymousAccessServiceProvider: (provider: () => AnonymousAccessServiceContract) => { @@ -150,7 +129,6 @@ export class SharePlugin implements Plugin { return { ...sharingContextMenuStart, - urlGenerators: this.urlGeneratorsService.start(core), url: this.url!, navigate: (options: RedirectOptions) => this.redirectManager!.navigate(options), }; diff --git a/src/plugins/share/public/url_generators/README.md b/src/plugins/share/public/url_generators/README.md deleted file mode 100644 index f948354aad959..0000000000000 --- a/src/plugins/share/public/url_generators/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# URL Generators are deprecated - -__Below is documentation of URL Generators, which are now deprecated and will be removed in favor of URL locators in 7.14.__ - ---- - -## URL Generator Services - -Developers who maintain pages in Kibana that other developers may want to link to -can register a direct access link generator. This provides backward compatibility support -so the developer of the app/page has a way to change their url structure without -breaking users of this system. If users were to generate the urls on their own, -using string concatenation, those links may break often. - -Owners: Kibana App Arch team. - -## Producer Usage - -If you are registering a new generator, don't forget to add a mapping of id to state - -```ts -declare module '../../share/public' { - export interface UrlGeneratorStateMapping { - [MY_GENERATOR]: MyState; - } -} -``` - -### Migration - -Once your generator is released, you should *never* change the `MyState` type, nor the value of `MY_GENERATOR`. -Instead, register a new generator id, with the new state type, and add a migration function to convert to it. - -To avoid having to refactor many run time usages of the old id, change the _value_ of the generator id, but not -the name itself. For example: - -Initial release: -```ts -export const MY_GENERATOR = 'MY_GENERATOR'; -export const MyState { - foo: string; -} -export interface UrlGeneratorStateMapping { - [MY_GENERATOR]: UrlGeneratorState; -} -``` - -Second release: -```ts - // Value stays the same here! This is important. - export const MY_LEGACY_GENERATOR_V1 = 'MY_GENERATOR'; - // Always point the const `MY_GENERATOR` to the most - // recent version of the state to avoid a large refactor. - export const MY_GENERATOR = 'MY_GENERATOR_V2'; - - // Same here, the mapping stays the same, but the names change. - export const MyLegacyState { - foo: string; - } - // New type, old name! - export const MyState { - bar: string; - } - export interface UrlGeneratorStateMapping { - [MY_LEGACY_GENERATOR_V1]: UrlGeneratorState; - [MY_GENERATOR]: UrlGeneratorState; - } -``` - -### Examples - -Working examples of registered link generators can be found in `examples/url_generator_examples` folder. Run these -examples via - -``` -yarn start --run-examples -``` - -## Consumer Usage - -Consumers of this service can use the ids and state to create URL strings: - -```ts - const { id, state } = getLinkData(); - const generator = urlGeneratorPluginStart.getLinkGenerator(id); - if (generator.isDeprecated) { - // Consumers have a few options here. - - // If the consumer constrols the persisted data, they can migrate this data and - // update it. Something like this: - const { id: newId, state: newState } = await generator.migrate(state); - replaceLegacyData({ oldId: id, newId, newState }); - - // If the consumer does not control the persisted data store, they can warn the - // user that they are using a deprecated id and should update the data on their - // own. - alert(`This data is deprecated, please generate new URL data.`); - - // They can also choose to do nothing. Calling `createUrl` will internally migrate this - // data. Depending on the cost, we may choose to keep support for deprecated generators - // along for a long time, using telemetry to make this decision. However another - // consideration is how many migrations are taking place and whether this is creating a - // performance issue. - } - const link = await generator.createUrl(savedLink.state); -``` - -**As a consumer, you should not persist the url string!** - -As soon as you do, you have lost your migration options. Instead you should store the id -and the state object. This will let you recreate the migrated url later. - -### Examples - -Working examples of consuming registered link generators can be found in `examples/url_generator_explorer` folder. Run these -via - -``` -yarn start --run-examples -``` diff --git a/src/plugins/share/public/url_generators/url_generator_definition.ts b/src/plugins/share/public/url_generators/url_generator_definition.ts deleted file mode 100644 index 32fe51a16d197..0000000000000 --- a/src/plugins/share/public/url_generators/url_generator_definition.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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 type UrlGeneratorId = string; - -export interface UrlGeneratorState< - S extends {}, - I extends string | undefined = undefined, - MS extends {} | undefined = undefined -> { - State: S; - MigratedId?: I; - MigratedState?: MS; -} - -export interface UrlGeneratorStateMapping { - // The `any` here is quite unfortunate. Using `object` actually gives no type errors in my IDE - // but running `node scripts/type_check` will cause an error: - // examples/url_generators_examples/public/url_generator.ts:77:66 - - // error TS2339: Property 'name' does not exist on type 'object'. However it's correctly - // typed when I edit that file. - [key: string]: UrlGeneratorState; -} - -export interface UrlGeneratorsDefinition { - id: Id; - createUrl?: (state: UrlGeneratorStateMapping[Id]['State']) => Promise; - isDeprecated?: boolean; - migrate?: (state: UrlGeneratorStateMapping[Id]['State']) => Promise<{ - state: UrlGeneratorStateMapping[Id]['MigratedState']; - id: UrlGeneratorStateMapping[Id]['MigratedId']; - }>; -} diff --git a/src/plugins/share/public/url_generators/url_generator_internal.ts b/src/plugins/share/public/url_generators/url_generator_internal.ts deleted file mode 100644 index 7f7dc0f63f87b..0000000000000 --- a/src/plugins/share/public/url_generators/url_generator_internal.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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'; -import { UrlGeneratorsStart } from './url_generator_service'; -import { - UrlGeneratorStateMapping, - UrlGeneratorId, - UrlGeneratorsDefinition, -} from './url_generator_definition'; -import { UrlGeneratorContract } from './url_generator_contract'; - -export class UrlGeneratorInternal { - constructor( - private spec: UrlGeneratorsDefinition, - private getGenerator: UrlGeneratorsStart['getUrlGenerator'] - ) { - if (spec.isDeprecated && !spec.migrate) { - throw new Error( - i18n.translate('share.urlGenerators.error.noMigrationFnProvided', { - defaultMessage: - 'If the access link generator is marked as deprecated, you must provide a migration function.', - }) - ); - } - - if (!spec.isDeprecated && spec.migrate) { - throw new Error( - i18n.translate('share.urlGenerators.error.migrationFnGivenNotDeprecated', { - defaultMessage: - 'If you provide a migration function, you must mark this generator as deprecated', - }) - ); - } - - if (!spec.createUrl && !spec.isDeprecated) { - throw new Error( - i18n.translate('share.urlGenerators.error.noCreateUrlFnProvided', { - defaultMessage: - 'This generator is not marked as deprecated. Please provide a createUrl fn.', - }) - ); - } - - if (spec.createUrl && spec.isDeprecated) { - throw new Error( - i18n.translate('share.urlGenerators.error.createUrlFnProvided', { - defaultMessage: 'This generator is marked as deprecated. Do not supply a createUrl fn.', - }) - ); - } - } - - getPublicContract(): UrlGeneratorContract { - return { - id: this.spec.id, - createUrl: async (state: UrlGeneratorStateMapping[Id]['State']) => { - if (this.spec.migrate && !this.spec.createUrl) { - const { id, state: newState } = await this.spec.migrate(state); - - // eslint-disable-next-line - console.warn(`URL generator is deprecated and may not work in future versions. Please migrate your data.`); - - return this.getGenerator(id!).createUrl(newState!); - } - - return this.spec.createUrl!(state); - }, - isDeprecated: !!this.spec.isDeprecated, - }; - } -} diff --git a/src/plugins/share/public/url_generators/url_generator_service.test.ts b/src/plugins/share/public/url_generators/url_generator_service.test.ts deleted file mode 100644 index c07aa3f915b2e..0000000000000 --- a/src/plugins/share/public/url_generators/url_generator_service.test.ts +++ /dev/null @@ -1,120 +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 { UrlGeneratorsService } from './url_generator_service'; -import { coreMock } from '../../../../core/public/mocks'; - -const service = new UrlGeneratorsService(); - -const setup = service.setup(coreMock.createSetup()); -const start = service.start(coreMock.createStart()); - -test('Asking for a generator that does not exist throws an error', () => { - expect(() => start.getUrlGenerator('noexist')).toThrowError(); -}); - -test('Registering and retrieving a generator', async () => { - const generator = setup.registerUrlGenerator({ - id: 'TEST_GENERATOR', - createUrl: () => Promise.resolve('myurl'), - }); - - expect(generator).toMatchInlineSnapshot(` - Object { - "createUrl": [Function], - "id": "TEST_GENERATOR", - "isDeprecated": false, - } - `); - expect(await generator.createUrl({})).toBe('myurl'); - - const retrievedGenerator = start.getUrlGenerator('TEST_GENERATOR'); - expect(retrievedGenerator).toMatchInlineSnapshot(` - Object { - "createUrl": [Function], - "id": "TEST_GENERATOR", - "isDeprecated": false, - } - `); - expect(await generator.createUrl({})).toBe('myurl'); -}); - -test('Registering a generator with a createUrl function that is deprecated throws an error', () => { - expect(() => - setup.registerUrlGenerator({ - id: 'TEST_GENERATOR', - migrate: () => Promise.resolve({ id: '', state: {} }), - createUrl: () => Promise.resolve('myurl'), - isDeprecated: true, - }) - ).toThrowError( - new Error('This generator is marked as deprecated. Do not supply a createUrl fn.') - ); -}); - -test('Registering a deprecated generator with no migration function throws an error', () => { - expect(() => - setup.registerUrlGenerator({ - id: 'TEST_GENERATOR', - isDeprecated: true, - }) - ).toThrowError( - new Error( - 'If the access link generator is marked as deprecated, you must provide a migration function.' - ) - ); -}); - -test('Registering a generator with no functions throws an error', () => { - expect(() => - setup.registerUrlGenerator({ - id: 'TEST_GENERATOR', - }) - ).toThrowError( - new Error('This generator is not marked as deprecated. Please provide a createUrl fn.') - ); -}); - -test('Registering a generator with a migrate function that is not deprecated throws an error', () => { - expect(() => - setup.registerUrlGenerator({ - id: 'TEST_GENERATOR', - migrate: () => Promise.resolve({ id: '', state: {} }), - isDeprecated: false, - }) - ).toThrowError( - new Error('If you provide a migration function, you must mark this generator as deprecated') - ); -}); - -test('Registering a generator with a migrate function and a createUrl fn throws an error', () => { - expect(() => - setup.registerUrlGenerator({ - id: 'TEST_GENERATOR', - createUrl: () => Promise.resolve('myurl'), - migrate: () => Promise.resolve({ id: '', state: {} }), - }) - ).toThrowError(); -}); - -test('Generator returns migrated url', async () => { - setup.registerUrlGenerator({ - id: 'v1', - migrate: (state: { bar: string }) => Promise.resolve({ id: 'v2', state: { foo: state.bar } }), - isDeprecated: true, - }); - setup.registerUrlGenerator({ - id: 'v2', - createUrl: (state: { foo: string }) => Promise.resolve(`www.${state.foo}.com`), - isDeprecated: false, - }); - - const generator = start.getUrlGenerator('v1'); - expect(generator.isDeprecated).toBe(true); - expect(await generator.createUrl({ bar: 'hi' })).toEqual('www.hi.com'); -}); diff --git a/src/plugins/share/public/url_generators/url_generator_service.ts b/src/plugins/share/public/url_generators/url_generator_service.ts deleted file mode 100644 index 5a8e7a1b5c17a..0000000000000 --- a/src/plugins/share/public/url_generators/url_generator_service.ts +++ /dev/null @@ -1,76 +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 } from 'src/core/public'; -import { i18n } from '@kbn/i18n'; -import { UrlGeneratorId, UrlGeneratorsDefinition } from './url_generator_definition'; -import { UrlGeneratorInternal } from './url_generator_internal'; -import { UrlGeneratorContract } from './url_generator_contract'; - -export interface UrlGeneratorsStart { - /** - * @deprecated - * - * URL Generators are deprecated, use URL locators in UrlService instead. - */ - getUrlGenerator: (urlGeneratorId: T) => UrlGeneratorContract; -} - -export interface UrlGeneratorsSetup { - /** - * @deprecated - * - * URL Generators are deprecated, use URL locators in UrlService instead. - */ - registerUrlGenerator: ( - generator: UrlGeneratorsDefinition - ) => UrlGeneratorContract; -} - -export class UrlGeneratorsService implements Plugin { - // Unfortunate use of any here, but I haven't figured out how to type this any better without - // getting warnings. - private urlGenerators: Map> = new Map(); - - constructor() {} - - public setup(core: CoreSetup) { - const setup: UrlGeneratorsSetup = { - registerUrlGenerator: ( - generatorOptions: UrlGeneratorsDefinition - ) => { - const generator = new UrlGeneratorInternal(generatorOptions, this.getUrlGenerator); - this.urlGenerators.set(generatorOptions.id, generator); - return generator.getPublicContract(); - }, - }; - return setup; - } - - public start(core: CoreStart) { - const start: UrlGeneratorsStart = { - getUrlGenerator: this.getUrlGenerator, - }; - return start; - } - - public stop() {} - - private readonly getUrlGenerator = (id: UrlGeneratorId) => { - const generator = this.urlGenerators.get(id); - if (!generator) { - throw new Error( - i18n.translate('share.urlGenerators.errors.noGeneratorWithId', { - defaultMessage: 'No generator found with id {id}', - values: { id }, - }) - ); - } - return generator.getPublicContract(); - }; -} diff --git a/src/plugins/shared_ux/public/components/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/shared_ux/public/components/exit_full_screen_button/exit_full_screen_button.tsx index eb941593bdf1a..1f0b2f43a6b25 100644 --- a/src/plugins/shared_ux/public/components/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/shared_ux/public/components/exit_full_screen_button/exit_full_screen_button.tsx @@ -25,8 +25,10 @@ export interface Props { /** * A service-enabled component that provides Kibana-specific functionality to the `ExitFullScreenButton` - * component. Use of this component requires both the `EuiTheme` context as well as the Shared UX - * `ServicesProvider`. + * component. + * + * Use of this component requires both the `EuiTheme` context as well as either a configured Shared UX + * `ServicesProvider` or the `ServicesContext` provided by the Shared UX public plugin contract. * * See shared-ux/public/services for information. */ diff --git a/src/plugins/shared_ux/public/components/exit_full_screen_button/index.ts b/src/plugins/shared_ux/public/components/exit_full_screen_button/index.ts index 0baf60d8499ca..66ffe2976d786 100644 --- a/src/plugins/shared_ux/public/components/exit_full_screen_button/index.ts +++ b/src/plugins/shared_ux/public/components/exit_full_screen_button/index.ts @@ -5,13 +5,5 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -/* eslint-disable import/no-default-export */ -import { ExitFullScreenButton } from './exit_full_screen_button'; export { ExitFullScreenButton } from './exit_full_screen_button'; - -/** - * Exporting the ExitFullScreenButton component as a default export so it can be - * loaded by React.lazy. - */ -export default ExitFullScreenButton; diff --git a/src/plugins/shared_ux/public/components/index.ts b/src/plugins/shared_ux/public/components/index.ts index f3c25ca023e8d..3c5e4424f99f5 100644 --- a/src/plugins/shared_ux/public/components/index.ts +++ b/src/plugins/shared_ux/public/components/index.ts @@ -13,7 +13,11 @@ import { withSuspense } from './utility'; * The Lazily-loaded `ExitFullScreenButton` component. Consumers should use `React.Suspennse` or the * `withSuspense` HOC to load this component. */ -export const LazyExitFullScreenButton = React.lazy(() => import('./exit_full_screen_button')); +export const LazyExitFullScreenButton = React.lazy(() => + import('./exit_full_screen_button').then(({ ExitFullScreenButton }) => ({ + default: ExitFullScreenButton, + })) +); /** * A `ExitFullScreenButton` component that is wrapped by the `withSuspense` HOC. This component can diff --git a/src/plugins/shared_ux/public/services/index.tsx b/src/plugins/shared_ux/public/services/index.tsx index acc8b9294d1df..0677f3ef0ca84 100644 --- a/src/plugins/shared_ux/public/services/index.tsx +++ b/src/plugins/shared_ux/public/services/index.tsx @@ -27,8 +27,11 @@ const ServicesContext = createContext(servicesFactory()); /** * The `React.Context` Provider component for the `SharedUXServices` context. Any - * plugin or environemnt that consumes SharedUX components needs to wrap their React + * plugin or environment that consumes SharedUX components needs to wrap their React * tree with this provider. + * + * Within a plugin, you can use the `ServicesContext` provided by the SharedUX plugin start + * lifeycle method. */ export const ServicesProvider: FC = ({ children, ...services }) => ( {children} diff --git a/src/plugins/vis_types/timeseries/public/metrics_type.ts b/src/plugins/vis_types/timeseries/public/metrics_type.ts index ff613c0eadb06..0c5b365938058 100644 --- a/src/plugins/vis_types/timeseries/public/metrics_type.ts +++ b/src/plugins/vis_types/timeseries/public/metrics_type.ts @@ -26,7 +26,6 @@ import { } from '../../../visualizations/public'; import { getDataStart } from './services'; import type { TimeseriesVisDefaultParams, TimeseriesVisParams } from './types'; -import { triggerTSVBtoLensConfiguration } from './trigger_action'; import type { IndexPatternValue, Panel } from '../common/types'; import { RequestAdapter } from '../../../inspector/public'; @@ -169,6 +168,8 @@ export const metricsVisDefinition: VisTypeDefinition< return []; }, navigateToLens: async (params?: VisParams) => { + const { triggerTSVBtoLensConfiguration } = await import('./trigger_action'); + const triggerConfiguration = params ? await triggerTSVBtoLensConfiguration(params as Panel) : null; diff --git a/src/plugins/vis_types/timeseries/public/trigger_action/get_series.test.ts b/src/plugins/vis_types/timeseries/public/trigger_action/get_series.test.ts index 7410c95677cff..3b01aeebb2dcf 100644 --- a/src/plugins/vis_types/timeseries/public/trigger_action/get_series.test.ts +++ b/src/plugins/vis_types/timeseries/public/trigger_action/get_series.test.ts @@ -84,6 +84,32 @@ describe('getSeries', () => { ]); }); + test('should return the correct formula config for a positive only function', () => { + const metric = [ + { + field: 'day_of_week_i', + id: '123456', + type: 'max', + }, + { + id: '891011', + type: 'positive_only', + field: '123456', + }, + ] as Metric[]; + const config = getSeries(metric); + expect(config).toStrictEqual([ + { + agg: 'formula', + fieldName: 'document', + isFullReference: true, + params: { + formula: 'clamp(max(day_of_week_i), 0, max(day_of_week_i))', + }, + }, + ]); + }); + test('should return the correct config for the cumulative sum on count', () => { const metric = [ { @@ -299,6 +325,42 @@ describe('getSeries', () => { ]); }); + test('should return the correct formula config for a top_hit size 1 aggregation', () => { + const metric = [ + { + id: '12345', + type: 'top_hit', + field: 'day_of_week_i', + size: 1, + order_by: 'timestamp', + }, + ] as Metric[]; + const config = getSeries(metric); + expect(config).toStrictEqual([ + { + agg: 'last_value', + fieldName: 'day_of_week_i', + isFullReference: false, + params: { + sortField: 'timestamp', + }, + }, + ]); + }); + + test('should return null for a top_hit size >1 aggregation', () => { + const metric = [ + { + id: '12345', + type: 'top_hit', + field: 'day_of_week_i', + size: 2, + }, + ] as Metric[]; + const config = getSeries(metric); + expect(config).toBeNull(); + }); + test('should return the correct formula for the math aggregation with percentiles as variables', () => { const metric = [ { diff --git a/src/plugins/vis_types/timeseries/public/trigger_action/get_series.ts b/src/plugins/vis_types/timeseries/public/trigger_action/get_series.ts index eed1594300b92..5e7d39f3085f6 100644 --- a/src/plugins/vis_types/timeseries/public/trigger_action/get_series.ts +++ b/src/plugins/vis_types/timeseries/public/trigger_action/get_series.ts @@ -57,6 +57,15 @@ export const getSeries = (metrics: Metric[]): VisualizeEditorLayersContext['metr continue; } const currentMetric = metrics[layerMetricIdx]; + // We can only support top_hit with size 1 + if ( + (currentMetric.type === 'top_hit' && + currentMetric?.size && + Number(currentMetric?.size) !== 1) || + currentMetric?.order === 'asc' + ) { + return null; + } // should treat percentiles differently if (currentMetric.type === 'percentile') { @@ -125,6 +134,14 @@ export const getSeries = (metrics: Metric[]): VisualizeEditorLayersContext['metr } break; } + case 'positive_only': { + const formula = getSiblingPipelineSeriesFormula(aggregation, metrics[metricIdx], metrics); + if (!formula) { + return null; + } + metricsArray = getFormulaSeries(formula) as VisualizeEditorLayersContext['metrics']; + break; + } case 'avg_bucket': case 'max_bucket': case 'min_bucket': @@ -144,6 +161,29 @@ export const getSeries = (metrics: Metric[]): VisualizeEditorLayersContext['metr metricsArray = getFormulaSeries(formula); break; } + case 'top_hit': { + const currentMetric = metrics[metricIdx]; + // We can only support top_hit with size 1 + if ( + (currentMetric?.size && Number(currentMetric?.size) !== 1) || + currentMetric?.order === 'asc' + ) { + return null; + } + const timeScale = getTimeScale(currentMetric); + metricsArray = [ + { + agg: aggregationMap.name, + isFullReference: aggregationMap.isFullReference, + fieldName: fieldName ?? 'document', + params: { + ...(timeScale && { timeScale }), + ...(currentMetric?.order_by && { sortField: currentMetric?.order_by }), + }, + }, + ]; + break; + } default: { const timeScale = getTimeScale(metrics[metricIdx]); metricsArray = [ diff --git a/src/plugins/vis_types/timeseries/public/trigger_action/metrics_helpers.ts b/src/plugins/vis_types/timeseries/public/trigger_action/metrics_helpers.ts index 07140c9fdd9d1..dc9457ac1fafc 100644 --- a/src/plugins/vis_types/timeseries/public/trigger_action/metrics_helpers.ts +++ b/src/plugins/vis_types/timeseries/public/trigger_action/metrics_helpers.ts @@ -192,20 +192,31 @@ export const getSiblingPipelineSeriesFormula = ( return null; } const aggregationMap = SUPPORTED_METRICS[aggregation]; - const subMetricField = subFunctionMetric.field; + const subMetricField = subFunctionMetric.type !== 'count' ? subFunctionMetric.field : ''; // support nested aggs with formula const additionalSubFunction = metrics.find((metric) => metric.id === subMetricField); let formula = `${aggregationMap.name}(`; + let minMax = ''; if (additionalSubFunction) { const additionalPipelineAggMap = SUPPORTED_METRICS[additionalSubFunction.type]; if (!additionalPipelineAggMap) { return null; } + const additionalSubFunctionField = + additionalSubFunction.type !== 'count' ? additionalSubFunction.field : ''; + if (currentMetric.type === 'positive_only') { + minMax = `, 0, ${pipelineAggMap.name}(${additionalPipelineAggMap.name}(${ + additionalSubFunctionField ?? '' + }))`; + } formula += `${pipelineAggMap.name}(${additionalPipelineAggMap.name}(${ - additionalSubFunction.field ?? '' - })))`; + additionalSubFunctionField ?? '' + }))${minMax})`; } else { - formula += `${pipelineAggMap.name}(${subFunctionMetric.field ?? ''}))`; + if (currentMetric.type === 'positive_only') { + minMax = `, 0, ${pipelineAggMap.name}(${subMetricField ?? ''})`; + } + formula += `${pipelineAggMap.name}(${subMetricField ?? ''})${minMax})`; } return formula; }; @@ -262,7 +273,8 @@ export const getFormulaEquivalent = ( case 'avg_bucket': case 'max_bucket': case 'min_bucket': - case 'sum_bucket': { + case 'sum_bucket': + case 'positive_only': { return getSiblingPipelineSeriesFormula(currentMetric.type, currentMetric, metrics); } case 'count': { diff --git a/src/plugins/vis_types/timeseries/public/trigger_action/supported_metrics.ts b/src/plugins/vis_types/timeseries/public/trigger_action/supported_metrics.ts index b3d58d81105ab..354b60c31854a 100644 --- a/src/plugins/vis_types/timeseries/public/trigger_action/supported_metrics.ts +++ b/src/plugins/vis_types/timeseries/public/trigger_action/supported_metrics.ts @@ -80,8 +80,16 @@ export const SUPPORTED_METRICS: { [key: string]: AggOptions } = { name: 'filter_ratio', isFullReference: false, }, + top_hit: { + name: 'last_value', + isFullReference: false, + }, math: { name: 'formula', isFullReference: true, }, + positive_only: { + name: 'clamp', + isFullReference: true, + }, }; diff --git a/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx b/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx index 66cb87d0d6138..f9b6f3c0e36df 100644 --- a/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx +++ b/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import React, { useEffect, useCallback, useRef } from 'react'; -import { EuiResizeObserver } from '@elastic/eui'; +import React, { useEffect, useRef, useMemo, useCallback } from 'react'; +import { EuiResizeObserver, EuiResizeObserverProps } from '@elastic/eui'; import { throttle } from 'lodash'; import type { IInterpreterRenderHandlers, RenderMode } from 'src/plugins/expressions'; @@ -27,6 +27,8 @@ interface VegaVisComponentProps { type VegaVisController = InstanceType>; +const THROTTLE_INTERVAL = 300; + export const VegaVisComponent = ({ visData, fireEvent, @@ -35,6 +37,7 @@ export const VegaVisComponent = ({ renderMode, }: VegaVisComponentProps) => { const chartDiv = useRef(null); + const renderCompleted = useRef(false); const visController = useRef(null); useEffect(() => { @@ -42,7 +45,6 @@ export const VegaVisComponent = ({ const VegaVis = createVegaVisualization(deps, renderMode); visController.current = new VegaVis(chartDiv.current, fireEvent); } - return () => { visController.current?.destroy(); visController.current = null; @@ -50,23 +52,40 @@ export const VegaVisComponent = ({ }, [deps, fireEvent, renderMode]); useEffect(() => { + const asyncRender = async (visCtrl: VegaVisController) => { + await visCtrl.render(visData); + renderCompleted.current = true; + renderComplete(); + }; + if (visController.current) { - visController.current.render(visData).then(renderComplete); + asyncRender(visController.current); } - }, [visData, renderComplete]); + }, [renderComplete, visData]); + + const resizeChart = useMemo( + () => + throttle( + (dimensions) => { + visController.current?.resize(dimensions); + }, + THROTTLE_INTERVAL, + { leading: false, trailing: true } + ), + [] + ); - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const updateChartSize = useCallback( - throttle(() => { - if (visController.current) { - visController.current.render(visData).then(renderComplete); + const onContainerResize: EuiResizeObserverProps['onResize'] = useCallback( + (dimensions) => { + if (renderCompleted.current) { + resizeChart(dimensions); } - }, 300), - [renderComplete, visData] + }, + [resizeChart] ); return ( - + {(resizeRef) => (

diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.d.ts b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.d.ts index a1d79394de4eb..1ddd2849e3f39 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.d.ts +++ b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.d.ts @@ -34,6 +34,7 @@ export class VegaBaseView { _addDestroyHandler(handler: Function): void; destroy(): Promise; + resize(dimensions?: { height: number; width: number }): Promise; _$container: any; _$controls: any; diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js index 25cfcdb4dde04..c6ec924f07d9d 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js @@ -262,16 +262,19 @@ export class VegaBaseView { } } - resize() { + async resize(dimensions) { if (this._parser.useResize && this._view) { - this.updateVegaSize(this._view); - return this._view.runAsync(); + this.updateVegaSize(this._view, dimensions); + await this._view.runAsync(); + + // The derived class should create this method + this.onViewContainerResize?.(); } } - updateVegaSize(view) { - const width = Math.floor(Math.max(0, this._$container.width())); - const height = Math.floor(Math.max(0, this._$container.height())); + updateVegaSize(view, dimensions) { + const width = Math.floor(Math.max(0, dimensions?.width ?? this._$container.width())); + const height = Math.floor(Math.max(0, dimensions?.height ?? this._$container.height())); if (view.width() !== width || view.height() !== height) { view.width(width).height(height); diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts b/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts index be68abaebc638..fe8d85a011442 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts +++ b/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts @@ -46,6 +46,8 @@ async function updateVegaView(mapBoxInstance: Map, vegaView: View) { } export class VegaMapView extends VegaBaseView { + private mapBoxInstance?: Map; + private get shouldShowZoomControl() { return Boolean(this._parser.mapConfig.zoomControl); } @@ -139,6 +141,7 @@ export class VegaMapView extends VegaBaseView { }; mapBoxInstance.once('load', initMapComponents); + this.mapBoxInstance = mapBoxInstance; }); } @@ -193,4 +196,8 @@ export class VegaMapView extends VegaBaseView { await this.initMapContainer(vegaView); } + + protected async onViewContainerResize() { + this.mapBoxInstance?.resize(); + } } diff --git a/src/plugins/vis_types/vega/public/vega_vis_renderer.tsx b/src/plugins/vis_types/vega/public/vega_vis_renderer.tsx index a7bab7f0f0860..ac36c9f2d20e1 100644 --- a/src/plugins/vis_types/vega/public/vega_vis_renderer.tsx +++ b/src/plugins/vis_types/vega/public/vega_vis_renderer.tsx @@ -27,7 +27,6 @@ export const getVegaVisRenderer: ( handlers.onDestroy(() => { unmountComponentAtNode(domNode); }); - render( diff --git a/src/plugins/vis_types/vega/public/vega_visualization.ts b/src/plugins/vis_types/vega/public/vega_visualization.ts index f9a18067e6886..49e6355a7109d 100644 --- a/src/plugins/vis_types/vega/public/vega_visualization.ts +++ b/src/plugins/vis_types/vega/public/vega_visualization.ts @@ -16,6 +16,7 @@ import { createVegaStateRestorer } from './lib/vega_state_restorer'; type VegaVisType = new (el: HTMLDivElement, fireEvent: IInterpreterRenderHandlers['event']) => { render(visData: VegaParser): Promise; + resize(dimensions?: { height: number; width: number }): Promise; destroy(): void; }; @@ -97,6 +98,10 @@ export const createVegaVisualization = ( } } + async resize(dimensions?: { height: number; width: number }) { + return this.vegaView?.resize(dimensions); + } + destroy() { this.vegaStateRestorer.clear(); this.vegaView?.destroy(); diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index 24b451533532f..efc3bbf8314f8 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -401,7 +401,7 @@ export class VisualizeEmbeddable const parentContext = this.parent?.getInput().executionContext; const child: KibanaExecutionContext = { type: 'visualization', - name: this.vis.type.title, + name: this.vis.type.name, id: this.vis.id ?? 'an_unsaved_vis', description: this.vis.title || this.input.title || this.vis.type.name, url: this.output.editUrl, diff --git a/test/functional/apps/context/_date_nanos.ts b/test/functional/apps/context/_date_nanos.ts index 6b7b28a4c7690..84793150b3edc 100644 --- a/test/functional/apps/context/_date_nanos.ts +++ b/test/functional/apps/context/_date_nanos.ts @@ -24,16 +24,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_date_nanos']); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/date_nanos'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/date_nanos'); await kibanaServer.uiSettings.replace({ defaultIndex: TEST_INDEX_PATTERN }); await kibanaServer.uiSettings.update({ 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, 'context:step': `${TEST_STEP_SIZE}`, + 'doc_table:legacy': true, }); }); after(async function unloadMakelogs() { await security.testUser.restoreDefaults(); await esArchiver.unload('test/functional/fixtures/es_archiver/date_nanos'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); it('displays predessors - anchor - successors in right order ', async function () { diff --git a/test/functional/apps/context/_date_nanos_custom_timestamp.ts b/test/functional/apps/context/_date_nanos_custom_timestamp.ts index 43714804a1912..1c9417968f8ab 100644 --- a/test/functional/apps/context/_date_nanos_custom_timestamp.ts +++ b/test/functional/apps/context/_date_nanos_custom_timestamp.ts @@ -24,10 +24,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_date_nanos_custom']); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/date_nanos_custom'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/date_nanos_custom' + ); await kibanaServer.uiSettings.replace({ defaultIndex: TEST_INDEX_PATTERN }); await kibanaServer.uiSettings.update({ 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, 'context:step': `${TEST_STEP_SIZE}`, + 'doc_table:legacy': true, }); }); @@ -45,6 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async function () { await security.testUser.restoreDefaults(); await esArchiver.unload('test/functional/fixtures/es_archiver/date_nanos_custom'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); }); } diff --git a/test/functional/apps/context/_filters.ts b/test/functional/apps/context/_filters.ts index d07cf802b7ebb..103d08f8f6a95 100644 --- a/test/functional/apps/context/_filters.ts +++ b/test/functional/apps/context/_filters.ts @@ -18,10 +18,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const docTable = getService('docTable'); const filterBar = getService('filterBar'); const retry = getService('retry'); + const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['common', 'context']); describe('context filters', function contextSize() { + before(async function () { + await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); + }); + + after(async function () { + await kibanaServer.uiSettings.replace({}); + }); + beforeEach(async function () { await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID, { columns: TEST_COLUMN_NAMES, diff --git a/test/functional/apps/context/_size.ts b/test/functional/apps/context/_size.ts index 52b16d2b9abe5..10f7b125c46b5 100644 --- a/test/functional/apps/context/_size.ts +++ b/test/functional/apps/context/_size.ts @@ -28,6 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.update({ 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, 'context:step': `${TEST_STEP_SIZE}`, + 'doc_table:legacy': true, }); await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); }); diff --git a/test/functional/apps/dashboard/dashboard_time_picker.ts b/test/functional/apps/dashboard/dashboard_time_picker.ts index 8a25f941be70f..6f876185fd8dd 100644 --- a/test/functional/apps/dashboard/dashboard_time_picker.ts +++ b/test/functional/apps/dashboard/dashboard_time_picker.ts @@ -68,8 +68,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await dashboardExpect.docTableFieldCount(0); } else { - const initialRows = await dataGrid.getDocTableRows(); - expect(initialRows.length).to.above(10); + const docCount = await dataGrid.getDocCount(); + expect(docCount).to.above(10); // Set to time range with no data await PageObjects.timePicker.setAbsoluteRange( diff --git a/test/functional/apps/dashboard/saved_search_embeddable.ts b/test/functional/apps/dashboard/saved_search_embeddable.ts index ce1033fa02075..b08dc43210d26 100644 --- a/test/functional/apps/dashboard/saved_search_embeddable.ts +++ b/test/functional/apps/dashboard/saved_search_embeddable.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const marks = $('mark') .toArray() .map((mark) => $(mark).text()); - expect(marks.length).to.above(10); + expect(marks.length).to.above(0); }); it('removing a filter removes highlights', async function () { diff --git a/test/functional/apps/discover/_date_nanos.ts b/test/functional/apps/discover/_date_nanos.ts index 81327d0744bfe..dcabb3dac0585 100644 --- a/test/functional/apps/discover/_date_nanos.ts +++ b/test/functional/apps/discover/_date_nanos.ts @@ -21,6 +21,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('date_nanos', function () { before(async function () { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/date_nanos'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/date_nanos'); await kibanaServer.uiSettings.replace({ defaultIndex: 'date-nanos' }); await security.testUser.setRoles(['kibana_admin', 'kibana_date_nanos']); await PageObjects.common.navigateToApp('discover'); @@ -30,6 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async function unloadMakelogs() { await security.testUser.restoreDefaults(); await esArchiver.unload('test/functional/fixtures/es_archiver/date_nanos'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); it('should show a timestamp with nanoseconds in the first result row', async function () { diff --git a/test/functional/apps/discover/_date_nanos_mixed.ts b/test/functional/apps/discover/_date_nanos_mixed.ts index 7d4104c3ac344..5cd72a67f36b1 100644 --- a/test/functional/apps/discover/_date_nanos_mixed.ts +++ b/test/functional/apps/discover/_date_nanos_mixed.ts @@ -21,6 +21,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('date_nanos_mixed', function () { before(async function () { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/date_nanos_mixed'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/date_nanos_mixed' + ); await kibanaServer.uiSettings.replace({ defaultIndex: 'timestamp-*' }); await security.testUser.setRoles(['kibana_admin', 'kibana_date_nanos_mixed']); await PageObjects.common.navigateToApp('discover'); @@ -30,6 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await security.testUser.restoreDefaults(); esArchiver.unload('test/functional/fixtures/es_archiver/date_nanos_mixed'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); it('shows a list of records of indices with date & date_nanos fields in the right order', async function () { diff --git a/test/functional/apps/discover/_discover_fields_api.ts b/test/functional/apps/discover/_discover_fields_api.ts index 700c865031cd6..fb3ee3b9858d3 100644 --- a/test/functional/apps/discover/_discover_fields_api.ts +++ b/test/functional/apps/discover/_discover_fields_api.ts @@ -11,7 +11,6 @@ import { FtrProviderContext } from './ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); - const docTable = getService('docTable'); const retry = getService('retry'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); @@ -19,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', 'discover:searchFieldsFromSource': false, + 'doc_table:legacy': true, }; describe('discover uses fields API test', function describeIndexTests() { before(async function () { @@ -27,13 +27,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); - log.debug('discover'); await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); }); after(async () => { - await kibanaServer.uiSettings.replace({ 'discover:searchFieldsFromSource': true }); + await kibanaServer.uiSettings.replace({}); }); it('should correctly display documents', async function () { @@ -61,8 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('displays _source viewer in doc viewer', async function () { - await docTable.clickRowToggle({ rowIndex: 0 }); - + await PageObjects.discover.clickDocTableRowToggle(0); await PageObjects.discover.isShowingDocViewer(); await PageObjects.discover.clickDocViewerTab(1); await PageObjects.discover.expectSourceViewerToExist(); diff --git a/test/functional/apps/discover/_errors.ts b/test/functional/apps/discover/_errors.ts index b252cbf5f0824..327f39ee0dec4 100644 --- a/test/functional/apps/discover/_errors.ts +++ b/test/functional/apps/discover/_errors.ts @@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); const toasts = getService('toasts'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'header', 'discover', 'timePicker']); @@ -18,13 +19,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('errors', function describeIndexTests() { before(async function () { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - await esArchiver.load('test/functional/fixtures/es_archiver/invalid_scripted_field'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/invalid_scripted_field' + ); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); after(async function () { - await esArchiver.load('test/functional/fixtures/es_archiver/empty_kibana'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); describe('invalid scripted field error', () => { diff --git a/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts index 2a1e60db541e8..c3982ba72824b 100644 --- a/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts +++ b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts @@ -19,6 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('index pattern with unmapped fields', () => { before(async () => { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/unmapped_fields'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/unmapped_fields'); await security.testUser.setRoles(['kibana_admin', 'test-index-unmapped-fields']); const fromTime = 'Jan 20, 2021 @ 00:00:00.000'; const toTime = 'Jan 25, 2021 @ 00:00:00.000'; @@ -35,6 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await esArchiver.unload('test/functional/fixtures/es_archiver/unmapped_fields'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); await kibanaServer.uiSettings.unset('defaultIndex'); await kibanaServer.uiSettings.unset('discover:searchFieldsFromSource'); await kibanaServer.uiSettings.unset('timepicker:timeDefaults'); diff --git a/test/functional/apps/discover/_indexpattern_without_timefield.ts b/test/functional/apps/discover/_indexpattern_without_timefield.ts index 6d6e1af7b2fbc..2d5892fa6e6ca 100644 --- a/test/functional/apps/discover/_indexpattern_without_timefield.ts +++ b/test/functional/apps/discover/_indexpattern_without_timefield.ts @@ -23,6 +23,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.loadIfNeeded( 'test/functional/fixtures/es_archiver/index_pattern_without_timefield' ); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/index_pattern_without_timefield' + ); await kibanaServer.uiSettings.replace({ defaultIndex: 'without-timefield', 'timepicker:timeDefaults': '{ "from": "2019-01-18T19:37:13.000Z", "to": "now"}', @@ -37,6 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.unload( 'test/functional/fixtures/es_archiver/index_pattern_without_timefield' ); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); it('should not display a timepicker', async () => { diff --git a/test/functional/apps/management/_scripted_fields_filter.js b/test/functional/apps/management/_scripted_fields_filter.js index 6a7d414becfe7..117b8747c5a0a 100644 --- a/test/functional/apps/management/_scripted_fields_filter.js +++ b/test/functional/apps/management/_scripted_fields_filter.js @@ -16,7 +16,8 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const PageObjects = getPageObjects(['settings']); - describe('filter scripted fields', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/126027 + describe.skip('filter scripted fields', function describeIndexTests() { before(async function () { // delete .kibana index and then wait for Kibana to re-create it await browser.setWindowSize(1200, 800); diff --git a/test/functional/fixtures/es_archiver/date_nanos/data.json b/test/functional/fixtures/es_archiver/date_nanos/data.json index 60feed23ee149..1638b6dd4190b 100644 --- a/test/functional/fixtures/es_archiver/date_nanos/data.json +++ b/test/functional/fixtures/es_archiver/date_nanos/data.json @@ -1,47 +1,3 @@ -{ - "type": "doc", - "value": { - "id": "index-pattern:date-nanos", - "index": ".kibana", - "source": { - "index-pattern": { - "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", - "timeFieldName": "@timestamp", - "title": "date-nanos", - "fieldFormatMap": "{\"@timestamp\":{\"id\":\"date_nanos\"}}" - }, - "type": "index-pattern" - } - } -} - -{ - "type": "doc", - "value": { - "id": "search:ab12e3c0-f231-11e6-9486-733b1ac9221a", - "index": ".kibana", - "source": { - "search": { - "columns": [ - "_source" - ], - "description": "A Saved Search Description", - "hits": 0, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\n \"index\": \"date-nanos\",\n \"highlightAll\": true,\n \"filter\": [],\n \"query\": {\n \"query_string\": {\n \"query\": \"*\",\n \"analyze_wildcard\": true\n }\n }\n}" - }, - "sort": [ - "@timestamp", - "desc" - ], - "title": "A Saved Search", - "version": 1 - }, - "type": "search" - } - } -} - { "type": "doc", "value": { diff --git a/test/functional/fixtures/es_archiver/date_nanos_custom/data.json b/test/functional/fixtures/es_archiver/date_nanos_custom/data.json index 73cba70a8b93d..10b0db8beda72 100644 --- a/test/functional/fixtures/es_archiver/date_nanos_custom/data.json +++ b/test/functional/fixtures/es_archiver/date_nanos_custom/data.json @@ -1,22 +1,3 @@ -{ - "type": "doc", - "value": { - "id": "index-pattern:date_nanos_custom_timestamp", - "index": ".kibana", - "source": { - "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"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\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"test\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"test.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"test\"}}},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date_nanos\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "timeFieldName": "timestamp", - "title": "date_nanos_custom_timestamp" - }, - "references": [ - ], - "type": "index-pattern", - "updated_at": "2020-01-09T21:43:20.283Z" - } - } -} - { "type": "doc", "value": { diff --git a/test/functional/fixtures/es_archiver/date_nanos_mixed/data.json b/test/functional/fixtures/es_archiver/date_nanos_mixed/data.json index abde15e2b08c4..d49a6fa2f2412 100644 --- a/test/functional/fixtures/es_archiver/date_nanos_mixed/data.json +++ b/test/functional/fixtures/es_archiver/date_nanos_mixed/data.json @@ -1,51 +1,3 @@ -{ - "type": "doc", - "value": { - "id": "index-pattern:timestamp-*", - "index": ".kibana", - "source": { - "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"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\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\",\"date_nanos\"],\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "timeFieldName": "timestamp", - "title": "timestamp-*", - "fieldFormatMap": "{\"timestamp\":{\"id\":\"date_nanos\"}}" - }, - "type": "index-pattern" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "search:82116b30-d407-11e9-8004-932185690e7b", - "index": ".kibana", - "source": { - "search": { - "columns": [ - "_source" - ], - "description": "", - "hits": 0, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"number\",\"negate\":false,\"params\":{\"query\":123},\"type\":\"phrase\",\"value\":\"123\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"match\":{\"number\":{\"query\":123,\"type\":\"phrase\"}}}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" - }, - "sort": [ - [ - "@timestamp", - "desc" - ] - ], - "title": "New Saved Search", - "version": 1 - }, - "type": "search" - }, - "type": "_doc" - } -} - { "type": "doc", "value": { diff --git a/test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json b/test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json index 0888079ec7c52..9998cb3a71732 100644 --- a/test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json +++ b/test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json @@ -1,18 +1,3 @@ -{ - "type": "doc", - "value": { - "id": "index-pattern:without-timefield", - "index": ".kibana", - "source": { - "index-pattern": { - "fields": "[]", - "title": "without-timefield" - }, - "type": "index-pattern" - } - } -} - { "type": "doc", "value": { diff --git a/test/functional/fixtures/es_archiver/invalid_scripted_field/data.json.gz b/test/functional/fixtures/es_archiver/invalid_scripted_field/data.json.gz deleted file mode 100644 index 380dd6049179a..0000000000000 Binary files a/test/functional/fixtures/es_archiver/invalid_scripted_field/data.json.gz and /dev/null differ diff --git a/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json b/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json deleted file mode 100644 index 0d41e0ce86c14..0000000000000 --- a/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "dynamic": "strict", - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/test/functional/fixtures/es_archiver/mgmt/data.json.gz b/test/functional/fixtures/es_archiver/mgmt/data.json.gz deleted file mode 100644 index c230ff8ff7e39..0000000000000 Binary files a/test/functional/fixtures/es_archiver/mgmt/data.json.gz and /dev/null differ diff --git a/test/functional/fixtures/es_archiver/mgmt/mappings.json b/test/functional/fixtures/es_archiver/mgmt/mappings.json deleted file mode 100644 index f4962f9c47668..0000000000000 --- a/test/functional/fixtures/es_archiver/mgmt/mappings.json +++ /dev/null @@ -1,242 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "dynamic": "strict", - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/test/functional/fixtures/es_archiver/unmapped_fields/data.json b/test/functional/fixtures/es_archiver/unmapped_fields/data.json index 10c33280696b1..d23a5b83eb2c6 100644 --- a/test/functional/fixtures/es_archiver/unmapped_fields/data.json +++ b/test/functional/fixtures/es_archiver/unmapped_fields/data.json @@ -1,48 +1,3 @@ -{ - "type": "doc", - "value": { - "id": "search:cd43f5c2-h761-13f6-9486-733b1ac9221a", - "index": ".kibana", - "source": { - "search": { - "columns": [ - "_source" - ], - "description": "Existing Saved Search", - "hits": 4, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\n \"index\": \"test-index-unmapped-fields\",\n \"highlightAll\": true,\n \"filter\": [],\n \"query\": {\n \"query_string\": {\n \"query\": \"*\",\n \"analyze_wildcard\": true\n }\n }\n}" - }, - "sort": [ - "@timestamp", - "desc" - ], - "title": "Existing Saved Search", - "version": 1 - }, - "type": "search" - } - } -} - -{ - "type": "doc", - "value": { - "id": "index-pattern:test-index-unmapped-fields", - "index": ".kibana", - "source": { - "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"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\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":4,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "timeFieldName": "timestamp", - "title": "test-index-unmapped-fields", - "fieldFormatMap": "{\"timestamp\":{\"id\":\"date\"}}" - }, - "type": "index-pattern" - }, - "type": "_doc" - } -} - { "type": "doc", "value": { diff --git a/test/functional/fixtures/es_archiver/visualize_embedding/data.json.gz b/test/functional/fixtures/es_archiver/visualize_embedding/data.json.gz deleted file mode 100644 index 95b32f0ee11e5..0000000000000 Binary files a/test/functional/fixtures/es_archiver/visualize_embedding/data.json.gz and /dev/null differ diff --git a/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json b/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json deleted file mode 100644 index 451369d85acd8..0000000000000 --- a/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/test/functional/fixtures/es_archiver/visualize_source-filters/data.json.gz b/test/functional/fixtures/es_archiver/visualize_source-filters/data.json.gz deleted file mode 100644 index c8d1c98790e59..0000000000000 Binary files a/test/functional/fixtures/es_archiver/visualize_source-filters/data.json.gz and /dev/null differ diff --git a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json deleted file mode 100644 index 451369d85acd8..0000000000000 --- a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/test/functional/fixtures/es_archiver/visualize_source_filters/data.json.gz b/test/functional/fixtures/es_archiver/visualize_source_filters/data.json.gz deleted file mode 100644 index 238ffe3b76241..0000000000000 Binary files a/test/functional/fixtures/es_archiver/visualize_source_filters/data.json.gz and /dev/null differ diff --git a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json deleted file mode 100644 index ec6a9ce7f13a1..0000000000000 --- a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "dateFormat:tz": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/date_nanos.json b/test/functional/fixtures/kbn_archiver/date_nanos.json new file mode 100644 index 0000000000000..249f364887620 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/date_nanos.json @@ -0,0 +1,51 @@ +{ + "attributes": { + "fieldFormatMap": "{\"@timestamp\":{\"id\":\"date_nanos\"}}", + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "timeFieldName": "@timestamp", + "title": "date-nanos" + }, + "coreMigrationVersion": "7.17.1", + "id": "date-nanos", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzM2LDJd" +} + +{ + "attributes": { + "columns": [ + "_source" + ], + "description": "A Saved Search Description", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"filter\":[],\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "title": "A Saved Search", + "version": 1 + }, + "coreMigrationVersion": "7.17.1", + "id": "ab12e3c0-f231-11e6-9486-733b1ac9221a", + "migrationVersion": { + "search": "7.9.3" + }, + "references": [ + { + "id": "date-nanos", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "version": "WzM3LDJd" +} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/date_nanos_custom.json b/test/functional/fixtures/kbn_archiver/date_nanos_custom.json new file mode 100644 index 0000000000000..b789407a0ba88 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/date_nanos_custom.json @@ -0,0 +1,68 @@ +{ + "attributes": { + "fieldFormatMap": "{\"@timestamp\":{\"id\":\"date_nanos\"}}", + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "timeFieldName": "@timestamp", + "title": "date-nanos" + }, + "coreMigrationVersion": "7.17.1", + "id": "date-nanos", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzQyLDJd" +} + +{ + "attributes": { + "columns": [ + "_source" + ], + "description": "A Saved Search Description", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"filter\":[],\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "title": "A Saved Search", + "version": 1 + }, + "coreMigrationVersion": "7.17.1", + "id": "ab12e3c0-f231-11e6-9486-733b1ac9221a", + "migrationVersion": { + "search": "7.9.3" + }, + "references": [ + { + "id": "date-nanos", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "version": "WzQzLDJd" +} + +{ + "attributes": { + "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"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\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"test\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"test.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"test\"}}},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date_nanos\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "timestamp", + "title": "date_nanos_custom_timestamp" + }, + "coreMigrationVersion": "7.17.1", + "id": "date_nanos_custom_timestamp", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2020-01-09T21:43:20.283Z", + "version": "WzQ1LDJd" +} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/date_nanos_mixed.json b/test/functional/fixtures/kbn_archiver/date_nanos_mixed.json new file mode 100644 index 0000000000000..6d528a382753f --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/date_nanos_mixed.json @@ -0,0 +1,16 @@ +{ + "attributes": { + "fieldFormatMap": "{\"timestamp\":{\"id\":\"date_nanos\"}}", + "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"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\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\",\"date_nanos\"],\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "timestamp", + "title": "timestamp-*" + }, + "coreMigrationVersion": "7.17.1", + "id": "timestamp-*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzU2LDJd" +} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/index_pattern_without_timefield.json b/test/functional/fixtures/kbn_archiver/index_pattern_without_timefield.json new file mode 100644 index 0000000000000..d5906dc8a2e99 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/index_pattern_without_timefield.json @@ -0,0 +1,30 @@ +{ + "attributes": { + "fields": "[]", + "timeFieldName": "@timestamp", + "title": "with-timefield" + }, + "coreMigrationVersion": "7.17.1", + "id": "with-timefield", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzEzLDJd" +} + +{ + "attributes": { + "fields": "[]", + "title": "without-timefield" + }, + "coreMigrationVersion": "7.17.1", + "id": "without-timefield", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzEyLDJd" +} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/invalid_scripted_field.json b/test/functional/fixtures/kbn_archiver/invalid_scripted_field.json new file mode 100644 index 0000000000000..23abe20855e3e --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/invalid_scripted_field.json @@ -0,0 +1,16 @@ +{ + "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\":\"invalid_scripted_field\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"invalid\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "timeFieldName": "@timestamp", + "title": "log*" + }, + "coreMigrationVersion": "7.17.1", + "id": "18ea0c30-2d77-11e8-93f2-6f8a391f33ef", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2018-03-22T02:17:11.427Z", + "version": "WzMsMl0=" +} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/unmapped_fields.json b/test/functional/fixtures/kbn_archiver/unmapped_fields.json new file mode 100644 index 0000000000000..aa1e464af5377 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/unmapped_fields.json @@ -0,0 +1,51 @@ +{ + "attributes": { + "fieldFormatMap": "{\"timestamp\":{\"id\":\"date\"}}", + "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"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\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":4,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "timestamp", + "title": "test-index-unmapped-fields" + }, + "coreMigrationVersion": "7.17.1", + "id": "test-index-unmapped-fields", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzEzLDJd" +} + +{ + "attributes": { + "columns": [ + "_source" + ], + "description": "Existing Saved Search", + "hits": 4, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"filter\":[],\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "title": "Existing Saved Search", + "version": 1 + }, + "coreMigrationVersion": "7.17.1", + "id": "cd43f5c2-h761-13f6-9486-733b1ac9221a", + "migrationVersion": { + "search": "7.9.3" + }, + "references": [ + { + "id": "test-index-unmapped-fields", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "version": "WzEyLDJd" +} \ No newline at end of file diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index effacb30bdc89..1583903be4991 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -240,7 +240,7 @@ export class DiscoverPageObject extends FtrService { } public async useLegacyTable() { - return (await this.kibanaServer.uiSettings.get('doc_table:legacy')) !== false; + return (await this.kibanaServer.uiSettings.get('doc_table:legacy')) === true; } public async getDocTableIndex(index: number) { @@ -276,6 +276,11 @@ export class DiscoverPageObject extends FtrService { return result[usedCellIdx]; } + public async clickDocTableRowToggle(rowIndex: number = 0) { + const docTable = await this.getDocTable(); + await docTable.clickRowToggle({ rowIndex }); + } + public async skipToEndOfDocTable() { // add the focus to the button to make it appear const skipButton = await this.testSubjects.find('discoverSkipTableButton'); diff --git a/test/functional/services/dashboard/expectations.ts b/test/functional/services/dashboard/expectations.ts index 75f60b1448eea..c56e7c1eae27e 100644 --- a/test/functional/services/dashboard/expectations.ts +++ b/test/functional/services/dashboard/expectations.ts @@ -226,11 +226,20 @@ export class DashboardExpectService extends FtrService { async savedSearchRowCount(expectedMinCount: number) { this.log.debug(`DashboardExpect.savedSearchRowCount(${expectedMinCount})`); await this.retry.try(async () => { - const savedSearchRows = await this.testSubjects.findAll( - 'docTableExpandToggleColumn', - this.findTimeout - ); - expect(savedSearchRows.length).to.be.above(expectedMinCount); + const gridExists = await this.find.existsByCssSelector('[data-document-number]'); + if (gridExists) { + const grid = await this.find.byCssSelector('[data-document-number]'); + // in this case it's the document explorer + const docNr = Number(await grid.getAttribute('data-document-number')); + expect(docNr).to.be.above(expectedMinCount); + } else { + // in this case it's the classic table + const savedSearchRows = await this.testSubjects.findAll( + 'docTableExpandToggleColumn', + this.findTimeout + ); + expect(savedSearchRows.length).to.be.above(expectedMinCount); + } }); } diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index 753d9b7b0b85e..b8dec2a2092fb 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -195,8 +195,12 @@ export class InspectorService extends FtrService { */ public async openInspectorView(viewId: string): Promise { this.log.debug(`Open Inspector view ${viewId}`); - await this.testSubjects.click('inspectorViewChooser'); - await this.testSubjects.click(viewId); + await this.retry.try(async () => { + await this.testSubjects.click('inspectorViewChooser'); + // check whether popover menu opens, if not, fail and retry opening + await this.testSubjects.existOrFail(viewId, { timeout: 2000 }); + await this.testSubjects.click(viewId); + }); } /** diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx index 91fb8552beed8..4635d4694ed1e 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx @@ -65,9 +65,9 @@ export class DashboardToDiscoverDrilldown }; private readonly getPath = async (config: Config, context: ActionContext): Promise => { - const { urlGenerator } = this.params.start().plugins.discover; + const { locator } = this.params.start().plugins.discover; - if (!urlGenerator) throw new Error('Discover URL generator not available.'); + if (!locator) throw new Error('Discover locator not available.'); let indexPatternId = !!config.customIndexPattern && !!config.indexPatternId ? config.indexPatternId : ''; @@ -79,7 +79,7 @@ export class DashboardToDiscoverDrilldown } } - return await urlGenerator.createUrl({ + return await locator.getUrl({ indexPatternId, }); }; diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index bc917fbf43bc4..8fdfe77776b4e 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -19,6 +19,7 @@ Table of Contents - [Methods](#methods) - [Executor](#executor) - [Action variables](#action-variables) + - [Recovered Alerts](#recovered-alerts) - [Licensing](#licensing) - [Documentation](#documentation) - [Tests](#tests) @@ -100,6 +101,7 @@ The following table describes the properties of the `options` object. |isExportable|Whether the rule type is exportable from the Saved Objects Management UI.|boolean| |defaultScheduleInterval|The default interval that will show up in the UI when creating a rule of this rule type.|boolean| |minimumScheduleInterval|The minimum interval that will be allowed for all rules of this rule type.|boolean| +|doesSetRecoveryContext|Whether the rule type will set context variables for recovered alerts. Defaults to `false`. If this is set to true, context variables are made available for the recovery action group and executors will be provided with the ability to set recovery context.|boolean| ### Executor @@ -170,6 +172,35 @@ This function should take the rule type params as input and extract out any save This function should take the rule type params (with saved object references) and the saved object references array as input and inject the saved object ID in place of any saved object references in the rule type params. Note that any error thrown within this function will be propagated. + +## Recovered Alerts +The Alerting framework automatically determines which alerts are recovered by comparing the active alerts from the previous rule execution to the active alerts in the current rule execution. Alerts that were active previously but not active currently are considered `recovered`. If any actions were specified on the Recovery action group for the rule, they will be scheduled at the end of the execution cycle. + +Because this determination occurs after rule type executors have completed execution, the framework provides a mechanism for rule type executors to set contextual information for recovered alerts that can be templated and used inside recovery actions. In order to use this mechanism, the rule type must set the `doesSetRecoveryContext` flag to `true` during rule type registration. + +Then, the following code would be added within a rule type executor. As you can see, when the rule type is finished creating and scheduling actions for active alerts, it should call `done()` on the alertFactory. This will give the executor access to the list recovered alerts for this execution cycle, for which it can iterate and set context. + +``` +// Create and schedule actions for active alerts +for (const i = 0; i < 5; ++i) { + alertFactory + .create('server_1') + .scheduleActions('default', { + server: 'server_1', + }); +} + +// Call done() to gain access to recovery utils +// If `doesSetRecoveryContext` is set to `false`, getRecoveredAlerts() returns an empty list +const { getRecoveredAlerts } = alertsFactory.done(); + +for (const alert of getRecoveredAlerts()) { + const alertId = alert.getId(); + alert.setContext({ + server: + }) +} +``` ## Licensing Currently most rule types are free features. But some rule types are subscription features, such as the tracking containment rule. @@ -743,6 +774,7 @@ This factory returns an instance of `Alert`. The `Alert` class has the following |scheduleActions(actionGroup, context)|Call this to schedule the execution of actions. The actionGroup is a string `id` that relates to the group of alert `actions` to execute and the context will be used for templating purposes. `scheduleActions` or `scheduleActionsWithSubGroup` should only be called once per alert.| |scheduleActionsWithSubGroup(actionGroup, subgroup, context)|Call this to schedule the execution of actions within a subgroup. The actionGroup is a string `id` that relates to the group of alert `actions` to execute, the `subgroup` is a dynamic string that denotes a subgroup within the actionGroup and the context will be used for templating purposes. `scheduleActions` or `scheduleActionsWithSubGroup` should only be called once per alert.| |replaceState(state)|Used to replace the current state of the alert. This doesn't work like React, the entire state must be provided. Use this feature as you see fit. The state that is set will persist between rule executions whenever you re-create an alert with the same id. The alert state will be erased when `scheduleActions` or `scheduleActionsWithSubGroup` aren't called during an execution.| +|setContext(context)|Call this to set the context for this alert that is used for templating purposes. ### When should I use `scheduleActions` and `scheduleActionsWithSubGroup`? The `scheduleActions` or `scheduleActionsWithSubGroup` methods are both used to achieve the same thing: schedule actions to be run under a specific action group. @@ -758,13 +790,16 @@ Action Subgroups are dynamic, and can be defined on the fly. This approach enables users to specify actions under specific action groups, but they can't specify actions that are specific to subgroups. As subgroups fall under action groups, we will schedule the actions specified for the action group, but the subgroup allows the RuleType implementer to reuse the same action group for multiple different active subgroups. +### When should I use `setContext`? +`setContext` is intended to be used for setting context for recovered alerts. While rule type executors make the determination as to which alerts are active for an execution, the Alerting Framework automatically determines which alerts are recovered for an execution. `setContext` empowers rule type executors to provide additional contextual information for these recovered alerts that will be templated into actions. + ## Templating Actions There needs to be a way to map rule context into action parameters. For this, we started off by adding template support. Any string within the `params` of a rule saved object's `actions` will be processed as a template and can inject context or state values. When an alert executes, the first argument is the `group` of actions to execute and the second is the context the rule exposes to templates. We iterate through each action parameter attributes recursively and render templates if they are a string. Templates have access to the following "variables": -- `context` - provided by context argument of `.scheduleActions(...)` and `.scheduleActionsWithSubGroup(...)` on an alert. +- `context` - provided by context argument of `.scheduleActions(...)`, `.scheduleActionsWithSubGroup(...)` and `setContext(...)` on an alert. - `state` - the alert's `state` provided by the most recent `replaceState` call on an alert. - `alertId` - the id of the rule - `alertInstanceId` - the alert id diff --git a/x-pack/plugins/alerting/common/rule_type.ts b/x-pack/plugins/alerting/common/rule_type.ts index 6f5f00e8f4073..eb24e29f552b9 100644 --- a/x-pack/plugins/alerting/common/rule_type.ts +++ b/x-pack/plugins/alerting/common/rule_type.ts @@ -37,6 +37,7 @@ export interface RuleType< ruleTaskTimeout?: string; defaultScheduleInterval?: string; minimumScheduleInterval?: string; + doesSetRecoveryContext?: boolean; enabledInLicense: boolean; authorizedConsumers: Record; } diff --git a/x-pack/plugins/alerting/server/alert/alert.test.ts b/x-pack/plugins/alerting/server/alert/alert.test.ts index 83b82de904703..eae1b18164b0f 100644 --- a/x-pack/plugins/alerting/server/alert/alert.test.ts +++ b/x-pack/plugins/alerting/server/alert/alert.test.ts @@ -17,14 +17,21 @@ beforeAll(() => { beforeEach(() => clock.reset()); afterAll(() => clock.restore()); +describe('getId()', () => { + test('correctly sets id in constructor', () => { + const alert = new Alert('1'); + expect(alert.getId()).toEqual('1'); + }); +}); + describe('hasScheduledActions()', () => { test('defaults to false', () => { - const alert = new Alert(); + const alert = new Alert('1'); expect(alert.hasScheduledActions()).toEqual(false); }); test('returns true when scheduleActions is called', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActions('default'); expect(alert.hasScheduledActions()).toEqual(true); }); @@ -32,7 +39,7 @@ describe('hasScheduledActions()', () => { describe('isThrottled', () => { test(`should throttle when group didn't change and throttle period is still active`, () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -46,7 +53,7 @@ describe('isThrottled', () => { }); test(`shouldn't throttle when group didn't change and throttle period expired`, () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -60,7 +67,7 @@ describe('isThrottled', () => { }); test(`shouldn't throttle when group changes`, () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -76,12 +83,12 @@ describe('isThrottled', () => { describe('scheduledActionGroupOrSubgroupHasChanged()', () => { test('should be false if no last scheduled and nothing scheduled', () => { - const alert = new Alert(); + const alert = new Alert('1'); expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); }); test('should be false if group does not change', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -94,7 +101,7 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }); test('should be false if group and subgroup does not change', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -108,7 +115,7 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }); test('should be false if group does not change and subgroup goes from undefined to defined', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -121,7 +128,7 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }); test('should be false if group does not change and subgroup goes from defined to undefined', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -135,13 +142,13 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }); test('should be true if no last scheduled and has scheduled action', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActions('default'); expect(alert.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); }); test('should be true if group does change', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -154,7 +161,7 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }); test('should be true if group does change and subgroup does change', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -168,7 +175,7 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { }); test('should be true if group does not change and subgroup does change', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: { lastScheduledActions: { date: new Date(), @@ -184,14 +191,14 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => { describe('getScheduledActionOptions()', () => { test('defaults to undefined', () => { - const alert = new Alert(); + const alert = new Alert('1'); expect(alert.getScheduledActionOptions()).toBeUndefined(); }); }); describe('unscheduleActions()', () => { test('makes hasScheduledActions() return false', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActions('default'); expect(alert.hasScheduledActions()).toEqual(true); alert.unscheduleActions(); @@ -199,7 +206,7 @@ describe('unscheduleActions()', () => { }); test('makes getScheduledActionOptions() return undefined', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActions('default'); expect(alert.getScheduledActionOptions()).toEqual({ actionGroup: 'default', @@ -214,7 +221,7 @@ describe('unscheduleActions()', () => { describe('getState()', () => { test('returns state passed to constructor', () => { const state = { foo: true }; - const alert = new Alert({ + const alert = new Alert('1', { state, }); expect(alert.getState()).toEqual(state); @@ -223,7 +230,7 @@ describe('getState()', () => { describe('scheduleActions()', () => { test('makes hasScheduledActions() return true', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -237,7 +244,7 @@ describe('scheduleActions()', () => { }); test('makes isThrottled() return true when throttled', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -251,7 +258,7 @@ describe('scheduleActions()', () => { }); test('make isThrottled() return false when throttled expired', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -266,7 +273,7 @@ describe('scheduleActions()', () => { }); test('makes getScheduledActionOptions() return given options', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: {}, }); @@ -279,7 +286,7 @@ describe('scheduleActions()', () => { }); test('cannot schdule for execution twice', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActions('default', { field: true }); expect(() => alert.scheduleActions('default', { field: false }) @@ -291,7 +298,7 @@ describe('scheduleActions()', () => { describe('scheduleActionsWithSubGroup()', () => { test('makes hasScheduledActions() return true', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -307,7 +314,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('makes isThrottled() return true when throttled and subgroup is the same', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -324,7 +331,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('makes isThrottled() return true when throttled and last schedule had no subgroup', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -340,7 +347,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('makes isThrottled() return false when throttled and subgroup is the different', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -357,7 +364,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('make isThrottled() return false when throttled expired', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: { lastScheduledActions: { @@ -374,7 +381,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('makes getScheduledActionOptions() return given options', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, meta: {}, }); @@ -390,7 +397,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('cannot schdule for execution twice', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); expect(() => alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: false }) @@ -400,7 +407,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('cannot schdule for execution twice with different subgroups', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: true }); expect(() => alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: false }) @@ -410,7 +417,7 @@ describe('scheduleActionsWithSubGroup()', () => { }); test('cannot schdule for execution twice whether there are subgroups', () => { - const alert = new Alert(); + const alert = new Alert('1'); alert.scheduleActions('default', { field: true }); expect(() => alert.scheduleActionsWithSubGroup('default', 'subgroup', { field: false }) @@ -422,7 +429,7 @@ describe('scheduleActionsWithSubGroup()', () => { describe('replaceState()', () => { test('replaces previous state', () => { - const alert = new Alert({ + const alert = new Alert('1', { state: { foo: true }, }); alert.replaceState({ bar: true }); @@ -434,7 +441,7 @@ describe('replaceState()', () => { describe('updateLastScheduledActions()', () => { test('replaces previous lastScheduledActions', () => { - const alert = new Alert({ + const alert = new Alert('1', { meta: {}, }); alert.updateLastScheduledActions('default'); @@ -450,9 +457,82 @@ describe('updateLastScheduledActions()', () => { }); }); +describe('getContext()', () => { + test('returns empty object when context has not been set', () => { + const alert = new Alert('1', { + state: { foo: true }, + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + expect(alert.getContext()).toStrictEqual({}); + }); + + test('returns context when context has not been set', () => { + const alert = new Alert('1', { + state: { foo: true }, + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + alert.setContext({ field: true }); + expect(alert.getContext()).toStrictEqual({ field: true }); + }); +}); + +describe('hasContext()', () => { + test('returns true when context has been set via scheduleActions()', () => { + const alert = new Alert('1', { + state: { foo: true }, + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + alert.scheduleActions('default', { field: true }); + expect(alert.hasContext()).toEqual(true); + }); + + test('returns true when context has been set via setContext()', () => { + const alert = new Alert('1', { + state: { foo: true }, + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + alert.setContext({ field: true }); + expect(alert.hasContext()).toEqual(true); + }); + + test('returns false when context has not been set', () => { + const alert = new Alert('1', { + state: { foo: true }, + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + expect(alert.hasContext()).toEqual(false); + }); +}); + describe('toJSON', () => { test('only serializes state and meta', () => { const alertInstance = new Alert( + '1', { state: { foo: true }, meta: { @@ -481,6 +561,7 @@ describe('toRaw', () => { }, }; const alertInstance = new Alert( + '1', raw ); expect(alertInstance.toRaw()).toEqual(raw); diff --git a/x-pack/plugins/alerting/server/alert/alert.ts b/x-pack/plugins/alerting/server/alert/alert.ts index d34aa68ac1a11..bf29cacf556c1 100644 --- a/x-pack/plugins/alerting/server/alert/alert.ts +++ b/x-pack/plugins/alerting/server/alert/alert.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { AlertInstanceMeta, AlertInstanceState, @@ -33,7 +34,13 @@ export type PublicAlert< ActionGroupIds extends string = DefaultActionGroupId > = Pick< Alert, - 'getState' | 'replaceState' | 'scheduleActions' | 'scheduleActionsWithSubGroup' + | 'getState' + | 'replaceState' + | 'scheduleActions' + | 'scheduleActionsWithSubGroup' + | 'setContext' + | 'getContext' + | 'hasContext' >; export class Alert< @@ -44,12 +51,20 @@ export class Alert< private scheduledExecutionOptions?: ScheduledExecutionOptions; private meta: AlertInstanceMeta; private state: State; + private context: Context; + private readonly id: string; - constructor({ state, meta = {} }: RawAlertInstance = {}) { + constructor(id: string, { state, meta = {} }: RawAlertInstance = {}) { + this.id = id; this.state = (state || {}) as State; + this.context = {} as Context; this.meta = meta; } + getId() { + return this.id; + } + hasScheduledActions() { return this.scheduledExecutionOptions !== undefined; } @@ -134,8 +149,17 @@ export class Alert< return this.state; } + getContext() { + return this.context; + } + + hasContext() { + return !isEmpty(this.context); + } + scheduleActions(actionGroup: ActionGroupIds, context: Context = {} as Context) { this.ensureHasNoScheduledActions(); + this.setContext(context); this.scheduledExecutionOptions = { actionGroup, context, @@ -150,6 +174,7 @@ export class Alert< context: Context = {} as Context ) { this.ensureHasNoScheduledActions(); + this.setContext(context); this.scheduledExecutionOptions = { actionGroup, subgroup, @@ -159,6 +184,11 @@ export class Alert< return this; } + setContext(context: Context) { + this.context = context; + return this; + } + private ensureHasNoScheduledActions() { if (this.hasScheduledActions()) { throw new Error('Alert instance execution has already been scheduled, cannot schedule twice'); diff --git a/x-pack/plugins/alerting/server/alert/create_alert_factory.test.ts b/x-pack/plugins/alerting/server/alert/create_alert_factory.test.ts index ecb1a10bbac42..254da05c0dd53 100644 --- a/x-pack/plugins/alerting/server/alert/create_alert_factory.test.ts +++ b/x-pack/plugins/alerting/server/alert/create_alert_factory.test.ts @@ -6,64 +6,206 @@ */ import sinon from 'sinon'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { Alert } from './alert'; import { createAlertFactory } from './create_alert_factory'; +import { getRecoveredAlerts } from '../lib'; -let clock: sinon.SinonFakeTimers; +jest.mock('../lib', () => ({ + getRecoveredAlerts: jest.fn(), +})); -beforeAll(() => { - clock = sinon.useFakeTimers(); -}); -beforeEach(() => clock.reset()); -afterAll(() => clock.restore()); - -test('creates new alerts for ones not passed in', () => { - const alertFactory = createAlertFactory({ alerts: {} }); - const result = alertFactory.create('1'); - expect(result).toMatchInlineSnapshot(` - Object { - "meta": Object {}, - "state": Object {}, - } - `); -}); +let clock: sinon.SinonFakeTimers; +const logger = loggingSystemMock.create().get(); -test('reuses existing alerts', () => { - const alert = new Alert({ - state: { foo: true }, - meta: { lastScheduledActions: { group: 'default', date: new Date() } }, +describe('createAlertFactory()', () => { + beforeAll(() => { + clock = sinon.useFakeTimers(); }); - const alertFactory = createAlertFactory({ - alerts: { - '1': alert, - }, + beforeEach(() => clock.reset()); + afterAll(() => clock.restore()); + + test('creates new alerts for ones not passed in', () => { + const alertFactory = createAlertFactory({ + alerts: {}, + logger, + }); + const result = alertFactory.create('1'); + expect(result).toMatchInlineSnapshot(` + Object { + "meta": Object {}, + "state": Object {}, + } + `); + expect(result.getId()).toEqual('1'); }); - const result = alertFactory.create('1'); - expect(result).toMatchInlineSnapshot(` - Object { - "meta": Object { - "lastScheduledActions": Object { - "date": "1970-01-01T00:00:00.000Z", - "group": "default", + + test('reuses existing alerts', () => { + const alert = new Alert('1', { + state: { foo: true }, + meta: { lastScheduledActions: { group: 'default', date: new Date() } }, + }); + const alertFactory = createAlertFactory({ + alerts: { + '1': alert, + }, + logger, + }); + const result = alertFactory.create('1'); + expect(result).toMatchInlineSnapshot(` + Object { + "meta": Object { + "lastScheduledActions": Object { + "date": "1970-01-01T00:00:00.000Z", + "group": "default", + }, + }, + "state": Object { + "foo": true, }, + } + `); + }); + + test('mutates given alerts', () => { + const alerts = {}; + const alertFactory = createAlertFactory({ + alerts, + logger, + }); + alertFactory.create('1'); + expect(alerts).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object {}, + "state": Object {}, + }, + } + `); + }); + + test('throws error when creating alerts after done() is called', () => { + const alertFactory = createAlertFactory({ + alerts: {}, + logger, + }); + const result = alertFactory.create('1'); + expect(result).toEqual({ + meta: {}, + state: {}, + context: {}, + scheduledExecutionOptions: undefined, + id: '1', + }); + + alertFactory.done(); + + expect(() => { + alertFactory.create('2'); + }).toThrowErrorMatchingInlineSnapshot( + `"Can't create new alerts after calling done() in AlertsFactory."` + ); + }); + + test('returns recovered alerts when setsRecoveryContext is true', () => { + (getRecoveredAlerts as jest.Mock).mockReturnValueOnce({ + z: { + id: 'z', + state: { foo: true }, + meta: { lastScheduledActions: { group: 'default', date: new Date() } }, }, - "state": Object { - "foo": true, + y: { + id: 'y', + state: { foo: true }, + meta: { lastScheduledActions: { group: 'default', date: new Date() } }, }, - } - `); -}); + }); + const alertFactory = createAlertFactory({ + alerts: {}, + logger, + canSetRecoveryContext: true, + }); + const result = alertFactory.create('1'); + expect(result).toEqual({ + meta: {}, + state: {}, + context: {}, + scheduledExecutionOptions: undefined, + id: '1', + }); -test('mutates given alerts', () => { - const alerts = {}; - const alertFactory = createAlertFactory({ alerts }); - alertFactory.create('1'); - expect(alerts).toMatchInlineSnapshot(` - Object { - "1": Object { - "meta": Object {}, - "state": Object {}, - }, - } - `); + const { getRecoveredAlerts: getRecoveredAlertsFn } = alertFactory.done(); + expect(getRecoveredAlertsFn).toBeDefined(); + const recoveredAlerts = getRecoveredAlertsFn!(); + expect(Array.isArray(recoveredAlerts)).toBe(true); + expect(recoveredAlerts.length).toEqual(2); + }); + + test('returns empty array if no recovered alerts', () => { + (getRecoveredAlerts as jest.Mock).mockReturnValueOnce({}); + const alertFactory = createAlertFactory({ + alerts: {}, + logger, + canSetRecoveryContext: true, + }); + const result = alertFactory.create('1'); + expect(result).toEqual({ + meta: {}, + state: {}, + context: {}, + scheduledExecutionOptions: undefined, + id: '1', + }); + + const { getRecoveredAlerts: getRecoveredAlertsFn } = alertFactory.done(); + const recoveredAlerts = getRecoveredAlertsFn!(); + expect(Array.isArray(recoveredAlerts)).toBe(true); + expect(recoveredAlerts.length).toEqual(0); + }); + + test('returns empty array if getRecoveredAlerts returns null', () => { + (getRecoveredAlerts as jest.Mock).mockReturnValueOnce(null); + const alertFactory = createAlertFactory({ + alerts: {}, + logger, + canSetRecoveryContext: true, + }); + const result = alertFactory.create('1'); + expect(result).toEqual({ + meta: {}, + state: {}, + context: {}, + scheduledExecutionOptions: undefined, + id: '1', + }); + + const { getRecoveredAlerts: getRecoveredAlertsFn } = alertFactory.done(); + const recoveredAlerts = getRecoveredAlertsFn!(); + expect(Array.isArray(recoveredAlerts)).toBe(true); + expect(recoveredAlerts.length).toEqual(0); + }); + + test('returns empty array if recovered alerts exist but setsRecoveryContext is false', () => { + const alertFactory = createAlertFactory({ + alerts: {}, + logger, + canSetRecoveryContext: false, + }); + const result = alertFactory.create('1'); + expect(result).toEqual({ + meta: {}, + state: {}, + context: {}, + scheduledExecutionOptions: undefined, + id: '1', + }); + + const { getRecoveredAlerts: getRecoveredAlertsFn } = alertFactory.done(); + const recoveredAlerts = getRecoveredAlertsFn!(); + expect(Array.isArray(recoveredAlerts)).toBe(true); + expect(recoveredAlerts.length).toEqual(0); + expect(logger.debug).toHaveBeenCalledWith( + `Set doesSetRecoveryContext to true on rule type to get access to recovered alerts.` + ); + }); }); diff --git a/x-pack/plugins/alerting/server/alert/create_alert_factory.ts b/x-pack/plugins/alerting/server/alert/create_alert_factory.ts index 07f4dbc7b20ea..ad83b0a416c72 100644 --- a/x-pack/plugins/alerting/server/alert/create_alert_factory.ts +++ b/x-pack/plugins/alerting/server/alert/create_alert_factory.ts @@ -5,8 +5,18 @@ * 2.0. */ +import { Logger } from 'src/core/server'; import { AlertInstanceContext, AlertInstanceState } from '../types'; import { Alert } from './alert'; +import { getRecoveredAlerts } from '../lib'; + +export interface AlertFactoryDoneUtils< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext, + ActionGroupIds extends string +> { + getRecoveredAlerts: () => Array>; +} export interface CreateAlertFactoryOpts< InstanceState extends AlertInstanceState, @@ -14,20 +24,50 @@ export interface CreateAlertFactoryOpts< ActionGroupIds extends string > { alerts: Record>; + logger: Logger; + canSetRecoveryContext?: boolean; } export function createAlertFactory< InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string ->({ alerts }: CreateAlertFactoryOpts) { +>({ + alerts, + logger, + canSetRecoveryContext = false, +}: CreateAlertFactoryOpts) { + // Keep track of which alerts we started with so we can determine which have recovered + const initialAlertIds = new Set(Object.keys(alerts)); + let isDone = false; return { create: (id: string): Alert => { + if (isDone) { + throw new Error(`Can't create new alerts after calling done() in AlertsFactory.`); + } if (!alerts[id]) { - alerts[id] = new Alert(); + alerts[id] = new Alert(id); } return alerts[id]; }, + done: (): AlertFactoryDoneUtils => { + isDone = true; + return { + getRecoveredAlerts: () => { + if (!canSetRecoveryContext) { + logger.debug( + `Set doesSetRecoveryContext to true on rule type to get access to recovered alerts.` + ); + return []; + } + + const recoveredAlerts = getRecoveredAlerts(alerts, initialAlertIds); + return Object.keys(recoveredAlerts ?? []).map( + (alertId: string) => recoveredAlerts[alertId] + ); + }, + }; + }, }; } diff --git a/x-pack/plugins/alerting/server/alert/index.ts b/x-pack/plugins/alerting/server/alert/index.ts index 5e1a9ee626b57..2b5dc4791037e 100644 --- a/x-pack/plugins/alerting/server/alert/index.ts +++ b/x-pack/plugins/alerting/server/alert/index.ts @@ -8,3 +8,4 @@ export type { PublicAlert } from './alert'; export { Alert } from './alert'; export { createAlertFactory } from './create_alert_factory'; +export type { AlertFactoryDoneUtils } from './create_alert_factory'; diff --git a/x-pack/plugins/alerting/server/lib/get_recovered_alerts.test.ts b/x-pack/plugins/alerting/server/lib/get_recovered_alerts.test.ts new file mode 100644 index 0000000000000..b984b04fc65d4 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/get_recovered_alerts.test.ts @@ -0,0 +1,40 @@ +/* + * 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 { getRecoveredAlerts } from './get_recovered_alerts'; +import { Alert } from '../alert'; +import { AlertInstanceState, AlertInstanceContext, DefaultActionGroupId } from '../types'; + +describe('getRecoveredAlerts', () => { + test('considers alert recovered if it has no scheduled actions', () => { + const alert1 = new Alert('1'); + alert1.scheduleActions('default', { foo: '1' }); + + const alert2 = new Alert('2'); + alert2.setContext({ foo: '2' }); + const alerts = { + '1': alert1, + '2': alert2, + }; + + expect(getRecoveredAlerts(alerts, new Set(['1', '2']))).toEqual({ + '2': alert2, + }); + }); + + test('does not consider alert recovered if it has no actions but was not in original alerts list', () => { + const alert1 = new Alert('1'); + alert1.scheduleActions('default', { foo: '1' }); + const alert2 = new Alert('2'); + const alerts = { + '1': alert1, + '2': alert2, + }; + + expect(getRecoveredAlerts(alerts, new Set(['1']))).toEqual({}); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/get_recovered_alerts.ts b/x-pack/plugins/alerting/server/lib/get_recovered_alerts.ts new file mode 100644 index 0000000000000..f389f56a813d0 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/get_recovered_alerts.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 { Dictionary, pickBy } from 'lodash'; +import { Alert } from '../alert'; +import { AlertInstanceState, AlertInstanceContext } from '../types'; + +export function getRecoveredAlerts< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext, + RecoveryActionGroupId extends string +>( + alerts: Record>, + originalAlertIds: Set +): Dictionary> { + return pickBy( + alerts, + (alert: Alert, id) => + !alert.hasScheduledActions() && originalAlertIds.has(id) + ); +} diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerting/server/lib/index.ts index 29526f17268f2..a5fa1b29c3044 100644 --- a/x-pack/plugins/alerting/server/lib/index.ts +++ b/x-pack/plugins/alerting/server/lib/index.ts @@ -24,3 +24,4 @@ export { ruleExecutionStatusToRaw, ruleExecutionStatusFromRaw, } from './rule_execution_status'; +export { getRecoveredAlerts } from './get_recovered_alerts'; diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index afbc3ef9cec43..f7872ba797856 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -7,7 +7,7 @@ import { rulesClientMock } from './rules_client.mock'; import { PluginSetupContract, PluginStartContract } from './plugin'; -import { Alert } from './alert'; +import { Alert, AlertFactoryDoneUtils } from './alert'; import { elasticsearchServiceMock, savedObjectsClientMock, @@ -64,6 +64,17 @@ const createAlertFactoryMock = { return mock as unknown as AlertInstanceMock; }, + done: < + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext, + ActionGroupIds extends string = string + >() => { + const mock: jest.Mocked> = + { + getRecoveredAlerts: jest.fn().mockReturnValue([]), + }; + return mock; + }, }; const createAbortableSearchClientMock = () => { @@ -86,9 +97,11 @@ const createAlertServicesMock = < InstanceContext extends AlertInstanceContext = AlertInstanceContext >() => { const alertFactoryMockCreate = createAlertFactoryMock.create(); + const alertFactoryMockDone = createAlertFactoryMock.done(); return { alertFactory: { create: jest.fn().mockReturnValue(alertFactoryMockCreate), + done: jest.fn().mockReturnValue(alertFactoryMockDone), }, savedObjectsClient: savedObjectsClientMock.create(), scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(), diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 70aad0d6921e1..ac3253346138a 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -293,6 +293,7 @@ export class AlertingPlugin { ruleType.ruleTaskTimeout = ruleType.ruleTaskTimeout ?? config.defaultRuleTaskTimeout; ruleType.cancelAlertsOnRuleTimeout = ruleType.cancelAlertsOnRuleTimeout ?? config.cancelAlertsOnRuleTimeout; + ruleType.doesSetRecoveryContext = ruleType.doesSetRecoveryContext ?? false; ruleTypeRegistry.register(ruleType); }); }, diff --git a/x-pack/plugins/alerting/server/routes/rule_types.test.ts b/x-pack/plugins/alerting/server/routes/rule_types.test.ts index 7deb2704fb7ec..752f729fb8e38 100644 --- a/x-pack/plugins/alerting/server/routes/rule_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule_types.test.ts @@ -60,6 +60,7 @@ describe('ruleTypesRoute', () => { enabledInLicense: true, minimumScheduleInterval: '1m', defaultScheduleInterval: '10m', + doesSetRecoveryContext: false, } as RegistryAlertTypeWithAuth, ]; const expectedResult: Array> = [ @@ -74,6 +75,7 @@ describe('ruleTypesRoute', () => { ], default_action_group_id: 'default', default_schedule_interval: '10m', + does_set_recovery_context: false, minimum_license_required: 'basic', minimum_schedule_interval: '1m', is_exportable: true, @@ -109,6 +111,7 @@ describe('ruleTypesRoute', () => { "authorized_consumers": Object {}, "default_action_group_id": "default", "default_schedule_interval": "10m", + "does_set_recovery_context": false, "enabled_in_license": true, "id": "1", "is_exportable": true, diff --git a/x-pack/plugins/alerting/server/routes/rule_types.ts b/x-pack/plugins/alerting/server/routes/rule_types.ts index d1f24538d76d8..7b2a0c63be198 100644 --- a/x-pack/plugins/alerting/server/routes/rule_types.ts +++ b/x-pack/plugins/alerting/server/routes/rule_types.ts @@ -25,6 +25,7 @@ const rewriteBodyRes: RewriteResponseCase = (result authorizedConsumers, minimumScheduleInterval, defaultScheduleInterval, + doesSetRecoveryContext, ...rest }) => ({ ...rest, @@ -39,6 +40,7 @@ const rewriteBodyRes: RewriteResponseCase = (result authorized_consumers: authorizedConsumers, minimum_schedule_interval: minimumScheduleInterval, default_schedule_interval: defaultScheduleInterval, + does_set_recovery_context: doesSetRecoveryContext, }) ); }; diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index e23c7f25a4f76..8ba2847486bca 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -493,6 +493,7 @@ describe('list()', () => { }, ], defaultActionGroupId: 'testActionGroup', + doesSetRecoveryContext: false, isExportable: true, ruleTaskTimeout: '20m', minimumLicenseRequired: 'basic', @@ -520,6 +521,7 @@ describe('list()', () => { }, "defaultActionGroupId": "testActionGroup", "defaultScheduleInterval": undefined, + "doesSetRecoveryContext": false, "enabledInLicense": false, "id": "test", "isExportable": true, diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index 9b4f94f3510be..6673fb630ef59 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -51,6 +51,7 @@ export interface RegistryRuleType | 'ruleTaskTimeout' | 'minimumScheduleInterval' | 'defaultScheduleInterval' + | 'doesSetRecoveryContext' > { id: string; enabledInLicense: boolean; @@ -331,6 +332,7 @@ export class RuleTypeRegistry { ruleTaskTimeout, minimumScheduleInterval, defaultScheduleInterval, + doesSetRecoveryContext, }, ]: [string, UntypedNormalizedRuleType]) => ({ id, @@ -345,6 +347,7 @@ export class RuleTypeRegistry { ruleTaskTimeout, minimumScheduleInterval, defaultScheduleInterval, + doesSetRecoveryContext, enabledInLicense: !!this.licenseState.getLicenseCheckForRuleType( id, name, 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 63e35583bc9a1..72ef2dba89ce7 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -1337,7 +1337,7 @@ export class RulesClient { const recoveredAlertInstances = mapValues, Alert>( state.alertInstances ?? {}, - (rawAlertInstance) => new Alert(rawAlertInstance) + (rawAlertInstance, alertId) => new Alert(alertId, rawAlertInstance) ); const recoveredAlertInstanceIds = Object.keys(recoveredAlertInstances); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index 5e2d8efedbcb3..1d7d3d2a362a9 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -2063,31 +2063,172 @@ describe('successful migrations', () => { { params: { outputIndex: 'output-index', type: 'query' }, alertTypeId: 'not.siem.signals' }, true ); - expect(migration800(alert, migrationContext).attributes.alertTypeId).toEqual( - 'not.siem.signals' - ); - expect(migration800(alert, migrationContext).attributes.enabled).toEqual(true); - expect(migration800(alert, migrationContext).attributes.params.outputIndex).toEqual( - 'output-index' - ); + const migratedAlert = migration800(alert, migrationContext); + expect(migratedAlert.attributes.alertTypeId).toEqual('not.siem.signals'); + expect(migratedAlert.attributes.enabled).toEqual(true); + expect(migratedAlert.attributes.tags).toEqual(['foo']); + expect(migratedAlert.attributes.params.outputIndex).toEqual('output-index'); }); test.each(Object.keys(ruleTypeMappings) as RuleType[])( - 'Changes AAD rule params accordingly if rule is a siem.signals %p rule', + 'changes AAD rule params accordingly if rule is a siem.signals %p rule', (ruleType) => { const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; const alert = getMockData( { params: { outputIndex: 'output-index', type: ruleType }, alertTypeId: 'siem.signals' }, true ); - expect(migration800(alert, migrationContext).attributes.alertTypeId).toEqual( - ruleTypeMappings[ruleType] - ); - expect(migration800(alert, migrationContext).attributes.enabled).toEqual(false); - expect(migration800(alert, migrationContext).attributes.params.outputIndex).toEqual(''); + const migratedAlert = migration800(alert, migrationContext); + expect(migratedAlert.attributes.alertTypeId).toEqual(ruleTypeMappings[ruleType]); + expect(migratedAlert.attributes.enabled).toEqual(false); + expect(migratedAlert.attributes.tags).toEqual(['foo']); + expect(migratedAlert.attributes.params.outputIndex).toEqual(''); } ); + describe('8.0.1', () => { + describe.each(Object.keys(ruleTypeMappings) as RuleType[])( + 'auto_disabled %p rule tags', + (ruleType) => { + const alert717Enabled = getMockData( + { + params: { outputIndex: 'output-index', type: ruleType }, + alertTypeId: 'siem.signals', + enabled: true, + scheduledTaskId: 'abcd', + }, + true + ); + const alert717Disabled = getMockData( + { + params: { outputIndex: 'output-index', type: ruleType }, + alertTypeId: 'siem.signals', + enabled: false, + }, + true + ); + const alert800 = getMockData( + { + params: { outputIndex: '', type: ruleType }, + alertTypeId: ruleTypeMappings[ruleType], + enabled: false, + scheduledTaskId: 'abcd', + }, + true + ); + + test('Does not update rule tags if rule has already been enabled', () => { + const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migration800 = migrations['8.0.0']; + const migration801 = migrations['8.0.1']; + + // migrate to 8.0.0 + const migratedAlert800 = migration800(alert717Enabled, migrationContext); + expect(migratedAlert800.attributes.enabled).toEqual(false); + + // reenable rule + migratedAlert800.attributes.enabled = true; + + // migrate to 8.0.1 + const migratedAlert801 = migration801(migratedAlert800, migrationContext); + + expect(migratedAlert801.attributes.alertTypeId).toEqual(ruleTypeMappings[ruleType]); + expect(migratedAlert801.attributes.enabled).toEqual(true); + expect(migratedAlert801.attributes.params.outputIndex).toEqual(''); + + // tags not updated + expect(migratedAlert801.attributes.tags).toEqual(['foo']); + }); + + test('Does not update rule tags if rule was already disabled before upgrading to 8.0', () => { + const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migration800 = migrations['8.0.0']; + const migration801 = migrations['8.0.1']; + + // migrate to 8.0.0 + const migratedAlert800 = migration800(alert717Disabled, migrationContext); + expect(migratedAlert800.attributes.enabled).toEqual(false); + + // migrate to 8.0.1 + const migratedAlert801 = migration801(migratedAlert800, migrationContext); + + expect(migratedAlert801.attributes.alertTypeId).toEqual(ruleTypeMappings[ruleType]); + expect(migratedAlert801.attributes.enabled).toEqual(false); + expect(migratedAlert801.attributes.params.outputIndex).toEqual(''); + + // tags not updated + expect(migratedAlert801.attributes.tags).toEqual(['foo']); + }); + + test('Updates rule tags if rule was auto-disabled in 8.0 upgrade and not reenabled', () => { + const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migration800 = migrations['8.0.0']; + const migration801 = migrations['8.0.1']; + + // migrate to 8.0.0 + const migratedAlert800 = migration800(alert717Enabled, migrationContext); + expect(migratedAlert800.attributes.enabled).toEqual(false); + + // migrate to 8.0.1 + const migratedAlert801 = migration801(migratedAlert800, migrationContext); + + expect(migratedAlert801.attributes.alertTypeId).toEqual(ruleTypeMappings[ruleType]); + expect(migratedAlert801.attributes.enabled).toEqual(false); + expect(migratedAlert801.attributes.params.outputIndex).toEqual(''); + + // tags updated + expect(migratedAlert801.attributes.tags).toEqual(['foo', 'auto_disabled_8.0']); + }); + + test('Updates rule tags correctly if tags are undefined', () => { + const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migration801 = migrations['8.0.1']; + + const alert = { + ...alert800, + attributes: { + ...alert800.attributes, + tags: undefined, + }, + }; + + // migrate to 8.0.1 + const migratedAlert801 = migration801(alert, migrationContext); + + expect(migratedAlert801.attributes.alertTypeId).toEqual(ruleTypeMappings[ruleType]); + expect(migratedAlert801.attributes.enabled).toEqual(false); + expect(migratedAlert801.attributes.params.outputIndex).toEqual(''); + + // tags updated + expect(migratedAlert801.attributes.tags).toEqual(['auto_disabled_8.0']); + }); + + test('Updates rule tags correctly if tags are null', () => { + const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migration801 = migrations['8.0.1']; + + const alert = { + ...alert800, + attributes: { + ...alert800.attributes, + tags: null, + }, + }; + + // migrate to 8.0.1 + const migratedAlert801 = migration801(alert, migrationContext); + + expect(migratedAlert801.attributes.alertTypeId).toEqual(ruleTypeMappings[ruleType]); + expect(migratedAlert801.attributes.enabled).toEqual(false); + expect(migratedAlert801.attributes.params.outputIndex).toEqual(''); + + // tags updated + expect(migratedAlert801.attributes.tags).toEqual(['auto_disabled_8.0']); + }); + } + ); + }); + describe('Metrics Inventory Threshold rule', () => { test('Migrates incorrect action group spelling', () => { const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index e664095e8c846..6e6c886d91b53 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -58,6 +58,9 @@ export const isAnyActionSupportIncidents = (doc: SavedObjectUnsanitizedDoc): boolean => doc.attributes.alertTypeId === 'siem.signals'; +export const isDetectionEngineAADRuleType = (doc: SavedObjectUnsanitizedDoc): boolean => + (Object.values(ruleTypeMappings) as string[]).includes(doc.attributes.alertTypeId); + /** * Returns true if the alert type is that of "siem.notifications" which is a legacy notification system that was deprecated in 7.16.0 * in favor of using the newer alerting notifications system. @@ -136,6 +139,12 @@ export function getMigrations( ) ); + const migrationRules801 = createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(addSecuritySolutionAADRuleTypeTags) + ); + return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyWhen, '7.11.0'), @@ -145,6 +154,7 @@ export function getMigrations( '7.15.0': executeMigrationWithErrorHandling(migrationSecurityRules715, '7.15.0'), '7.16.0': executeMigrationWithErrorHandling(migrateRules716, '7.16.0'), '8.0.0': executeMigrationWithErrorHandling(migrationRules800, '8.0.0'), + '8.0.1': executeMigrationWithErrorHandling(migrationRules801, '8.0.1'), }; } @@ -672,6 +682,28 @@ function addSecuritySolutionAADRuleTypes( : doc; } +function addSecuritySolutionAADRuleTypeTags( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const ruleType = doc.attributes.params.type; + return isDetectionEngineAADRuleType(doc) && isRuleType(ruleType) + ? { + ...doc, + attributes: { + ...doc.attributes, + // If the rule is disabled at this point, then the rule has not been re-enabled after + // running the 8.0.0 migrations. If `doc.attributes.scheduledTaskId` exists, then the + // rule was enabled prior to running the migration. Thus we know we should add the + // tag to indicate it was auto-disabled. + tags: + !doc.attributes.enabled && doc.attributes.scheduledTaskId + ? [...(doc.attributes.tags ?? []), 'auto_disabled_8.0'] + : doc.attributes.tags ?? [], + }, + } + : doc; +} + function addThreatIndicatorPathToThreatMatchRules( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 51d50c398c6f5..8bc4ad280873e 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -66,6 +66,7 @@ import { Event, } from '../lib/create_alert_event_log_record_object'; import { createAbortableEsClientFactory } from '../lib/create_abortable_es_client_factory'; +import { getRecoveredAlerts } from '../lib'; const FALLBACK_RETRY_INTERVAL = '5m'; const CONNECTIVITY_RETRY_INTERVAL = '5m'; @@ -334,7 +335,11 @@ export class TaskRunner< const alerts = mapValues< Record, CreatedAlert - >(alertRawInstances, (rawAlert) => new CreatedAlert(rawAlert)); + >( + alertRawInstances, + (rawAlert, alertId) => new CreatedAlert(alertId, rawAlert) + ); + const originalAlerts = cloneDeep(alerts); const originalAlertIds = new Set(Object.keys(originalAlerts)); @@ -364,6 +369,8 @@ export class TaskRunner< WithoutReservedActionGroups >({ alerts, + logger: this.logger, + canSetRecoveryContext: ruleType.doesSetRecoveryContext ?? false, }), shouldWriteAlerts: () => this.shouldLogAndScheduleActionsForAlerts(), shouldStopExecution: () => this.cancelled, @@ -424,17 +431,15 @@ export class TaskRunner< alerts, (alert: CreatedAlert) => alert.hasScheduledActions() ); - const recoveredAlerts = pickBy( - alerts, - (alert: CreatedAlert, id) => - !alert.hasScheduledActions() && originalAlertIds.has(id) - ); + + const recoveredAlerts = getRecoveredAlerts(alerts, originalAlertIds); logActiveAndRecoveredAlerts({ logger: this.logger, activeAlerts: alertsWithScheduledActions, recoveredAlerts, ruleLabel, + canSetRecoveryContext: ruleType.doesSetRecoveryContext ?? false, }); trackAlertDurations({ @@ -1155,7 +1160,7 @@ async function scheduleActionsForRecoveredAlerts< alert.unscheduleActions(); const triggeredActionsForRecoveredAlert = await executionHandler({ actionGroup: recoveryActionGroup.id, - context: {}, + context: alert.getContext(), state: {}, alertId: id, }); @@ -1176,6 +1181,7 @@ interface LogActiveAndRecoveredAlertsParams< activeAlerts: Dictionary>; recoveredAlerts: Dictionary>; ruleLabel: string; + canSetRecoveryContext: boolean; } function logActiveAndRecoveredAlerts< @@ -1191,7 +1197,7 @@ function logActiveAndRecoveredAlerts< RecoveryActionGroupId > ) { - const { logger, activeAlerts, recoveredAlerts, ruleLabel } = params; + const { logger, activeAlerts, recoveredAlerts, ruleLabel, canSetRecoveryContext } = params; const activeAlertIds = Object.keys(activeAlerts); const recoveredAlertIds = Object.keys(recoveredAlerts); @@ -1218,6 +1224,16 @@ function logActiveAndRecoveredAlerts< recoveredAlertIds )}` ); + + if (canSetRecoveryContext) { + for (const id of recoveredAlertIds) { + if (!recoveredAlerts[id].hasContext()) { + logger.debug( + `rule ${ruleLabel} has no recovery context specified for recovered alert ${id}` + ); + } + } + } } } diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 9d6302774f889..50acb67a3de47 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -7,7 +7,7 @@ import type { IRouter, RequestHandlerContext, SavedObjectReference } from 'src/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; -import { PublicAlert } from './alert'; +import { AlertFactoryDoneUtils, PublicAlert } from './alert'; import { RuleTypeRegistry as OrigruleTypeRegistry } from './rule_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { RulesClient } from './rules_client'; @@ -76,6 +76,7 @@ export interface AlertServices< > extends Services { alertFactory: { create: (id: string) => PublicAlert; + done: () => AlertFactoryDoneUtils; }; shouldWriteAlerts: () => boolean; shouldStopExecution: () => boolean; @@ -167,6 +168,7 @@ export interface RuleType< minimumScheduleInterval?: string; ruleTaskTimeout?: string; cancelAlertsOnRuleTimeout?: boolean; + doesSetRecoveryContext?: boolean; } export type UntypedRuleType = RuleType< AlertTypeParams, diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/integration_settings/apm_server_not_installed.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/integration_settings/apm_server_not_installed.ts new file mode 100644 index 0000000000000..354ffa5e42e18 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/integration_settings/apm_server_not_installed.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. + */ + +const integrationsPath = '/app/integrations/browse'; + +describe('when navigating to the integrations browse page', () => { + beforeEach(() => { + cy.loginAsReadOnlyUser(); + cy.visit(integrationsPath); + }); + + it('should display Elastic APM integration option', () => { + cy.get('[data-test-subj="integration-card:epr:apm:featured').should( + 'exist' + ); + cy.contains('Elastic APM'); + }); + + describe('when clicking on the Elastic APM option but Fleet is not installed', () => { + it('should display Elastic APM in Fleet tab', () => { + cy.get('[data-test-subj="integration-card:epr:apm:featured').click(); + cy.get('[aria-selected="true"]').contains('Elastic APM in Fleet'); + cy.contains('Elastic APM now available in Fleet!'); + cy.contains('APM integration'); + }); + + it('should display no APM server detected when checking the apm server status', () => { + cy.intercept('POST', '/api/home/hits_status', { + count: 0, + }).as('hitsStatus'); + + cy.get('[data-test-subj="integration-card:epr:apm:featured').click(); + cy.contains('Check APM Server status').click(); + cy.wait('@hitsStatus'); + cy.contains( + 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.' + ); + }); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/ftr_config.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config.ts index 84d1c40930c70..ec2e8d05a97dd 100644 --- a/x-pack/plugins/apm/ftr_e2e/ftr_config.ts +++ b/x-pack/plugins/apm/ftr_e2e/ftr_config.ts @@ -8,9 +8,6 @@ import { FtrConfigProviderContext } from '@kbn/test'; import { CA_CERT_PATH } from '@kbn/dev-utils'; -// Used to spin up a docker container with package registry service that will be used by fleet -export const packageRegistryPort = 1234; - async function config({ readConfigFile }: FtrConfigProviderContext) { const kibanaCommonTestsConfig = await readConfigFile( require.resolve('../../../../test/common/config.js') @@ -41,11 +38,6 @@ async function config({ readConfigFile }: FtrConfigProviderContext) { '--csp.warnLegacyBrowsers=false', // define custom kibana server args here `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, - - // Fleet config - `--xpack.fleet.packages.0.name=endpoint`, - `--xpack.fleet.packages.0.version=latest`, - `--xpack.fleet.registryUrl=http://localhost:${packageRegistryPort}`, ], }, }; diff --git a/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts index 8fc87b49a6607..768ad9b3f79f6 100644 --- a/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts +++ b/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts @@ -5,41 +5,17 @@ * 2.0. */ -import { defineDockerServersConfig, FtrConfigProviderContext } from '@kbn/test'; +import { FtrConfigProviderContext } from '@kbn/test'; import cypress from 'cypress'; -import path from 'path'; import { cypressStart } from './cypress_start'; -import { packageRegistryPort } from './ftr_config'; import { FtrProviderContext } from './ftr_provider_context'; -export const dockerImage = - 'docker.elastic.co/package-registry/distribution@sha256:de952debe048d903fc73e8a4472bb48bb95028d440cba852f21b863d47020c61'; - async function ftrConfigRun({ readConfigFile }: FtrConfigProviderContext) { const kibanaConfig = await readConfigFile(require.resolve('./ftr_config.ts')); - // mount the config file for the package registry - const dockerArgs: string[] = [ - '-v', - `${path.join( - path.dirname(__filename), - './apis/fixtures/package_registry_config.yml' - )}:/package-registry/config.yml`, - ]; - return { ...kibanaConfig.getAll(), testRunner, - dockerServers: defineDockerServersConfig({ - registry: { - enabled: true, - image: dockerImage, - portInContainer: 8080, - port: packageRegistryPort, - args: dockerArgs, - waitForLogLine: 'package manifests loaded', - }, - }), }; } diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap index de13bf910ce0f..5f300b45de80a 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap @@ -34,11 +34,7 @@ exports[`DetailView should render Discover button 1`] = ` } kuery="" > - - View 10 occurrences in Discover. - + View 10 occurrences in Discover. `; diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx index 97118bf763d43..0a6b134275121 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx @@ -6,7 +6,6 @@ */ import { - EuiButtonEmpty, EuiIcon, EuiPanel, EuiSpacer, @@ -100,16 +99,14 @@ export function DetailView({ errorGroup, urlParams, kuery }: Props) { - - {i18n.translate( - 'xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel', - { - defaultMessage: - 'View {occurrencesCount} {occurrencesCount, plural, one {occurrence} other {occurrences}} in Discover.', - values: { occurrencesCount }, - } - )} - + {i18n.translate( + 'xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel', + { + defaultMessage: + 'View {occurrencesCount} {occurrencesCount, plural, one {occurrence} other {occurrences}} in Discover.', + values: { occurrencesCount }, + } + )} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/index.tsx index 0f7a6a295601b..477098aa81d04 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/index.tsx @@ -7,7 +7,6 @@ import { EuiBadge, - EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, @@ -126,14 +125,12 @@ export function SpanFlyout({ - - {i18n.translate( - 'xpack.apm.transactionDetails.spanFlyout.viewSpanInDiscoverButtonLabel', - { - defaultMessage: 'View span in Discover', - } - )} - + {i18n.translate( + 'xpack.apm.transactionDetails.spanFlyout.viewSpanInDiscoverButtonLabel', + { + defaultMessage: 'View span in Discover', + } + )} diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts index bcb52f4721dd5..1d37dde6c6343 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts @@ -67,7 +67,7 @@ describe('Transaction action menu', () => { actions: [ { key: 'sampleDocument', - label: 'View sample document', + label: 'View transaction in Discover', href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', condition: true, }, @@ -134,7 +134,7 @@ describe('Transaction action menu', () => { actions: [ { key: 'sampleDocument', - label: 'View sample document', + label: 'View transaction in Discover', href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', condition: true, }, @@ -200,7 +200,7 @@ describe('Transaction action menu', () => { actions: [ { key: 'sampleDocument', - label: 'View sample document', + label: 'View transaction in Discover', href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', condition: true, }, diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts index daf5cb0833b61..20c10b6d9557c 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts @@ -191,7 +191,7 @@ export const getSections = ({ label: i18n.translate( 'xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel', { - defaultMessage: 'View sample document', + defaultMessage: 'View transaction in Discover', } ), href: getDiscoverHref({ diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx index 4d82bb69ee9a5..851472cfedabe 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx @@ -74,7 +74,7 @@ describe('TransactionActionMenu component', () => { Transactions.transactionWithMinimalData ); - expect(queryByText('View sample document')).not.toBeNull(); + expect(queryByText('View transaction in Discover')).not.toBeNull(); }); it('always renders the trace logs link', async () => { diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts index 75be545a7e427..12c47936374e1 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts @@ -6,11 +6,11 @@ */ import { getSearchAggregatedTransactions } from '.'; -import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; import { Setup } from '../setup_request'; import { kqlQuery, rangeQuery } from '../../../../../observability/server'; import { ProcessorEvent } from '../../../../common/processor_event'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; +import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; export async function getIsUsingTransactionEvents({ setup: { config, apmEventClient }, @@ -23,20 +23,6 @@ export async function getIsUsingTransactionEvents({ start?: number; end?: number; }): Promise { - const searchAggregatedTransactions = config.searchAggregatedTransactions; - - if ( - searchAggregatedTransactions === SearchAggregatedTransactionSetting.never - ) { - return false; - } - if ( - !kuery && - searchAggregatedTransactions === SearchAggregatedTransactionSetting.always - ) { - return false; - } - const searchesAggregatedTransactions = await getSearchAggregatedTransactions({ config, start, @@ -45,7 +31,11 @@ export async function getIsUsingTransactionEvents({ kuery, }); - if (!searchesAggregatedTransactions) { + if ( + !searchesAggregatedTransactions && + config.searchAggregatedTransactions !== + SearchAggregatedTransactionSetting.never + ) { // if no aggregrated transactions, check if any transactions at all return await getHasTransactions({ start, diff --git a/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts b/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts index f881b4476fe22..a34b3cdb1334d 100644 --- a/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts +++ b/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts @@ -42,7 +42,7 @@ export const createRuleTypeMocks = () => { savedObjectsClient: { get: () => ({ attributes: { consumer: APM_SERVER_FEATURE_ID } }), }, - alertFactory: { create: jest.fn(() => ({ scheduleActions })) }, + alertFactory: { create: jest.fn(() => ({ scheduleActions })), done: {} }, alertWithLifecycle: jest.fn(), logger: loggerMock, shouldWriteAlerts: () => true, diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index f6bfb510cab81..95135f4a0e9a0 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -26,15 +26,6 @@ export interface CasesContextFeatures { export type CasesFeatures = Partial; -export interface CasesContextValue { - owner: string[]; - appId: string; - appTitle: string; - userCanCrud: boolean; - basePath: string; - features: CasesContextFeatures; -} - export interface CasesUiConfigType { markdownPlugins: { lens: boolean; diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts new file mode 100644 index 0000000000000..f948072323214 --- /dev/null +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts @@ -0,0 +1,50 @@ +/* + * 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 { CreateCaseFlyoutProps } from '../create/flyout'; + +export const getInitialCasesContextState = (): CasesContextState => { + return { + createCaseFlyout: { + isFlyoutOpen: false, + }, + }; +}; + +export interface CasesContextState { + createCaseFlyout: { + isFlyoutOpen: boolean; + props?: CreateCaseFlyoutProps; + }; +} + +export enum CasesContextStoreActionsList { + OPEN_CREATE_CASE_FLYOUT, + CLOSE_CREATE_CASE_FLYOUT, +} +export type CasesContextStoreAction = + | { + type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT; + payload: CreateCaseFlyoutProps; + } + | { type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT }; + +export const casesContextReducer: React.Reducer = ( + state: CasesContextState, + action: CasesContextStoreAction +): CasesContextState => { + switch (action.type) { + case CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT: { + return { ...state, createCaseFlyout: { isFlyoutOpen: true, props: action.payload } }; + } + case CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT: { + return { ...state, createCaseFlyout: { isFlyoutOpen: false } }; + } + default: + return state; + } +}; diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx new file mode 100644 index 0000000000000..53c9129812d8b --- /dev/null +++ b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx @@ -0,0 +1,48 @@ +/* + * 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 { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; +import { getCreateCaseFlyoutLazyNoProvider } from '../../methods/get_create_case_flyout'; +import { CasesGlobalComponents } from './cases_global_components'; + +jest.mock('../../methods/get_create_case_flyout'); + +const getCreateCaseFlyoutLazyNoProviderMock = getCreateCaseFlyoutLazyNoProvider as jest.Mock; + +describe('Cases context UI', () => { + let appMock: AppMockRenderer; + + beforeEach(() => { + appMock = createAppMockRenderer(); + getCreateCaseFlyoutLazyNoProviderMock.mockClear(); + }); + + describe('create case flyout', () => { + it('should render the create case flyout when isFlyoutOpen is true', async () => { + const state = { + createCaseFlyout: { + isFlyoutOpen: true, + props: { + attachments: [], + }, + }, + }; + appMock.render(); + expect(getCreateCaseFlyoutLazyNoProviderMock).toHaveBeenCalledWith({ attachments: [] }); + }); + it('should not render the create case flyout when isFlyoutOpen is false', async () => { + const state = { + createCaseFlyout: { + isFlyoutOpen: false, + }, + }; + appMock.render(); + expect(getCreateCaseFlyoutLazyNoProviderMock).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_global_components.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.tsx new file mode 100644 index 0000000000000..42ff36e201df4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { getCreateCaseFlyoutLazyNoProvider } from '../../methods'; +import { CasesContextState } from './cases_context_reducer'; + +export const CasesGlobalComponents = React.memo(({ state }: { state: CasesContextState }) => { + return ( + <> + {state.createCaseFlyout.isFlyoutOpen && state.createCaseFlyout.props !== undefined + ? getCreateCaseFlyoutLazyNoProvider(state.createCaseFlyout.props) + : null} + + ); +}); +CasesGlobalComponents.displayName = 'CasesContextUi'; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index aceefad97382a..1f1da31595a04 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -5,21 +5,38 @@ * 2.0. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useReducer, Dispatch } from 'react'; import { merge } from 'lodash'; -import { CasesContextValue, CasesFeatures } from '../../../common/ui/types'; import { DEFAULT_FEATURES } from '../../../common/constants'; import { DEFAULT_BASE_PATH } from '../../common/navigation'; import { useApplication } from './use_application'; +import { + CasesContextStoreAction, + casesContextReducer, + getInitialCasesContextState, +} from './cases_context_reducer'; +import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; +import { CasesGlobalComponents } from './cases_global_components'; -export const CasesContext = React.createContext(undefined); +export type CasesContextValueDispatch = Dispatch; + +export interface CasesContextValue { + owner: string[]; + appId: string; + appTitle: string; + userCanCrud: boolean; + basePath: string; + features: CasesContextFeatures; + dispatch: CasesContextValueDispatch; +} -export interface CasesContextProps - extends Omit { +export interface CasesContextProps extends Pick { basePath?: string; features?: CasesFeatures; } +export const CasesContext = React.createContext(undefined); + export interface CasesContextStateValue extends Omit { appId?: string; appTitle?: string; @@ -30,6 +47,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ value: { owner, userCanCrud, basePath = DEFAULT_BASE_PATH, features = {} }, }) => { const { appId, appTitle } = useApplication(); + const [state, dispatch] = useReducer(casesContextReducer, getInitialCasesContextState()); const [value, setValue] = useState(() => ({ owner, userCanCrud, @@ -39,6 +57,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ * of the DEFAULT_FEATURES object */ features: merge({}, DEFAULT_FEATURES, features), + dispatch, })); /** @@ -58,7 +77,10 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ }, [appTitle, appId, userCanCrud]); return isCasesContextValue(value) ? ( - {children} + + + {children} + ) : null; }; CasesProvider.displayName = 'CasesProvider'; @@ -66,3 +88,6 @@ CasesProvider.displayName = 'CasesProvider'; function isCasesContextValue(value: CasesContextStateValue): value is CasesContextValue { return value.appId != null && value.appTitle != null && value.userCanCrud != null; } + +// eslint-disable-next-line import/no-default-export +export default CasesProvider; diff --git a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx index 0097df1587a73..c40dfc98513d8 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx @@ -16,8 +16,8 @@ import { UsePostComment } from '../../../containers/use_post_comment'; export interface CreateCaseFlyoutProps { afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise; - onClose: () => void; - onSuccess: (theCase: Case) => Promise; + onClose?: () => void; + onSuccess?: (theCase: Case) => Promise; attachments?: CreateCaseAttachment; } @@ -66,6 +66,8 @@ const FormWrapper = styled.div` export const CreateCaseFlyout = React.memo( ({ afterCaseCreated, onClose, onSuccess, attachments }) => { + const handleCancel = onClose || function () {}; + const handleOnSuccess = onSuccess || async function () {}; return ( <> @@ -85,8 +87,8 @@ export const CreateCaseFlyout = React.memo( diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx new file mode 100644 index 0000000000000..2c3750887cb1d --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/* eslint-disable react/display-name */ + +import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; +import { CasesContext } from '../../cases_context'; +import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; +import { useCasesAddToNewCaseFlyout } from './use_cases_add_to_new_case_flyout'; + +describe('use cases add to new case flyout hook', () => { + const dispatch = jest.fn(); + let wrapper: React.FC; + beforeEach(() => { + dispatch.mockReset(); + wrapper = ({ children }) => { + return ( + + {children} + + ); + }; + }); + + it('should throw if called outside of a cases context', () => { + const { result } = renderHook(() => { + useCasesAddToNewCaseFlyout({}); + }); + expect(result.error?.message).toContain( + 'useCasesContext must be used within a CasesProvider and have a defined value' + ); + }); + + it('should dispatch the open action when invoked', () => { + const { result } = renderHook( + () => { + return useCasesAddToNewCaseFlyout({}); + }, + { wrapper } + ); + result.current.open(); + expect(dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, + }) + ); + }); + + it('should dispatch the close action when invoked', () => { + const { result } = renderHook( + () => { + return useCasesAddToNewCaseFlyout({}); + }, + { wrapper } + ); + result.current.close(); + expect(dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT, + }) + ); + }); +}); diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx new file mode 100644 index 0000000000000..e9514ee582d99 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -0,0 +1,48 @@ +/* + * 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 { useCallback } from 'react'; +import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; +import { useCasesContext } from '../../cases_context/use_cases_context'; +import { CreateCaseFlyoutProps } from './create_case_flyout'; + +export const useCasesAddToNewCaseFlyout = (props: CreateCaseFlyoutProps) => { + const context = useCasesContext(); + + const closeFlyout = useCallback(() => { + context.dispatch({ + type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT, + }); + }, [context]); + + const openFlyout = useCallback(() => { + context.dispatch({ + type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, + payload: { + ...props, + onClose: () => { + closeFlyout(); + if (props.onClose) { + return props.onClose(); + } + }, + afterCaseCreated: async (...args) => { + closeFlyout(); + if (props.afterCaseCreated) { + return props.afterCaseCreated(...args); + } + }, + }, + }); + }, [closeFlyout, context, props]); + return { + open: openFlyout, + close: closeFlyout, + }; +}; + +export type UseCasesAddToNewCaseFlyout = typeof useCasesAddToNewCaseFlyout; diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx index c4784f9a891b1..c4646ff7f7c02 100644 --- a/x-pack/plugins/cases/public/components/create/form.tsx +++ b/x-pack/plugins/cases/public/components/create/form.tsx @@ -57,6 +57,7 @@ const MySpinner = styled(EuiLoadingSpinner)` `; export type SupportedCreateCaseAttachment = CommentRequestAlertType | CommentRequestUserType; export type CreateCaseAttachment = SupportedCreateCaseAttachment[]; +export type CaseAttachments = SupportedCreateCaseAttachment[]; export interface CreateCaseFormFieldsProps { connectors: ActionConnector[]; diff --git a/x-pack/plugins/cases/public/index.tsx b/x-pack/plugins/cases/public/index.tsx index 79eefba78a488..be23b9a46893b 100644 --- a/x-pack/plugins/cases/public/index.tsx +++ b/x-pack/plugins/cases/public/index.tsx @@ -19,6 +19,8 @@ export type { GetCreateCaseFlyoutProps } from './methods/get_create_case_flyout' export type { GetAllCasesSelectorModalProps } from './methods/get_all_cases_selector_modal'; export type { GetRecentCasesProps } from './methods/get_recent_cases'; +export type { CaseAttachments } from './components/create/form'; + export type { ICasesDeepLinkId } from './common/navigation'; export { getCasesDeepLinks, diff --git a/x-pack/plugins/cases/public/methods/get_cases_context.tsx b/x-pack/plugins/cases/public/methods/get_cases_context.tsx new file mode 100644 index 0000000000000..a2314696773b0 --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_cases_context.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 { EuiLoadingSpinner } from '@elastic/eui'; +import React, { lazy, ReactNode, Suspense } from 'react'; +import { CasesContextProps } from '../components/cases_context'; + +export type GetCasesContextProps = CasesContextProps; + +const CasesProviderLazy: React.FC<{ value: GetCasesContextProps }> = lazy( + () => import('../components/cases_context') +); + +const CasesProviderLazyWrapper = ({ + owner, + userCanCrud, + features, + children, +}: GetCasesContextProps & { children: ReactNode }) => { + return ( + }> + {children} + + ); +}; +CasesProviderLazyWrapper.displayName = 'CasesProviderLazyWrapper'; + +export const getCasesContextLazy = () => { + return CasesProviderLazyWrapper; +}; diff --git a/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx b/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx index 90fbeafaa9ed0..a0453c8fbb47e 100644 --- a/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx +++ b/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx @@ -12,7 +12,7 @@ import { CasesProvider, CasesContextProps } from '../components/cases_context'; export type GetCreateCaseFlyoutProps = CreateCaseFlyoutProps & CasesContextProps; -const CreateCaseFlyoutLazy: React.FC = lazy( +export const CreateCaseFlyoutLazy: React.FC = lazy( () => import('../components/create/flyout') ); export const getCreateCaseFlyoutLazy = ({ @@ -35,3 +35,9 @@ export const getCreateCaseFlyoutLazy = ({ ); + +export const getCreateCaseFlyoutLazyNoProvider = (props: CreateCaseFlyoutProps) => ( + }> + + +); diff --git a/x-pack/plugins/cases/public/mocks.ts b/x-pack/plugins/cases/public/mocks.ts index 6f508d9b6da3b..7c89bb1ddc2f9 100644 --- a/x-pack/plugins/cases/public/mocks.ts +++ b/x-pack/plugins/cases/public/mocks.ts @@ -10,9 +10,14 @@ import { CasesUiStart } from './types'; const createStartContract = (): jest.Mocked => ({ canUseCases: jest.fn(), getCases: jest.fn(), + getCasesContext: jest.fn(), getAllCasesSelectorModal: jest.fn(), getCreateCaseFlyout: jest.fn(), getRecentCases: jest.fn(), + getCreateCaseFlyoutNoProvider: jest.fn(), + hooks: { + getUseCasesAddToNewCaseFlyout: jest.fn(), + }, }); export const casesPluginMock = { diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 70882560edb77..acf51c8380f65 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -14,8 +14,11 @@ import { getAllCasesSelectorModalLazy, getCreateCaseFlyoutLazy, canUseCases, + getCreateCaseFlyoutLazyNoProvider, } from './methods'; import { CasesUiConfigType } from '../common/ui/types'; +import { getCasesContextLazy } from './methods/get_cases_context'; +import { useCasesAddToNewCaseFlyout } from './components/create/flyout/use_cases_add_to_new_case_flyout'; /** * @public @@ -35,9 +38,14 @@ export class CasesUiPlugin implements Plugin} */ getCases: (props: GetCasesProps) => ReactElement; + getCasesContext: () => ( + props: GetCasesContextProps & { children: ReactNode } + ) => ReactElement; /** * Modal to select a case in a list of all owner cases * @param props GetAllCasesSelectorModalProps @@ -78,10 +84,16 @@ export interface CasesUiStart { * @returns A react component that is a flyout for creating a case */ getCreateCaseFlyout: (props: GetCreateCaseFlyoutProps) => ReactElement; + getCreateCaseFlyoutNoProvider: ( + props: CreateCaseFlyoutProps + ) => ReactElement; /** * Get the recent cases component * @param props GetRecentCasesProps * @returns A react component for showing recent cases */ getRecentCases: (props: GetRecentCasesProps) => ReactElement; + hooks: { + getUseCasesAddToNewCaseFlyout: UseCasesAddToNewCaseFlyout; + }; } diff --git a/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/types.ts index f2d0fcdb178ed..ee68681ccc8f5 100644 --- a/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/types.ts +++ b/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/types.ts @@ -10,4 +10,5 @@ export type DrilldownConfig = { dashboardId?: string; useCurrentFilters: boolean; useCurrentDateRange: boolean; + openInNewTab: boolean; }; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx index 5ee62cd414d4c..1e58b13c00004 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx @@ -69,6 +69,7 @@ export abstract class AbstractDashboardDrilldown { @@ -86,11 +87,12 @@ export abstract class AbstractDashboardDrilldown { - const { app, path, state } = await this.getLocation(config, context, false); - await this.params.start().core.application.navigateToApp(app, { - path, - state, - }); + if (config.openInNewTab) { + window.open(await this.getHref(config, context), '_blank'); + } else { + const { app, path, state } = await this.getLocation(config, context, false); + await this.params.start().core.application.navigateToApp(app, { path, state }); + } }; protected get locator() { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/collect_config_container.tsx index 537dabb7c7011..7b6caabfd5a7e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/collect_config_container.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/collect_config_container.tsx @@ -84,6 +84,7 @@ export class CollectConfigContainer extends React.Component< dashboards={mergeDashboards(dashboards, selectedDashboard)} currentFilters={config.useCurrentFilters} keepRange={config.useCurrentDateRange} + openInNewTab={config.openInNewTab} isLoading={isLoading} error={error} onDashboardSelect={(dashboardId) => { @@ -105,6 +106,12 @@ export class CollectConfigContainer extends React.Component< useCurrentDateRange: !config.useCurrentDateRange, }) } + onOpenInNewTab={() => + onConfig({ + ...config, + openInNewTab: !config.openInNewTab, + }) + } /> ); } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx index c694d48c98c8f..d6e00f7de0edb 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx @@ -11,6 +11,7 @@ import { txtChooseDestinationDashboard, txtUseCurrentFilters, txtUseCurrentDateRange, + txtOpenInNewTab, } from './i18n'; export interface DashboardDrilldownConfigProps { @@ -18,9 +19,11 @@ export interface DashboardDrilldownConfigProps { dashboards: Array>; currentFilters?: boolean; keepRange?: boolean; + openInNewTab?: boolean; onDashboardSelect: (dashboardId: string) => void; onCurrentFiltersToggle?: () => void; onKeepRangeToggle?: () => void; + onOpenInNewTab?: () => void; onSearchChange: (searchString: string) => void; isLoading: boolean; error?: string; @@ -31,13 +34,15 @@ export const DashboardDrilldownConfig: React.FC = dashboards, currentFilters, keepRange, + openInNewTab, onDashboardSelect, onCurrentFiltersToggle, onKeepRangeToggle, + onOpenInNewTab, onSearchChange, isLoading, error, -}) => { +}: DashboardDrilldownConfigProps) => { const selectedTitle = dashboards.find((item) => item.value === activeDashboardId)?.label || ''; return ( @@ -78,6 +83,16 @@ export const DashboardDrilldownConfig: React.FC = /> )} + {!!onOpenInNewTab && ( + + + + )} ); }; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/i18n.ts index c6f9ac57b88d1..cf822a6a72d7a 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/i18n.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/dashboard_drilldown_config/i18n.ts @@ -27,3 +27,10 @@ export const txtUseCurrentDateRange = i18n.translate( defaultMessage: 'Use date range from origin dashboard', } ); + +export const txtOpenInNewTab = i18n.translate( + 'xpack.dashboard.components.DashboardDrilldownConfig.openInNewTab', + { + defaultMessage: 'Open dashboard in new tab', + } +); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx index f8b26f0012325..2ceff7713f62d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx @@ -28,6 +28,7 @@ describe('.isConfigValid()', () => { dashboardId: '', useCurrentDateRange: false, useCurrentFilters: false, + openInNewTab: false, }) ).toBe(false); }); @@ -38,6 +39,7 @@ describe('.isConfigValid()', () => { dashboardId: 'id', useCurrentDateRange: false, useCurrentFilters: false, + openInNewTab: false, }) ).toBe(true); }); @@ -112,6 +114,7 @@ describe('.execute() & getHref', () => { dashboardId: 'id', useCurrentFilters: false, useCurrentDateRange: false, + openInNewTab: false, ...config, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx index 3ccd5f15e29e9..73380c0270821 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx @@ -27,6 +27,7 @@ import './inline_editable_tables.scss'; export interface InlineEditableTableProps { columns: Array>; items: Item[]; + defaultItem?: Partial; title: string; addButtonText?: string; canRemoveLastItem?: boolean; @@ -53,6 +54,7 @@ export const InlineEditableTable = ( const { instanceId, columns, + defaultItem, onAdd, onDelete, onReorder, @@ -67,6 +69,7 @@ export const InlineEditableTable = ( props={{ instanceId, columns, + defaultItem, onAdd, onDelete, onReorder, @@ -90,6 +93,7 @@ export const InlineEditableTableContents = ({ description, isLoading, lastItemWarning, + defaultItem, noItemsMessage = () => null, uneditableItems, ...rest diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts index d62d894d18ef5..a4a25d0ed5a66 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts @@ -48,6 +48,7 @@ interface InlineEditableTableValues { export interface InlineEditableTableProps { columns: Array>; instanceId: string; + defaultItem: Item; // TODO Because these callbacks are params, they are only set on the logic once (i.e., they are cached) // which makes using "useState" to back this really hard. onAdd(item: Item, onSuccess: () => void): void; @@ -79,12 +80,15 @@ export const InlineEditableTableLogic = kea ({ fieldErrors }), setRowErrors: (rowErrors) => ({ rowErrors }), }), - reducers: ({ props: { columns } }) => ({ + reducers: ({ props: { columns, defaultItem } }) => ({ editingItemValue: [ null, { doneEditing: () => null, - editNewItem: () => generateEmptyItem(columns), + editNewItem: () => + defaultItem + ? { ...generateEmptyItem(columns), ...defaultItem } + : generateEmptyItem(columns), editExistingItem: (_, { item }) => item, setEditingItemValue: (_, { item }) => item, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts index 7af40b23d9f64..b5309d8fedc1b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts @@ -7,6 +7,7 @@ import { groups } from './groups.mock'; +import { IndexingRule } from '../types'; import { staticSourceData } from '../views/content_sources/source_data'; import { mergeServerAndStaticData } from '../views/content_sources/sources_logic'; @@ -45,10 +46,25 @@ export const contentSources = [ }, ]; +const defaultIndexingRules: IndexingRule[] = [ + { + filterType: 'object_type', + include: 'value', + }, + { + filterType: 'path_template', + exclude: 'value', + }, + { + filterType: 'file_extension', + include: 'value', + }, +]; + const defaultIndexing = { enabled: true, defaultAction: 'include', - rules: [], + rules: defaultIndexingRules, schedule: { full: 'P1D', incremental: 'PT2H', diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index 9d3b2cb8aaefd..4510498465793 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -60,10 +60,10 @@ export const NAV = { defaultMessage: 'Frequency', } ), - SYNCHRONIZATION_OBJECTS_AND_ASSETS: i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.nav.synchronizationObjectsAndAssets', + SYNCHRONIZATION_ASSETS_AND_OBJECTS: i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.nav.synchronizationAssetsAndObjects', { - defaultMessage: 'Objects and assets', + defaultMessage: 'Assets and objects', } ), DISPLAY_SETTINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.displaySettings', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index ee180ae52e0b7..4857fa2a158a0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -76,7 +76,8 @@ export const DISPLAY_SETTINGS_RESULT_DETAIL_PATH = `${SOURCE_DISPLAY_SETTINGS_PA export const SYNC_FREQUENCY_PATH = `${SOURCE_SYNCHRONIZATION_PATH}/frequency`; export const BLOCKED_TIME_WINDOWS_PATH = `${SOURCE_SYNCHRONIZATION_PATH}/frequency/blocked_windows`; -export const OBJECTS_AND_ASSETS_PATH = `${SOURCE_SYNCHRONIZATION_PATH}/objects_and_assets`; +export const OLD_OBJECTS_AND_ASSETS_PATH = `${SOURCE_SYNCHRONIZATION_PATH}/objects_and_assets`; +export const ASSETS_AND_OBJECTS_PATH = `${SOURCE_SYNCHRONIZATION_PATH}/assets_and_objects`; export const ORG_SETTINGS_PATH = '/settings'; export const ORG_SETTINGS_CUSTOMIZE_PATH = `${ORG_SETTINGS_PATH}/customize`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts index 2e933d7bdf94a..b01700b8bce34 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts @@ -168,6 +168,18 @@ export interface BlockedWindow { end: string; } +export interface IndexingRuleExclude { + filterType: 'object_type' | 'path_template' | 'file_extension'; + exclude: string; +} + +export interface IndexingRuleInclude { + filterType: 'object_type' | 'path_template' | 'file_extension'; + include: string; +} + +export type IndexingRule = IndexingRuleInclude | IndexingRuleExclude; + export interface IndexingConfig { enabled: boolean; features: { @@ -178,6 +190,7 @@ export interface IndexingConfig { enabled: boolean; }; }; + rules: IndexingRule[]; schedule: IndexingSchedule; } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx index d5ba030e582b8..66c7f5df0b5b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx @@ -119,9 +119,9 @@ describe('useSourceSubNav', () => { href: '/sources/2/synchronization/frequency', }, { - id: 'sourceSynchronizationObjectsAndAssets', - name: 'Objects and assets', - href: '/sources/2/synchronization/objects_and_assets', + id: 'sourceSynchronizationAssetsAndObjects', + name: 'Assets and objects', + href: '/sources/2/synchronization/assets_and_objects', }, ], }, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/assets_and_objects.test.tsx similarity index 83% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/assets_and_objects.test.tsx index 13dc2872037c1..aee83b31be045 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/assets_and_objects.test.tsx @@ -16,19 +16,19 @@ import { shallow } from 'enzyme'; import { EuiSwitch } from '@elastic/eui'; -import { ObjectsAndAssets } from './objects_and_assets'; +import { AssetsAndObjects } from './assets_and_objects'; -describe('ObjectsAndAssets', () => { +describe('AssetsAndObjects', () => { const setThumbnailsChecked = jest.fn(); const setContentExtractionChecked = jest.fn(); - const updateObjectsAndAssetsSettings = jest.fn(); + const updateAssetsAndObjectsSettings = jest.fn(); const resetSyncSettings = jest.fn(); const contentSource = fullContentSources[0]; const mockActions = { setThumbnailsChecked, setContentExtractionChecked, - updateObjectsAndAssetsSettings, + updateAssetsAndObjectsSettings, resetSyncSettings, }; const mockValues = { @@ -37,7 +37,7 @@ describe('ObjectsAndAssets', () => { contentSource, thumbnailsChecked: true, contentExtractionChecked: true, - hasUnsavedObjectsAndAssetsChanges: false, + hasUnsavedAssetsAndObjectsChanges: false, }; beforeEach(() => { @@ -46,13 +46,13 @@ describe('ObjectsAndAssets', () => { }); it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiSwitch)).toHaveLength(2); }); it('handles thumbnails switch change', () => { - const wrapper = shallow(); + const wrapper = shallow(); wrapper .find('[data-test-subj="ThumbnailsToggle"]') .simulate('change', { target: { checked: false } }); @@ -61,7 +61,7 @@ describe('ObjectsAndAssets', () => { }); it('handles content extraction switch change', () => { - const wrapper = shallow(); + const wrapper = shallow(); wrapper .find('[data-test-subj="ContentExtractionToggle"]') .simulate('change', { target: { checked: false } }); @@ -77,7 +77,7 @@ describe('ObjectsAndAssets', () => { areThumbnailsConfigEnabled: false, }, }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('[data-test-subj="ThumbnailsToggle"]').prop('label')).toEqual( 'Sync thumbnails - disabled at global configuration level' diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/assets_and_objects.tsx similarity index 71% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/assets_and_objects.tsx index 460f7e7f42055..1d92398f53539 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/assets_and_objects.tsx @@ -18,7 +18,7 @@ import { EuiLink, EuiSpacer, EuiSwitch, - EuiText, + EuiTitle, } from '@elastic/eui'; import { SAVE_BUTTON_LABEL } from '../../../../../shared/constants'; @@ -27,27 +27,29 @@ import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prom import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { NAV, RESET_BUTTON } from '../../../../constants'; import { - LEARN_MORE_LINK, SYNC_MANAGEMENT_CONTENT_EXTRACTION_LABEL, SYNC_MANAGEMENT_THUMBNAILS_LABEL, SYNC_MANAGEMENT_THUMBNAILS_GLOBAL_CONFIG_LABEL, - SOURCE_OBJECTS_AND_ASSETS_DESCRIPTION, - SOURCE_OBJECTS_AND_ASSETS_LABEL, + SOURCE_ASSETS_AND_OBJECTS_DESCRIPTION, + SOURCE_ASSETS_AND_OBJECTS_ASSETS_LABEL, SYNC_UNSAVED_CHANGES_MESSAGE, + SOURCE_ASSETS_AND_OBJECTS_LEARN_MORE_LINK, + SOURCE_ASSETS_AND_OBJECTS_OBJECTS_LABEL, } from '../../constants'; import { SourceLogic } from '../../source_logic'; import { SourceLayout } from '../source_layout'; +import { IndexingRulesTable } from './indexing_rules_table'; import { SynchronizationLogic } from './synchronization_logic'; -export const ObjectsAndAssets: React.FC = () => { +export const AssetsAndObjects: React.FC = () => { const { contentSource, dataLoading } = useValues(SourceLogic); - const { thumbnailsChecked, contentExtractionChecked, hasUnsavedObjectsAndAssetsChanges } = + const { thumbnailsChecked, contentExtractionChecked, hasUnsavedAssetsAndObjectsChanges } = useValues(SynchronizationLogic({ contentSource })); const { setThumbnailsChecked, setContentExtractionChecked, - updateObjectsAndAssetsSettings, + updateAssetsAndObjectsSettings, resetSyncSettings, } = useActions(SynchronizationLogic({ contentSource })); @@ -55,47 +57,43 @@ export const ObjectsAndAssets: React.FC = () => { const actions = ( - - - {RESET_BUTTON} - - {SAVE_BUTTON_LABEL} + + + {RESET_BUTTON} + + ); return ( - - {SOURCE_OBJECTS_AND_ASSETS_DESCRIPTION}{' '} - - {LEARN_MORE_LINK} - - - } - action={actions} - /> + + {SOURCE_ASSETS_AND_OBJECTS_DESCRIPTION} + + + {SOURCE_ASSETS_AND_OBJECTS_LEARN_MORE_LINK} + - {SOURCE_OBJECTS_AND_ASSETS_LABEL} + +

{SOURCE_ASSETS_AND_OBJECTS_ASSETS_LABEL}

+
@@ -122,6 +120,15 @@ export const ObjectsAndAssets: React.FC = () => { /> + + +

{SOURCE_ASSETS_AND_OBJECTS_OBJECTS_LABEL}

+
+ + + + +
); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/indexing_rules_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/indexing_rules_table.test.tsx new file mode 100644 index 0000000000000..7d4b47e7cd8e6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/indexing_rules_table.test.tsx @@ -0,0 +1,250 @@ +/* + * 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 { + LogicMounter, + mockFlashMessageHelpers, + setMockActions, + setMockValues, +} from '../../../../../__mocks__/kea_logic'; +import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiFieldText, EuiSelect } from '@elastic/eui'; + +import { InlineEditableTable } from '../../../../../shared/tables/inline_editable_table'; + +import { SourceLogic } from '../../source_logic'; + +import { IndexingRulesTable } from './indexing_rules_table'; +import { SynchronizationLogic } from './synchronization_logic'; + +describe('IndexingRulesTable', () => { + const { clearFlashMessages } = mockFlashMessageHelpers; + const { mount: sourceMount } = new LogicMounter(SourceLogic); + const { mount: syncMount } = new LogicMounter(SynchronizationLogic); + + const indexingRules = [ + { id: 0, valueType: 'exclude', filterType: 'path_template', value: 'value' }, + { id: 1, valueType: 'include', filterType: 'file_extension', value: 'value' }, + { id: 2, valueType: 'include', filterType: 'object_type', value: 'value 2' }, + { id: 3, valueType: 'broken', filterType: 'not allowed', value: 'value 2' }, + ]; + const contentSource = fullContentSources[0]; + + beforeEach(() => { + jest.clearAllMocks(); + sourceMount({}, {}); + setMockValues({ contentSource }); + syncMount({}, { contentSource }); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(InlineEditableTable).exists()).toBe(true); + }); + + describe('columns', () => { + let wrapper: ShallowWrapper; + + beforeEach(() => { + wrapper = shallow(); + }); + + const renderColumn = (index: number, ruleIndex: number) => { + const columns = wrapper.find(InlineEditableTable).prop('columns'); + return shallow(
{columns[index].render(indexingRules[ruleIndex])}
); + }; + + const onChange = jest.fn(); + const renderColumnInEditingMode = (index: number, ruleIndex: number) => { + const columns = wrapper.find(InlineEditableTable).prop('columns'); + return shallow( +
+ {columns[index].editingRender(indexingRules[ruleIndex], onChange, { + isInvalid: false, + isLoading: false, + })} +
+ ); + }; + + describe(' column', () => { + it('shows the value type of an indexing rule', () => { + expect(renderColumn(0, 0).html()).toContain('Exclude'); + expect(renderColumn(0, 1).html()).toContain('Include'); + expect(renderColumn(0, 3).html()).toContain(''); + }); + + it('can show the value type of an indexing rule as editable', () => { + const column = renderColumnInEditingMode(0, 0); + + const selectField = column.find(EuiSelect); + expect(selectField.props()).toEqual( + expect.objectContaining({ + value: 'exclude', + disabled: false, + isInvalid: false, + options: [ + { text: 'Include', value: 'include' }, + { text: 'Exclude', value: 'exclude' }, + ], + }) + ); + + selectField.simulate('change', { target: { value: 'include' } }); + expect(onChange).toHaveBeenCalledWith('include'); + }); + }); + + describe('filter type column', () => { + it('shows the filter type of an indexing rule', () => { + expect(renderColumn(1, 0).html()).toContain('Path'); + expect(renderColumn(1, 1).html()).toContain('File'); + expect(renderColumn(1, 2).html()).toContain('Item'); + expect(renderColumn(1, 3).html()).toContain(''); + }); + + it('can show the filter type of an indexing rule as editable', () => { + const column = renderColumnInEditingMode(1, 0); + + const selectField = column.find(EuiSelect); + expect(selectField.props()).toEqual( + expect.objectContaining({ + value: 'path_template', + disabled: false, + isInvalid: false, + options: [ + { text: 'Item', value: 'object_type' }, + { text: 'Path', value: 'path_template' }, + { text: 'File type', value: 'file_extension' }, + ], + }) + ); + + selectField.simulate('change', { target: { value: 'object_type' } }); + expect(onChange).toHaveBeenCalledWith('object_type'); + }); + }); + + describe('pattern column', () => { + it('shows the value of an indexing rule', () => { + expect(renderColumn(2, 0).html()).toContain('value'); + }); + + it('can show the value of a indexing rule as editable', () => { + const column = renderColumnInEditingMode(2, 0); + + const field = column.find(EuiFieldText); + expect(field.props()).toEqual( + expect.objectContaining({ + value: 'value', + disabled: false, + isInvalid: false, + }) + ); + + field.simulate('change', { target: { value: 'foo' } }); + expect(onChange).toHaveBeenCalledWith('foo'); + }); + }); + }); + + describe('when an indexing rule is added', () => { + it('should update the indexing rules for the current domain, and clear flash messages', () => { + const initAddIndexingRule = jest.fn(); + const done = jest.fn(); + setMockActions({ + initAddIndexingRule, + }); + const wrapper = shallow(); + const table = wrapper.find(InlineEditableTable); + + const newIndexingRule = { + id: 2, + value: 'new value', + filterType: 'path_template', + valueType: 'include', + }; + table.prop('onAdd')(newIndexingRule, done); + expect(initAddIndexingRule).toHaveBeenCalledWith(newIndexingRule); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); + + describe('when an indexing rule is updated', () => { + it('should update the indexing rules for the current domain, and clear flash messages', () => { + const initSetIndexingRule = jest.fn(); + const done = jest.fn(); + setMockActions({ + initSetIndexingRule, + }); + const wrapper = shallow(); + const table = wrapper.find(InlineEditableTable); + + const newIndexingRule = { + id: 2, + value: 'new value', + filterType: 'path_template', + valueType: 'include', + }; + table.prop('onUpdate')(newIndexingRule, done); + expect(initSetIndexingRule).toHaveBeenCalledWith(newIndexingRule); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); + + describe('when a indexing rule is deleted', () => { + it('should update the indexing rules for the current domain, and clear flash messages', () => { + const deleteIndexingRule = jest.fn(); + const done = jest.fn(); + setMockActions({ + deleteIndexingRule, + }); + const wrapper = shallow(); + const table = wrapper.find(InlineEditableTable); + + const newIndexingRule = { + id: 2, + value: 'new value', + filterType: 'path_template', + valueType: 'include', + }; + table.prop('onDelete')(newIndexingRule, done); + expect(deleteIndexingRule).toHaveBeenCalledWith(newIndexingRule); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); + + describe('when an indexing rule is reordered', () => { + it('should update the indexing rules for the current domain, and clear flash messages', () => { + const setIndexingRules = jest.fn(); + const done = jest.fn(); + setMockActions({ + setIndexingRules, + }); + const wrapper = shallow(); + const table = wrapper.find(InlineEditableTable); + + const newIndexingRules = [ + { + id: 2, + value: 'new value', + filterType: 'path_template', + valueType: 'include', + }, + ]; + table.prop('onReorder')!(newIndexingRules, indexingRules, done); + expect(setIndexingRules).toHaveBeenCalledWith(newIndexingRules); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/indexing_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/indexing_rules_table.tsx new file mode 100644 index 0000000000000..ed1eb40c61ce5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/indexing_rules_table.tsx @@ -0,0 +1,235 @@ +/* + * 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 { useActions, useValues } from 'kea'; + +import { + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSelect, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { docLinks } from '../../../../../shared/doc_links'; +import { clearFlashMessages } from '../../../../../shared/flash_messages'; +import { InlineEditableTable } from '../../../../../shared/tables/inline_editable_table/inline_editable_table'; +import { InlineEditableTableColumn } from '../../../../../shared/tables/inline_editable_table/types'; + +import { SourceLogic } from '../../source_logic'; + +import { EditableIndexingRule, SynchronizationLogic } from './synchronization_logic'; + +const SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_POLICY_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePolicyLabel', + { + defaultMessage: 'Policy', + } +); + +const SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_PATH_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePathLabel', + { + defaultMessage: 'Path', + } +); + +export const SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_ITEM_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableItemLabel', + { + defaultMessage: 'Item', + } +); + +export const SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_FILE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableFileLabel', + { + defaultMessage: 'File type', + } +); + +export const SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_INCLUDE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableIncludeLabel', + { + defaultMessage: 'Include', + } +); + +export const SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_EXCLUDE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableExcludeLabel', + { + defaultMessage: 'Exclude', + } +); + +export const IndexingRulesTable: React.FC = () => { + const { contentSource } = useValues(SourceLogic); + const indexingRulesInstanceId = 'IndexingRulesTable'; + const { indexingRules } = useValues( + SynchronizationLogic({ contentSource, indexingRulesInstanceId }) + ); + const { initAddIndexingRule, deleteIndexingRule, initSetIndexingRule, setIndexingRules } = + useActions(SynchronizationLogic({ contentSource })); + + const description = ( + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsDescription', + { + defaultMessage: + 'Include or exclude high level items, file types and (file or folder) paths to synchronize from {contentSourceName}. Everything is included by default. Each document is tested against the rules below and the first rule that matches will be applied.', + values: { contentSourceName: contentSource.name }, + } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsSyncLearnMoreLink', + { + defaultMessage: 'Learn more about sync rules.', + } + )} + + + ); + + const valueTypeToString = (input: string): string => { + switch (input) { + case 'include': + return SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_INCLUDE_LABEL; + case 'exclude': + return SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_EXCLUDE_LABEL; + default: + return ''; + } + }; + + const filterTypeToString = (input: string): string => { + switch (input) { + case 'object_type': + return SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_ITEM_LABEL; + case 'path_template': + return SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_PATH_LABEL; + case 'file_extension': + return SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_FILE_LABEL; + default: + return ''; + } + }; + + const columns: Array> = [ + { + editingRender: (indexingRule, onChange, { isInvalid, isLoading }) => ( + onChange(e.target.value)} + disabled={isLoading} + isInvalid={isInvalid} + options={[ + { text: SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_INCLUDE_LABEL, value: 'include' }, + { text: SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_EXCLUDE_LABEL, value: 'exclude' }, + ]} + /> + ), + render: (indexingRule) => ( + {valueTypeToString(indexingRule.valueType)} + ), + name: SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_POLICY_LABEL, + field: 'valueType', + }, + { + editingRender: (indexingRule, onChange, { isInvalid, isLoading }) => ( + onChange(e.target.value)} + disabled={isLoading} + isInvalid={isInvalid} + options={[ + { text: SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_ITEM_LABEL, value: 'object_type' }, + { text: SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_PATH_LABEL, value: 'path_template' }, + { text: SOURCE_ASSETS_AND_OBJECTS_OBJECTS_TABLE_FILE_LABEL, value: 'file_extension' }, + ]} + /> + ), + render: (indexingRule) => ( + {filterTypeToString(indexingRule.filterType)} + ), + name: i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableRuleLabel', + { + defaultMessage: 'Rule', + } + ), + field: 'filterType', + }, + { + editingRender: (indexingRule, onChange, { isInvalid, isLoading }) => ( + + + onChange(e.target.value)} + disabled={isLoading} + isInvalid={isInvalid} + /> + + + ), + render: (indexingRule) => {indexingRule.value}, + name: i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableValueLabel', + { + defaultMessage: 'Value', + } + ), + field: 'value', + }, + ]; + + return ( + { + initAddIndexingRule(newRule); + clearFlashMessages(); + }} + onDelete={(rule) => { + deleteIndexingRule(rule); + clearFlashMessages(); + }} + onUpdate={(rule) => { + initSetIndexingRule(rule); + clearFlashMessages(); + }} + onReorder={(newIndexingRules) => { + setIndexingRules(newIndexingRules); + clearFlashMessages(); + }} + title="" + canRemoveLastItem + /> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts index 20a6ba238a2f4..63de2e4d55838 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts @@ -15,6 +15,11 @@ import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test-jest-helpers'; +import { + InlineEditableTableLogic, + InlineEditableTableProps, +} from '../../../../../shared/tables/inline_editable_table/inline_editable_table_logic'; +import { ItemWithAnID } from '../../../../../shared/tables/types'; import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; jest.mock('../../source_logic', () => ({ @@ -30,11 +35,12 @@ import { SynchronizationLogic, emptyBlockedWindow, stripScheduleSeconds, + EditableIndexingRule, } from './synchronization_logic'; describe('SynchronizationLogic', () => { const { http } = mockHttpValues; - const { flashSuccessToast } = mockFlashMessageHelpers; + const { flashSuccessToast, flashAPIErrors } = mockFlashMessageHelpers; const { navigateToUrl } = mockKibanaValues; const { mount } = new LogicMounter(SynchronizationLogic); const contentSource = fullContentSources[0]; @@ -49,12 +55,49 @@ describe('SynchronizationLogic', () => { }, }; + const defaultIndexingRules: EditableIndexingRule[] = [ + { + filterType: 'object_type', + id: 0, + value: 'value', + valueType: 'include', + }, + { + filterType: 'path_template', + id: 1, + value: 'value', + valueType: 'exclude', + }, + { + filterType: 'file_extension', + id: 2, + value: 'value', + valueType: 'include', + }, + ]; + const defaultValues = { navigatingBetweenTabs: false, - hasUnsavedObjectsAndAssetsChanges: false, + hasUnsavedAssetsAndObjectsChanges: false, + hasUnsavedIndexingRulesChanges: false, hasUnsavedFrequencyChanges: false, contentExtractionChecked: true, thumbnailsChecked: true, + indexingRules: defaultIndexingRules, + indexingRulesForAPI: [ + { + filter_type: 'object_type', + include: 'value', + }, + { + filter_type: 'path_template', + exclude: 'value', + }, + { + filter_type: 'file_extension', + include: 'value', + }, + ], schedule: contentSource.indexing.schedule, cachedSchedule: contentSource.indexing.schedule, }; @@ -109,10 +152,17 @@ describe('SynchronizationLogic', () => { it('resetSyncSettings', () => { SynchronizationLogic.actions.setContentExtractionChecked(false); SynchronizationLogic.actions.setThumbnailsChecked(false); + SynchronizationLogic.actions.addIndexingRule({ + filterType: 'file_extension', + valueType: 'exclude', + value: 'value', + }); SynchronizationLogic.actions.resetSyncSettings(); expect(SynchronizationLogic.values.thumbnailsChecked).toEqual(true); expect(SynchronizationLogic.values.contentExtractionChecked).toEqual(true); + expect(SynchronizationLogic.values.indexingRules).toEqual(defaultIndexingRules); + expect(SynchronizationLogic.values.hasUnsavedIndexingRulesChanges).toEqual(false); }); describe('setSyncFrequency', () => { @@ -151,37 +201,133 @@ describe('SynchronizationLogic', () => { expect(SynchronizationLogic.values.schedule.blockedWindows).toBeUndefined(); }); }); - }); - describe('setBlockedTimeWindow', () => { - it('sets "jobType"', () => { - SynchronizationLogic.actions.addBlockedWindow(); - SynchronizationLogic.actions.setBlockedTimeWindow(0, 'jobType', 'incremental'); + describe('setBlockedTimeWindow', () => { + it('sets "jobType"', () => { + SynchronizationLogic.actions.addBlockedWindow(); + SynchronizationLogic.actions.setBlockedTimeWindow(0, 'jobType', 'incremental'); + + expect(SynchronizationLogic.values.schedule.blockedWindows![0].jobType).toEqual( + 'incremental' + ); + }); + + it('sets "day"', () => { + SynchronizationLogic.actions.addBlockedWindow(); + SynchronizationLogic.actions.setBlockedTimeWindow(0, 'day', 'tuesday'); + + expect(SynchronizationLogic.values.schedule.blockedWindows![0].day).toEqual('tuesday'); + }); + + it('sets "start"', () => { + SynchronizationLogic.actions.addBlockedWindow(); + SynchronizationLogic.actions.setBlockedTimeWindow(0, 'start', '9:00:00Z'); - expect(SynchronizationLogic.values.schedule.blockedWindows![0].jobType).toEqual( - 'incremental' - ); + expect(SynchronizationLogic.values.schedule.blockedWindows![0].start).toEqual('9:00:00Z'); + }); + + it('sets "end"', () => { + SynchronizationLogic.actions.addBlockedWindow(); + SynchronizationLogic.actions.setBlockedTimeWindow(0, 'end', '11:00:00Z'); + + expect(SynchronizationLogic.values.schedule.blockedWindows![0].end).toEqual('11:00:00Z'); + }); }); - it('sets "day"', () => { - SynchronizationLogic.actions.addBlockedWindow(); - SynchronizationLogic.actions.setBlockedTimeWindow(0, 'day', 'tuesday'); + describe('addIndexingRule', () => { + const indexingRule: EditableIndexingRule = { + filterType: 'file_extension', + valueType: 'exclude', + value: 'value', + id: 10, + }; - expect(SynchronizationLogic.values.schedule.blockedWindows![0].day).toEqual('tuesday'); + it('adds indexing rule with id 0', () => { + SynchronizationLogic.actions.setIndexingRules([]); + SynchronizationLogic.actions.addIndexingRule(indexingRule); + + expect(SynchronizationLogic.values.indexingRules).toEqual([{ ...indexingRule, id: 0 }]); + expect(SynchronizationLogic.values.hasUnsavedIndexingRulesChanges).toEqual(true); + }); + + it('adds indexing rule with id existing length + 1', () => { + SynchronizationLogic.actions.addIndexingRule(indexingRule); + + expect(SynchronizationLogic.values.indexingRules).toEqual([ + ...defaultValues.indexingRules, + { ...indexingRule, id: 3 }, + ]); + expect(SynchronizationLogic.values.hasUnsavedIndexingRulesChanges).toEqual(true); + }); + it('adds indexing rule with unique id in case of previous deletions', () => { + SynchronizationLogic.actions.deleteIndexingRule({ ...indexingRule, id: 1 }); + SynchronizationLogic.actions.addIndexingRule(indexingRule); + + expect(SynchronizationLogic.values.indexingRules).toEqual([ + defaultValues.indexingRules[0], + defaultValues.indexingRules[2], + { ...indexingRule, id: 3 }, + ]); + expect(SynchronizationLogic.values.hasUnsavedIndexingRulesChanges).toEqual(true); + }); }); - it('sets "start"', () => { - SynchronizationLogic.actions.addBlockedWindow(); - SynchronizationLogic.actions.setBlockedTimeWindow(0, 'start', '9:00:00Z'); + describe('setIndexingRule', () => { + const indexingRule: EditableIndexingRule = { + filterType: 'file_extension', + valueType: 'exclude', + value: 'value', + id: 1, + }; + + it('updates indexing rule', () => { + SynchronizationLogic.actions.setIndexingRule(indexingRule); - expect(SynchronizationLogic.values.schedule.blockedWindows![0].start).toEqual('9:00:00Z'); + expect(SynchronizationLogic.values.indexingRules).toEqual([ + defaultValues.indexingRules[0], + indexingRule, + defaultValues.indexingRules[2], + ]); + expect(SynchronizationLogic.values.hasUnsavedIndexingRulesChanges).toEqual(true); + }); }); - it('sets "end"', () => { - SynchronizationLogic.actions.addBlockedWindow(); - SynchronizationLogic.actions.setBlockedTimeWindow(0, 'end', '11:00:00Z'); + describe('setIndexingRules', () => { + const indexingRule: EditableIndexingRule = { + filterType: 'file_extension', + valueType: 'exclude', + value: 'value', + id: 1, + }; - expect(SynchronizationLogic.values.schedule.blockedWindows![0].end).toEqual('11:00:00Z'); + it('updates indexing rules', () => { + SynchronizationLogic.actions.setIndexingRules([indexingRule, indexingRule]); + + expect(SynchronizationLogic.values.indexingRules).toEqual([ + { ...indexingRule, id: 0 }, + { ...indexingRule, id: 1 }, + ]); + expect(SynchronizationLogic.values.hasUnsavedIndexingRulesChanges).toEqual(true); + }); + }); + + describe('deleteIndexingRule', () => { + const indexingRule: EditableIndexingRule = { + filterType: 'file_extension', + valueType: 'exclude', + value: 'value', + id: 1, + }; + + it('updates indexing rules', () => { + const newIndexingRules = defaultValues.indexingRules.filter( + (val) => val.id !== indexingRule.id + ); + SynchronizationLogic.actions.deleteIndexingRule(indexingRule); + expect(SynchronizationLogic.values.indexingRules).toEqual(newIndexingRules); + + expect(SynchronizationLogic.values.hasUnsavedIndexingRulesChanges).toEqual(true); + }); }); }); @@ -209,6 +355,204 @@ describe('SynchronizationLogic', () => { }); }); + describe('initAddIndexingRule', () => { + const indexingRule: EditableIndexingRule = { + filterType: 'file_extension', + valueType: 'exclude', + value: 'value', + id: 1, + }; + it('calls validate endpoint and continues if no errors happen', async () => { + const addIndexingRuleSpy = jest.spyOn(SynchronizationLogic.actions, 'addIndexingRule'); + const promise = Promise.resolve({ rules: [] }); + const doneSpy = jest.spyOn( + InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps).actions, + 'doneEditing' + ); + http.post.mockReturnValue(promise); + SynchronizationLogic.actions.initAddIndexingRule(indexingRule); + + expect(http.post).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/indexing_rules/validate', + { + body: JSON.stringify({ + rules: [ + { + filter_type: 'file_extension', + exclude: 'value', + }, + ], + }), + } + ); + await promise; + expect(addIndexingRuleSpy).toHaveBeenCalledWith(indexingRule); + expect(doneSpy).toHaveBeenCalled(); + }); + + it('calls validate endpoint and sets errors if there is an error', async () => { + const addIndexingRuleSpy = jest.spyOn(SynchronizationLogic.actions, 'addIndexingRule'); + const promise = Promise.resolve({ rules: [{ valid: false, error: 'error' }] }); + http.post.mockReturnValue(promise); + SynchronizationLogic.actions.initAddIndexingRule({ ...indexingRule, valueType: 'include' }); + const doneSpy = jest.spyOn( + InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps).actions, + 'doneEditing' + ); + + expect(http.post).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/indexing_rules/validate', + { + body: JSON.stringify({ + rules: [ + { + filter_type: 'file_extension', + include: 'value', + }, + ], + }), + } + ); + await promise; + expect(addIndexingRuleSpy).not.toHaveBeenCalled(); + expect(doneSpy).toHaveBeenCalled(); + }); + + it('flashes an error if the API call fails', async () => { + const addIndexingRuleSpy = jest.spyOn(SynchronizationLogic.actions, 'addIndexingRule'); + const promise = Promise.reject('error'); + http.post.mockReturnValue(promise); + const doneSpy = jest.spyOn( + InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps).actions, + 'doneEditing' + ); + SynchronizationLogic.actions.initAddIndexingRule(indexingRule); + + expect(http.post).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/indexing_rules/validate', + { + body: JSON.stringify({ + rules: [ + { + filter_type: 'file_extension', + exclude: 'value', + }, + ], + }), + } + ); + await nextTick(); + expect(addIndexingRuleSpy).not.toHaveBeenCalled(); + expect(doneSpy).not.toHaveBeenCalled(); + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + + describe('initSetIndexingRule', () => { + const indexingRule: EditableIndexingRule = { + filterType: 'file_extension', + valueType: 'exclude', + value: 'value', + id: 1, + }; + + it('calls validate endpoint and continues if no errors happen', async () => { + const setIndexingRuleSpy = jest.spyOn(SynchronizationLogic.actions, 'setIndexingRule'); + const promise = Promise.resolve({ rules: [] }); + http.post.mockReturnValue(promise); + const doneSpy = jest.spyOn( + InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps).actions, + 'doneEditing' + ); + SynchronizationLogic.actions.initSetIndexingRule(indexingRule); + + expect(http.post).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/indexing_rules/validate', + { + body: JSON.stringify({ + rules: [ + { + filter_type: 'file_extension', + exclude: 'value', + }, + ], + }), + } + ); + await promise; + expect(setIndexingRuleSpy).toHaveBeenCalledWith(indexingRule); + expect(doneSpy).toHaveBeenCalled(); + }); + + it('calls validate endpoint and sets errors if there is an error', async () => { + const setIndexingRuleSpy = jest.spyOn(SynchronizationLogic.actions, 'setIndexingRule'); + const promise = Promise.resolve({ rules: [{ valid: false, error: 'error' }] }); + http.post.mockReturnValue(promise); + const doneSpy = jest.spyOn( + InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps).actions, + 'doneEditing' + ); + SynchronizationLogic.actions.initSetIndexingRule({ ...indexingRule, valueType: 'include' }); + + expect(http.post).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/indexing_rules/validate', + { + body: JSON.stringify({ + rules: [ + { + filter_type: 'file_extension', + include: 'value', + }, + ], + }), + } + ); + await promise; + expect(setIndexingRuleSpy).not.toHaveBeenCalled(); + expect(doneSpy).toHaveBeenCalled(); + }); + it('flashes an error if the API call fails', async () => { + const setIndexingRuleSpy = jest.spyOn(SynchronizationLogic.actions, 'setIndexingRule'); + const promise = Promise.reject('error'); + http.post.mockReturnValue(promise); + const doneSpy = jest.spyOn( + InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps).actions, + 'doneEditing' + ); + SynchronizationLogic.actions.initSetIndexingRule(indexingRule); + + expect(http.post).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/indexing_rules/validate', + { + body: JSON.stringify({ + rules: [ + { + filter_type: 'file_extension', + exclude: 'value', + }, + ], + }), + } + ); + await nextTick(); + expect(setIndexingRuleSpy).not.toHaveBeenCalled(); + expect(doneSpy).not.toHaveBeenCalled(); + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + describe('updateSyncEnabled', () => { it('calls updateServerSettings method', async () => { const updateServerSettingsSpy = jest.spyOn( @@ -225,13 +569,13 @@ describe('SynchronizationLogic', () => { }); }); - describe('updateObjectsAndAssetsSettings', () => { + describe('updateAssetsAndObjectsSettings', () => { it('calls updateServerSettings method', async () => { const updateServerSettingsSpy = jest.spyOn( SynchronizationLogic.actions, 'updateServerSettings' ); - SynchronizationLogic.actions.updateObjectsAndAssetsSettings(); + SynchronizationLogic.actions.updateAssetsAndObjectsSettings(); expect(updateServerSettingsSpy).toHaveBeenCalledWith({ content_source: { @@ -240,6 +584,20 @@ describe('SynchronizationLogic', () => { content_extraction: { enabled: true }, thumbnails: { enabled: true }, }, + rules: [ + { + filter_type: 'object_type', + include: 'value', + }, + { + filter_type: 'path_template', + exclude: 'value', + }, + { + filter_type: 'file_extension', + include: 'value', + }, + ], }, }, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts index 2f4fdca44d441..547ff1e849734 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts @@ -14,6 +14,11 @@ export type TabId = 'source_sync_frequency' | 'blocked_time_windows'; import { flashAPIErrors, flashSuccessToast } from '../../../../../shared/flash_messages'; import { HttpLogic } from '../../../../../shared/http'; import { KibanaLogic } from '../../../../../shared/kibana'; +import { + InlineEditableTableLogic, + InlineEditableTableProps, +} from '../../../../../shared/tables/inline_editable_table/inline_editable_table_logic'; +import { ItemWithAnID } from '../../../../../shared/tables/types'; import { AppLogic } from '../../../../app_logic'; import { SYNC_FREQUENCY_PATH, @@ -25,9 +30,11 @@ import { BlockedWindow, DayOfWeek, IndexingSchedule, + IndexingRule, ContentSourceFullData, SyncJobType, TimeUnit, + IndexingRuleInclude, } from '../../../../types'; import { SYNC_SETTINGS_UPDATED_MESSAGE } from '../../constants'; @@ -57,6 +64,7 @@ interface ServerSyncSettingsBody { permissions?: string; blocked_windows?: ServerBlockedWindow[]; }; + rules?: IndexingRule[]; }; }; } @@ -67,7 +75,7 @@ interface SynchronizationActions { addBlockedWindow(): void; removeBlockedWindow(index: number): number; updateFrequencySettings(): void; - updateObjectsAndAssetsSettings(): void; + updateAssetsAndObjectsSettings(): void; resetSyncSettings(): void; updateSyncEnabled(enabled: boolean): boolean; setThumbnailsChecked(checked: boolean): boolean; @@ -88,16 +96,26 @@ interface SynchronizationActions { setContentExtractionChecked(checked: boolean): boolean; setServerSchedule(schedule: IndexingSchedule): IndexingSchedule; updateServerSettings(body: ServerSyncSettingsBody): ServerSyncSettingsBody; + addIndexingRule(indexingRule: EditableIndexingRuleBase): EditableIndexingRuleBase; + initAddIndexingRule(rule: EditableIndexingRule): { rule: EditableIndexingRule }; + setIndexingRules(indexingRules: EditableIndexingRule[]): EditableIndexingRule[]; + setIndexingRule(indexingRule: EditableIndexingRule): EditableIndexingRule; + initSetIndexingRule(indexingRule: EditableIndexingRule): { rule: EditableIndexingRule }; + deleteIndexingRule(indexingRule: EditableIndexingRule): EditableIndexingRule; } interface SynchronizationValues { navigatingBetweenTabs: boolean; + hasUnsavedIndexingRulesChanges: boolean; hasUnsavedFrequencyChanges: boolean; - hasUnsavedObjectsAndAssetsChanges: boolean; + hasUnsavedAssetsAndObjectsChanges: boolean; + indexingRules: EditableIndexingRule[]; thumbnailsChecked: boolean; contentExtractionChecked: boolean; cachedSchedule: IndexingSchedule; schedule: IndexingSchedule; + indexingRulesForAPI: IndexingRule[]; + errors: string[]; } export const emptyBlockedWindow: BlockedWindow = { @@ -111,6 +129,26 @@ type BlockedWindowMap = { [prop in keyof BlockedWindow]: SyncJobType | DayOfWeek | 'all' | string; }; +interface EditableIndexingRuleBase { + filterType: 'object_type' | 'path_template' | 'file_extension'; + valueType: 'include' | 'exclude'; + value: string; +} + +export interface EditableIndexingRule extends EditableIndexingRuleBase { + id: number; +} + +interface IndexingRuleForAPI { + filter_type: 'object_type' | 'path_template' | 'file_extension'; + include?: string; + exclude?: string; +} + +const isIncludeRule = (rule: IndexingRule): rule is IndexingRuleInclude => { + return !!(rule as IndexingRuleInclude).include; +}; + export const SynchronizationLogic = kea< MakeLogicType >({ @@ -135,11 +173,28 @@ export const SynchronizationLogic = kea< setServerSchedule: (schedule: IndexingSchedule) => schedule, removeBlockedWindow: (index: number) => index, updateFrequencySettings: true, - updateObjectsAndAssetsSettings: true, + updateAssetsAndObjectsSettings: true, resetSyncSettings: true, addBlockedWindow: true, + addIndexingRule: (rule: EditableIndexingRuleBase) => rule, + deleteIndexingRule: (rule: EditableIndexingRule) => rule, + initAddIndexingRule: (rule: EditableIndexingRule) => ({ rule }), + initSetIndexingRule: (rule: EditableIndexingRule) => ({ rule }), + setIndexingRules: (indexingRules: EditableIndexingRule[]) => indexingRules, + setIndexingRule: (rule: EditableIndexingRule) => rule, }, reducers: ({ props }) => ({ + hasUnsavedIndexingRulesChanges: [ + false, + { + setIndexingRule: () => true, + setIndexingRules: () => true, + addIndexingRule: () => true, + deleteIndexingRule: () => true, + resetSyncSettings: () => false, + updateServerSettings: () => false, + }, + ], navigatingBetweenTabs: [ false, { @@ -228,15 +283,55 @@ export const SynchronizationLogic = kea< }, }, ], + indexingRules: [ + (props.contentSource.indexing.rules as IndexingRule[]).map((rule, index) => ({ + filterType: rule.filterType, + id: index, + valueType: isIncludeRule(rule) ? 'include' : 'exclude', + value: isIncludeRule(rule) ? rule.include : rule.exclude, + })), + { + addIndexingRule: (indexingRules, rule) => [ + ...indexingRules, + { + ...rule, + // make sure that we get a unique number, in case of multiple deletions and additions + id: indexingRules.reduce( + (prev, curr) => (curr.id >= prev ? curr.id + 1 : prev), + indexingRules.length + ), + }, + ], + deleteIndexingRule: (indexingRules, rule) => + indexingRules.filter((currentRule) => currentRule.id !== rule.id), + resetSyncSettings: () => + (props.contentSource.indexing.rules as IndexingRule[]).map((rule, index) => ({ + filterType: rule.filterType, + id: index, + valueType: isIncludeRule(rule) ? 'include' : 'exclude', + value: isIncludeRule(rule) ? rule.include : rule.exclude, + })), + setIndexingRules: (_, indexingRules) => + indexingRules.map((val, index) => ({ ...val, id: index })), + setIndexingRule: (state, rule) => + state.map((currentRule) => (currentRule.id === rule.id ? rule : currentRule)), + }, + ], }), selectors: ({ selectors }) => ({ - hasUnsavedObjectsAndAssetsChanges: [ + hasUnsavedAssetsAndObjectsChanges: [ () => [ selectors.thumbnailsChecked, selectors.contentExtractionChecked, + selectors.hasUnsavedIndexingRulesChanges, (_, props) => props.contentSource, ], - (thumbnailsChecked, contentExtractionChecked, contentSource) => { + ( + thumbnailsChecked, + contentExtractionChecked, + hasUnsavedIndexingRulesChanges, + contentSource + ) => { const { indexing: { features: { @@ -248,7 +343,8 @@ export const SynchronizationLogic = kea< return ( thumbnailsChecked !== thumbnailsEnabled || - contentExtractionChecked !== contentExtractionEnabled + contentExtractionChecked !== contentExtractionEnabled || + hasUnsavedIndexingRulesChanges ); }, ], @@ -256,6 +352,11 @@ export const SynchronizationLogic = kea< () => [selectors.cachedSchedule, selectors.schedule], (cachedSchedule, schedule) => !isEqual(cachedSchedule, schedule), ], + indexingRulesForAPI: [ + () => [selectors.indexingRules], + (indexingRules: EditableIndexingRule[]) => + indexingRules.map((indexingRule) => indexingRuleToApiFormat(indexingRule)), + ], }), listeners: ({ actions, values, props }) => ({ handleSelectedTabChanged: async (tabId, breakpoint) => { @@ -277,6 +378,62 @@ export const SynchronizationLogic = kea< KibanaLogic.values.navigateToUrl(path); actions.setNavigatingBetweenTabs(false); }, + initAddIndexingRule: async ({ rule }) => { + const { id: sourceId } = props.contentSource; + const route = `/internal/workplace_search/org/sources/${sourceId}/indexing_rules/validate`; + try { + const response = await HttpLogic.values.http.post<{ + rules: Array<{ + valid: boolean; + error?: string; + }>; + }>(route, { + body: JSON.stringify({ + rules: [indexingRuleToApiFormat(rule)], + }), + }); + const error = response.rules[0]?.error; + const tableLogic = InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps); + if (error) { + tableLogic.actions.setRowErrors([error]); + } else { + actions.addIndexingRule(rule); + } + tableLogic.actions.doneEditing(); + } catch (e) { + flashAPIErrors(e); + } + }, + initSetIndexingRule: async ({ rule }) => { + const { id: sourceId } = props.contentSource; + const route = `/internal/workplace_search/org/sources/${sourceId}/indexing_rules/validate`; + try { + const response = await HttpLogic.values.http.post<{ + rules: Array<{ + valid: boolean; + error?: string; + }>; + }>(route, { + body: JSON.stringify({ + rules: [indexingRuleToApiFormat(rule)], + }), + }); + const error = response.rules[0]?.error; + const tableLogic = InlineEditableTableLogic({ + instanceId: 'IndexingRulesTable', + } as InlineEditableTableProps); + if (error) { + tableLogic.actions.setRowErrors([error]); + } else { + actions.setIndexingRule(rule); + } + tableLogic.actions.doneEditing(); + } catch (e) { + flashAPIErrors(e); + } + }, updateSyncEnabled: async (enabled) => { actions.updateServerSettings({ content_source: { @@ -284,7 +441,7 @@ export const SynchronizationLogic = kea< }, }); }, - updateObjectsAndAssetsSettings: () => { + updateAssetsAndObjectsSettings: () => { actions.updateServerSettings({ content_source: { indexing: { @@ -292,6 +449,7 @@ export const SynchronizationLogic = kea< content_extraction: { enabled: values.contentExtractionChecked }, thumbnails: { enabled: values.thumbnailsChecked }, }, + rules: values.indexingRulesForAPI, }, }, }); @@ -360,3 +518,10 @@ const formatBlockedWindowsForServer = ( end, })); }; + +const indexingRuleToApiFormat = (indexingRule: EditableIndexingRule): IndexingRuleForAPI => { + const { valueType, filterType, value } = indexingRule; + return valueType === 'include' + ? { filter_type: filterType, include: value } + : { filter_type: filterType, exclude: value }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.test.tsx index cf130d2c21a57..b863c43edcb3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.test.tsx @@ -10,12 +10,12 @@ import '../../../../../__mocks__/shallow_useeffect.mock'; import { setMockValues } from '../../../../../__mocks__/kea_logic'; import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; import { shallow } from 'enzyme'; +import { AssetsAndObjects } from './assets_and_objects'; import { Frequency } from './frequency'; -import { ObjectsAndAssets } from './objects_and_assets'; import { Synchronization } from './synchronization'; import { SynchronizationRouter } from './synchronization_router'; @@ -25,9 +25,10 @@ describe('SynchronizationRouter', () => { const wrapper = shallow(); expect(wrapper.find(Synchronization)).toHaveLength(1); - expect(wrapper.find(ObjectsAndAssets)).toHaveLength(1); + expect(wrapper.find(AssetsAndObjects)).toHaveLength(1); expect(wrapper.find(Frequency)).toHaveLength(2); expect(wrapper.find(Switch)).toHaveLength(1); expect(wrapper.find(Route)).toHaveLength(4); + expect(wrapper.find(Redirect)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.tsx index ede0f293377cf..ad91dfb1bd65e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_router.tsx @@ -6,18 +6,19 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; import { SYNC_FREQUENCY_PATH, BLOCKED_TIME_WINDOWS_PATH, - OBJECTS_AND_ASSETS_PATH, + ASSETS_AND_OBJECTS_PATH, SOURCE_SYNCHRONIZATION_PATH, getSourcesPath, + OLD_OBJECTS_AND_ASSETS_PATH, } from '../../../../routes'; +import { AssetsAndObjects } from './assets_and_objects'; import { Frequency } from './frequency'; -import { ObjectsAndAssets } from './objects_and_assets'; import { Synchronization } from './synchronization'; export const SynchronizationRouter: React.FC = () => ( @@ -31,8 +32,12 @@ export const SynchronizationRouter: React.FC = () => ( - - + + + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.test.tsx index a2978c34475db..80e4e1fc06b10 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.test.tsx @@ -24,9 +24,9 @@ describe('useSynchronizationSubNav', () => { href: '/sources/1/synchronization/frequency', }, { - id: 'sourceSynchronizationObjectsAndAssets', - name: 'Objects and assets', - href: '/sources/1/synchronization/objects_and_assets', + id: 'sourceSynchronizationAssetsAndObjects', + name: 'Assets and objects', + href: '/sources/1/synchronization/assets_and_objects', }, ]); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.tsx index 2df6e9177211f..ab4f0cb3c0c09 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_sub_nav.tsx @@ -14,7 +14,7 @@ import { NAV } from '../../../../constants'; import { getContentSourcePath, SYNC_FREQUENCY_PATH, - OBJECTS_AND_ASSETS_PATH, + ASSETS_AND_OBJECTS_PATH, } from '../../../../routes'; import { SourceLogic } from '../../source_logic'; @@ -35,9 +35,9 @@ export const useSynchronizationSubNav = () => { }), }, { - id: 'sourceSynchronizationObjectsAndAssets', - name: NAV.SYNCHRONIZATION_OBJECTS_AND_ASSETS, - ...generateNavLink({ to: getContentSourcePath(OBJECTS_AND_ASSETS_PATH, id, true) }), + id: 'sourceSynchronizationAssetsAndObjects', + name: NAV.SYNCHRONIZATION_ASSETS_AND_OBJECTS, + ...generateNavLink({ to: getContentSourcePath(ASSETS_AND_OBJECTS_PATH, id, true) }), }, ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts index 92749f5129197..4dd41a7930181 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts @@ -543,21 +543,31 @@ export const SOURCE_FREQUENCY_DESCRIPTION = i18n.translate( } ); -export const SOURCE_OBJECTS_AND_ASSETS_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.sources.sourceObjectsAndAssetsDescription', +export const SOURCE_ASSETS_AND_OBJECTS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsDescription', { defaultMessage: - 'Customize the indexing rules that determine which objects and assets are synchronized from this content source to Workplace Search.', + 'Flexibly manage the documents to be synchronized and made available for search using granular controls below.', } ); -export const SOURCE_OBJECTS_AND_ASSETS_LABEL = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.sources.sourceObjectsAndAssetsLabel', +export const SOURCE_ASSETS_AND_OBJECTS_LEARN_MORE_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsLearnMoreLink', { - defaultMessage: 'Object and details to include in search results', + defaultMessage: 'Learn more about sync objects types.', } ); +export const SOURCE_ASSETS_AND_OBJECTS_ASSETS_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsAssetsLabel', + { defaultMessage: 'Assets' } +); + +export const SOURCE_ASSETS_AND_OBJECTS_OBJECTS_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsLabel', + { defaultMessage: 'Objects' } +); + export const SOURCE_SYNCHRONIZATION_TOGGLE_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationToggleLabel', { diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index 3702298e8bcae..bbcb102c6c4ed 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -44,6 +44,8 @@ import { registerOrgSourceOauthConfigurationRoute, registerOrgSourceSynchronizeRoute, registerOauthConnectorParamsRoute, + registerAccountSourceValidateIndexingRulesRoute, + registerOrgSourceValidateIndexingRulesRoute, } from './sources'; const mockConfig = { @@ -310,6 +312,45 @@ describe('sources routes', () => { }); }); + describe('POST /internal/workplace_search/account/sources/{id}/indexing_rules/validate', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/internal/workplace_search/account/sources/{id}/indexing_rules/validate', + }); + + registerAccountSourceValidateIndexingRulesRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/:id/indexing_rules/validate', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + rules: [ + { + filter_type: 'path_template', + exclude: '', + }, + ], + }, + }; + mockRouter.shouldValidate(request); + }); + }); + }); + describe('GET /internal/workplace_search/account/pre_sources/{id}', () => { let mockRouter: MockRouter; @@ -818,6 +859,45 @@ describe('sources routes', () => { }); }); + describe('POST /internal/workplace_search/org/sources/{id}/indexing_rules/validate', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/internal/workplace_search/org/sources/{id}/indexing_rules/validate', + }); + + registerOrgSourceValidateIndexingRulesRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/:id/indexing_rules/validate', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + rules: [ + { + filter_type: 'path_template', + exclude: '', + }, + ], + }, + }; + mockRouter.shouldValidate(request); + }); + }); + }); + describe('GET /internal/workplace_search/org/pre_sources/{id}', () => { let mockRouter: MockRouter; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index 12f4844461409..222288d369fdb 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -96,11 +96,32 @@ const sourceSettingsSchema = schema.object({ ), }) ), + rules: schema.maybe( + schema.arrayOf( + schema.object({ + filter_type: schema.string(), + exclude: schema.maybe(schema.string()), + include: schema.maybe(schema.string()), + }) + ) + ), }) ), }), }); +const validateRulesSchema = schema.object({ + rules: schema.maybe( + schema.arrayOf( + schema.object({ + filter_type: schema.string(), + exclude: schema.maybe(schema.string()), + include: schema.maybe(schema.string()), + }) + ) + ), +}); + // Account routes export function registerAccountSourcesRoute({ router, @@ -273,6 +294,26 @@ export function registerAccountSourceSettingsRoute({ ); } +export function registerAccountSourceValidateIndexingRulesRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.post( + { + path: '/internal/workplace_search/account/sources/{id}/indexing_rules/validate', + validate: { + body: validateRulesSchema, + params: schema.object({ + id: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/indexing_rules/validate', + }) + ); +} + export function registerAccountPreSourceRoute({ router, enterpriseSearchRequestHandler, @@ -620,6 +661,26 @@ export function registerOrgSourceSettingsRoute({ ); } +export function registerOrgSourceValidateIndexingRulesRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.post( + { + path: '/internal/workplace_search/org/sources/{id}/indexing_rules/validate', + validate: { + body: validateRulesSchema, + params: schema.object({ + id: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/indexing_rules/validate', + }) + ); +} + export function registerOrgPreSourceRoute({ router, enterpriseSearchRequestHandler, @@ -955,6 +1016,7 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerAccountSourceFederatedSummaryRoute(dependencies); registerAccountSourceReauthPrepareRoute(dependencies); registerAccountSourceSettingsRoute(dependencies); + registerAccountSourceValidateIndexingRulesRoute(dependencies); registerAccountPreSourceRoute(dependencies); registerAccountPrepareSourcesRoute(dependencies); registerAccountSourceSearchableRoute(dependencies); @@ -970,6 +1032,7 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerOrgSourceFederatedSummaryRoute(dependencies); registerOrgSourceReauthPrepareRoute(dependencies); registerOrgSourceSettingsRoute(dependencies); + registerOrgSourceValidateIndexingRulesRoute(dependencies); registerOrgPreSourceRoute(dependencies); registerOrgPrepareSourcesRoute(dependencies); registerOrgSourceSearchableRoute(dependencies); diff --git a/x-pack/plugins/fleet/.storybook/context/stubs.tsx b/x-pack/plugins/fleet/.storybook/context/stubs.tsx index f72b176bd8d7b..65485a31d376a 100644 --- a/x-pack/plugins/fleet/.storybook/context/stubs.tsx +++ b/x-pack/plugins/fleet/.storybook/context/stubs.tsx @@ -8,8 +8,10 @@ import type { FleetStartServices } from '../../public/plugin'; type Stubs = + | 'licensing' | 'storage' | 'data' + | 'fieldFormats' | 'deprecations' | 'fatalErrors' | 'navigation' @@ -19,8 +21,10 @@ type Stubs = type StubbedStartServices = Pick; export const stubbedStartServices: StubbedStartServices = { + licensing: {} as FleetStartServices['licensing'], storage: {} as FleetStartServices['storage'], data: {} as FleetStartServices['data'], + fieldFormats: {} as FleetStartServices['fieldFormats'], deprecations: {} as FleetStartServices['deprecations'], fatalErrors: {} as FleetStartServices['fatalErrors'], navigation: {} as FleetStartServices['navigation'], diff --git a/x-pack/plugins/fleet/common/constants/preconfiguration.ts b/x-pack/plugins/fleet/common/constants/preconfiguration.ts index 5689223852a32..e1dc87a3ebd06 100644 --- a/x-pack/plugins/fleet/common/constants/preconfiguration.ts +++ b/x-pack/plugins/fleet/common/constants/preconfiguration.ts @@ -7,6 +7,8 @@ import { uniqBy } from 'lodash'; +import { FLEET_ELASTIC_AGENT_PACKAGE, FLEET_SERVER_PACKAGE, FLEET_SYSTEM_PACKAGE } from '.'; + import { autoUpdatePackages, autoUpgradePoliciesPackages } from './epm'; // UUID v5 values require a namespace. We use UUID v5 for some of our preconfigured ID values. @@ -28,9 +30,18 @@ export const AUTO_UPGRADE_POLICIES_PACKAGES = autoUpgradePoliciesPackages.map((n version: PRECONFIGURATION_LATEST_KEYWORD, })); +export const FLEET_PACKAGES = [ + FLEET_SYSTEM_PACKAGE, + FLEET_ELASTIC_AGENT_PACKAGE, + FLEET_SERVER_PACKAGE, +].map((name) => ({ + name, + version: PRECONFIGURATION_LATEST_KEYWORD, +})); + // Controls whether the `Keep Policies up to date` setting is exposed to the user export const KEEP_POLICIES_UP_TO_DATE_PACKAGES = uniqBy( - [...AUTO_UPGRADE_POLICIES_PACKAGES, ...AUTO_UPDATE_PACKAGES], + [...AUTO_UPGRADE_POLICIES_PACKAGES, ...FLEET_PACKAGES, ...AUTO_UPDATE_PACKAGES], ({ name }) => name ); diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 432e72db05e8c..bc6fd8bff0da0 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -4226,9 +4226,6 @@ "ca_trusted_fingerprint": { "type": "string" }, - "api_key": { - "type": "string" - }, "config": { "type": "object" }, diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 439f56da63e5e..50a7463029f98 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -2664,8 +2664,6 @@ components: type: string ca_trusted_fingerprint: type: string - api_key: - type: string config: type: object config_yaml: diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml index a91ec2cb14e94..75823d02af865 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml @@ -20,8 +20,6 @@ properties: type: string ca_trusted_fingerprint: type: string - api_key: - type: string config: type: object config_yaml: diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index d8dec7e64a6af..6fbb423507c3b 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -75,7 +75,7 @@ export interface FullAgentPolicyOutputPermissions { }; } -export type FullAgentPolicyOutput = Pick & { +export type FullAgentPolicyOutput = Pick & { [key: string]: any; }; diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 983ee7fff3db1..64ea5665241e1 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -107,7 +107,13 @@ export type InstallablePackage = RegistryPackage | ArchivePackage; export type ArchivePackage = PackageSpecManifest & // should an uploaded package be able to specify `internal`? - Pick; + Pick; + +export interface BundledPackage { + name: string; + version: string; + buffer: Buffer; +} export type RegistryPackage = PackageSpecManifest & Partial & diff --git a/x-pack/plugins/fleet/common/types/models/output.ts b/x-pack/plugins/fleet/common/types/models/output.ts index 2ff50c0fc7bdb..ba09d41d2874f 100644 --- a/x-pack/plugins/fleet/common/types/models/output.ts +++ b/x-pack/plugins/fleet/common/types/models/output.ts @@ -18,7 +18,6 @@ export interface NewOutput { hosts?: string[]; ca_sha256?: string; ca_trusted_fingerprint?: string; - api_key?: string; config_yaml?: string; is_preconfigured?: boolean; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 29a491fe0c932..b5872b0a995a9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -216,7 +216,6 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { * and no routes defined */ export const FleetAppContext: React.FC<{ - basepath: string; startServices: FleetStartServices; config: FleetConfigType; history: AppMountParameters['history']; diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx index 9c6319a92b2ee..8946e6af0ce75 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx @@ -31,7 +31,6 @@ export const ProtectedRoute: React.FunctionComponent = ({ }; interface FleetAppProps { - basepath: string; startServices: FleetStartServices; config: FleetConfigType; history: AppMountParameters['history']; @@ -41,7 +40,6 @@ interface FleetAppProps { theme$: AppMountParameters['theme$']; } const FleetApp = ({ - basepath, startServices, config, history, @@ -52,7 +50,6 @@ const FleetApp = ({ }: FleetAppProps) => { return ( void; + updateAgentPolicy: (u: AgentPolicy | null, errorMessage?: JSX.Element) => void; isFleetServerPolicy?: boolean; agentPolicyName: string; } @@ -84,12 +84,24 @@ export const AgentPolicyCreateInlineForm: React.FunctionComponent = ({ updateAgentPolicy(resp.data.item); } } catch (e) { - updateAgentPolicy(null); + updateAgentPolicy(null, mapError(e)); } finally { setIsLoading(false); } }, [newAgentPolicy, withSysMonitoring, updateAgentPolicy]); + function mapError(e: { statusCode: number }): JSX.Element | undefined { + switch (e.statusCode) { + case 409: + return ( + + ); + } + } + return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_created_callout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_created_callout.tsx index ba3af7716d985..b73bd7251f853 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_created_callout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_created_callout.tsx @@ -8,21 +8,27 @@ import React from 'react'; import { EuiSpacer, EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; + export enum CREATE_STATUS { INITIAL = 'initial', CREATED = 'created', FAILED = 'failed', } +export interface AgentPolicyCreateState { + status: CREATE_STATUS; + errorMessage?: JSX.Element; +} + interface Props { - createStatus: CREATE_STATUS; + createState: AgentPolicyCreateState; } -export const AgentPolicyCreatedCallOut: React.FunctionComponent = ({ createStatus }) => { +export const AgentPolicyCreatedCallOut: React.FunctionComponent = ({ createState }) => { return ( <> - {createStatus === CREATE_STATUS.CREATED ? ( + {createState.status === CREATE_STATUS.CREATED ? ( = ({ crea } color="danger" iconType="cross" - /> + > + {createState.errorMessage ?? null} + )} ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx index aed6c34712012..29ea4102bc1ef 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx @@ -27,9 +27,7 @@ import { DataStreamRowActions } from './components/data_stream_row_actions'; export const DataStreamListPage: React.FunctionComponent<{}> = () => { useBreadcrumbs('data_streams'); - const { - data: { fieldFormats }, - } = useStartServices(); + const { fieldFormats } = useStartServices(); const { pagination, pageSizeOptions } = usePagination(); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 2a1c9a29f289d..2ba625ea420e3 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -514,7 +514,7 @@ export function Detail() { }); } - if (showCustomTab) { + if (canReadPackageSettings && showCustomTab) { tabs.push({ id: 'custom', name: ( diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index b0c9fac454c28..51836d9ebab3e 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -118,6 +118,25 @@ describe('', () => { jest.clearAllMocks(); }); + it('should show loading when agent policies are loading', async () => { + (useAgentEnrollmentFlyoutData as jest.Mock).mockReturnValue?.({ + agentPolicies: [], + refreshAgentPolicies: jest.fn(), + isLoadingInitialAgentPolicies: true, + }); + + await act(async () => { + testBed = await setup({ + onClose: jest.fn(), + }); + testBed.component.update(); + }); + + const { exists } = testBed; + expect(exists('agentEnrollmentFlyout')).toBe(true); + expect(exists('loadingSpinner')).toBe(true); + }); + describe('managed instructions', () => { it('uses the agent policy selection step', async () => { const { exists } = testBed; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx index 87382ac70a9bb..a8354237bbcb7 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx @@ -7,6 +7,7 @@ import React, { useState, useCallback, useEffect } from 'react'; +import type { AgentPolicyCreateState } from '../../applications/fleet/sections/agents/components'; import { AgentPolicyCreatedCallOut, CREATE_STATUS, @@ -40,7 +41,9 @@ export const SelectCreateAgentPolicy: React.FC = ({ }) => { const [showCreatePolicy, setShowCreatePolicy] = useState(agentPolicies.length === 0); - const [createStatus, setCreateStatus] = useState(CREATE_STATUS.INITIAL); + const [createState, setCreateState] = useState({ + status: CREATE_STATUS.INITIAL, + }); const [newName, setNewName] = useState(incrementPolicyName(agentPolicies, isFleetServerPolicy)); @@ -52,13 +55,13 @@ export const SelectCreateAgentPolicy: React.FC = ({ }, [agentPolicies, isFleetServerPolicy]); const onAgentPolicyCreated = useCallback( - async (policy: AgentPolicy | null) => { + async (policy: AgentPolicy | null, errorMessage?: JSX.Element) => { if (!policy) { - setCreateStatus(CREATE_STATUS.FAILED); + setCreateState({ status: CREATE_STATUS.FAILED, errorMessage }); return; } setShowCreatePolicy(false); - setCreateStatus(CREATE_STATUS.CREATED); + setCreateState({ status: CREATE_STATUS.CREATED }); if (onAgentPolicyChange) { onAgentPolicyChange(policy.id, policy!); } @@ -88,8 +91,8 @@ export const SelectCreateAgentPolicy: React.FC = ({ isFleetServerPolicy={isFleetServerPolicy} /> )} - {createStatus !== CREATE_STATUS.INITIAL && ( - + {createState.status !== CREATE_STATUS.INITIAL && ( + )} ); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx index 960230820e074..4be8eb8a03cd4 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx @@ -31,6 +31,8 @@ import { import { FLEET_SERVER_PACKAGE } from '../../constants'; import type { PackagePolicy } from '../../types'; +import { Loading } from '..'; + import { ManagedInstructions } from './managed_instructions'; import { StandaloneInstructions } from './standalone_instructions'; import { MissingFleetServerHostCallout } from './missing_fleet_server_host_callout'; @@ -64,7 +66,12 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ const [policyId, setSelectedPolicyId] = useState(agentPolicy?.id); const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false); - const { agentPolicies, refreshAgentPolicies } = useAgentEnrollmentFlyoutData(); + const { + agentPolicies, + isLoadingInitialAgentPolicies, + isLoadingAgentPolicies, + refreshAgentPolicies, + } = useAgentEnrollmentFlyoutData(); useEffect(() => { async function checkPolicyIsFleetServer() { @@ -141,7 +148,9 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ ) : undefined } > - {mode === 'managed' ? ( + {isLoadingInitialAgentPolicies ? ( + + ) : mode === 'managed' ? ( = ({ viewDataStep={viewDataStep} isFleetServerPolicySelected={isFleetServerPolicySelected} refreshAgentPolicies={refreshAgentPolicies} + isLoadingAgentPolicies={isLoadingAgentPolicies} /> ) : ( ( isFleetServerPolicySelected, settings, refreshAgentPolicies, + isLoadingAgentPolicies, }) => { const fleetStatus = useFleetStatus(); @@ -161,7 +162,10 @@ export const ManagedInstructions = React.memo( return null; } - if (fleetStatus.isReady && (isLoadingAgents || fleetServers.length > 0)) { + if ( + fleetStatus.isReady && + (isLoadingAgents || isLoadingAgentPolicies || fleetServers.length > 0) + ) { return ( <> diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts index d66c1006c4654..0c447ad0870ff 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts @@ -31,4 +31,5 @@ export interface BaseProps { export interface InstructionProps extends BaseProps { agentPolicies: AgentPolicy[]; refreshAgentPolicies: () => void; + isLoadingAgentPolicies?: boolean; } diff --git a/x-pack/plugins/fleet/public/components/loading.tsx b/x-pack/plugins/fleet/public/components/loading.tsx index 3e6f000694dbc..5cff54cc49e88 100644 --- a/x-pack/plugins/fleet/public/components/loading.tsx +++ b/x-pack/plugins/fleet/public/components/loading.tsx @@ -12,7 +12,7 @@ import type { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/ export const Loading: React.FunctionComponent<{ size?: EuiLoadingSpinnerSize }> = ({ size }) => ( - + ); diff --git a/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout.data.test.ts b/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout.data.test.ts index a7b4137b5be29..f3f3fa5d109c0 100644 --- a/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout.data.test.ts +++ b/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout.data.test.ts @@ -23,6 +23,7 @@ describe('useAgentEnrollmentFlyoutData', () => { (useGetAgentPolicies as jest.Mock).mockReturnValue({ data: undefined, isLoading: true }); const { result } = testRenderer.renderHook(() => useAgentEnrollmentFlyoutData()); expect(result.current.agentPolicies).toEqual([]); + expect(result.current.isLoadingAgentPolicies).toBe(true); }); it('should return empty agentPolicies when http not loading and no data', () => { diff --git a/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts b/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts index d93afd9ac1349..b672e39be7a86 100644 --- a/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts +++ b/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts @@ -14,11 +14,14 @@ import { useGetAgentPolicies } from './use_request'; interface AgentEnrollmentFlyoutData { agentPolicies: AgentPolicy[]; refreshAgentPolicies: () => void; + isLoadingInitialAgentPolicies: boolean; + isLoadingAgentPolicies: boolean; } export function useAgentEnrollmentFlyoutData(): AgentEnrollmentFlyoutData { const { data: agentPoliciesData, + isInitialRequest, isLoading: isLoadingAgentPolicies, resendRequest: refreshAgentPolicies, } = useGetAgentPolicies({ @@ -34,5 +37,10 @@ export function useAgentEnrollmentFlyoutData(): AgentEnrollmentFlyoutData { return []; }, [isLoadingAgentPolicies, agentPoliciesData?.items]); - return { agentPolicies, refreshAgentPolicies }; + return { + agentPolicies, + refreshAgentPolicies, + isLoadingInitialAgentPolicies: isInitialRequest && isLoadingAgentPolicies, + isLoadingAgentPolicies, + }; } diff --git a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx index 22b58c14fb072..5952fbebcf272 100644 --- a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx @@ -77,7 +77,6 @@ export const createFleetTestRendererMock = (): TestRenderer => { AppWrapper: memo(({ children }) => { return ( { const cloud = cloudMock.createSetup(); return { - licensing: licensingMock.createSetup(), data: dataPluginMock.createSetupContract(), home: homePluginMock.createSetupContract(), customIntegrations: customIntegrationsMock.createSetup(), @@ -26,7 +26,9 @@ export const createSetupDepsMock = () => { export const createStartDepsMock = () => { return { + licensing: licensingMock.createStart(), data: dataPluginMock.createStartContract(), + fieldFormats: fieldFormatsServiceMock.createStartContract() as any, navigation: navigationPluginMock.createStartContract(), customIntegrations: customIntegrationsMock.createStart(), share: sharePluginMock.createStartContract(), diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 7b1bcdc64abe1..b8bda08177a7e 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -38,10 +38,11 @@ import type { DataPublicPluginSetup, DataPublicPluginStart, } from '../../../../src/plugins/data/public'; +import type { FieldFormatsStart } from '../../../../src/plugins/field_formats/public/index'; import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; -import type { LicensingPluginSetup } from '../../licensing/public'; +import type { LicensingPluginStart } from '../../licensing/public'; import type { CloudSetup } from '../../cloud/public'; import type { GlobalSearchPluginSetup } from '../../global_search/public'; import { @@ -84,7 +85,6 @@ export interface FleetStart { } export interface FleetSetupDeps { - licensing: LicensingPluginSetup; data: DataPublicPluginSetup; home?: HomePublicPluginSetup; cloud?: CloudSetup; @@ -94,7 +94,9 @@ export interface FleetSetupDeps { } export interface FleetStartDeps { + licensing: LicensingPluginStart; data: DataPublicPluginStart; + fieldFormats: FieldFormatsStart; navigation: NavigationPublicPluginStart; customIntegrations: CustomIntegrationsStart; share: SharePluginStart; @@ -132,9 +134,6 @@ export class FleetPlugin implements Plugin(appRoutesService.getCheckPermissionsPath()) ); + // Set up license service + licenseService.start(deps.licensing.license$); + registerExtension({ package: CUSTOM_LOGS_INTEGRATION_NAME, view: 'package-detail-assets', diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index 380862d36fe01..0d8627c13b3dc 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -43,6 +43,7 @@ export class ConcurrentInstallOperationError extends IngestManagerError {} export class AgentReassignmentError extends IngestManagerError {} export class PackagePolicyIneligibleForUpgradeError extends IngestManagerError {} export class PackagePolicyValidationError extends IngestManagerError {} +export class BundledPackageNotFoundError extends IngestManagerError {} export class HostedAgentPolicyRestrictionRelatedError extends IngestManagerError { constructor(message = 'Cannot perform that action') { super( diff --git a/x-pack/plugins/fleet/server/integration_tests/docker_registry_helper.ts b/x-pack/plugins/fleet/server/integration_tests/docker_registry_helper.ts index 31b0831d7f3e5..ccea6d4bd919b 100644 --- a/x-pack/plugins/fleet/server/integration_tests/docker_registry_helper.ts +++ b/x-pack/plugins/fleet/server/integration_tests/docker_registry_helper.ts @@ -24,7 +24,7 @@ export function useDockerRegistry() { let dockerProcess: ChildProcess | undefined; async function startDockerRegistryServer() { - const dockerImage = `docker.elastic.co/package-registry/distribution@sha256:de952debe048d903fc73e8a4472bb48bb95028d440cba852f21b863d47020c61`; + const dockerImage = `docker.elastic.co/package-registry/distribution@sha256:c5bf8e058727de72e561b228f4b254a14a6f880e582190d01bd5ff74318e1d0b`; const args = ['run', '--rm', '-p', `${packageRegistryPort}:8080`, dockerImage]; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 4bc06d51e7e0b..272e92fca6eae 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -14,7 +14,7 @@ import type { CoreStart, ElasticsearchServiceStart, Logger, - AsyncPlugin, + Plugin, PluginInitializerContext, SavedObjectsServiceStart, HttpServiceSetup, @@ -33,7 +33,7 @@ import { ServiceStatusLevels, } from '../../../../src/core/server'; import type { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; -import type { LicensingPluginSetup, ILicense } from '../../licensing/server'; +import type { LicensingPluginStart } from '../../licensing/server'; import type { EncryptedSavedObjectsPluginStart, EncryptedSavedObjectsPluginSetup, @@ -93,7 +93,6 @@ import { TelemetryEventsSender } from './telemetry/sender'; import { setupFleet } from './services/setup'; export interface FleetSetupDeps { - licensing: LicensingPluginSetup; security: SecurityPluginSetup; features?: FeaturesPluginSetup; encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; @@ -105,6 +104,7 @@ export interface FleetSetupDeps { export interface FleetStartDeps { data: DataPluginStart; + licensing: LicensingPluginStart; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; security: SecurityPluginStart; telemetry?: TelemetryPluginStart; @@ -175,9 +175,8 @@ export interface FleetStartContract { } export class FleetPlugin - implements AsyncPlugin + implements Plugin { - private licensing$!: Observable; private config$: Observable; private configInitialValue: FleetConfigType; private cloud?: CloudSetup; @@ -212,7 +211,6 @@ export class FleetPlugin public setup(core: CoreSetup, deps: FleetSetupDeps) { this.httpSetup = core.http; - this.licensing$ = deps.licensing.license$; this.encryptedSavedObjectsSetup = deps.encryptedSavedObjects; this.cloud = deps.cloud; this.securitySetup = deps.security; @@ -384,7 +382,6 @@ export class FleetPlugin this.telemetryEventsSender.setup(deps.telemetry); } - public start(core: CoreStart, plugins: FleetStartDeps): FleetStartContract { appContextService.start({ elasticsearch: core.elasticsearch, @@ -404,7 +401,7 @@ export class FleetPlugin logger: this.logger, telemetryEventsSender: this.telemetryEventsSender, }); - licenseService.start(this.licensing$); + licenseService.start(plugins.licensing.license$); this.telemetryEventsSender.start(plugins.telemetry, core); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap index 585eca4919eaf..eccd218b42447 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap @@ -46,7 +46,6 @@ Object { }, "outputs": Object { "data-output-id": Object { - "api_key": undefined, "ca_sha256": undefined, "hosts": Array [ "http://es-data.co:9201", @@ -54,7 +53,6 @@ Object { "type": "elasticsearch", }, "default": Object { - "api_key": undefined, "ca_sha256": undefined, "hosts": Array [ "http://127.0.0.1:9201", @@ -112,7 +110,6 @@ Object { }, "outputs": Object { "default": Object { - "api_key": undefined, "ca_sha256": undefined, "hosts": Array [ "http://127.0.0.1:9201", @@ -120,7 +117,6 @@ Object { "type": "elasticsearch", }, "monitoring-output-id": Object { - "api_key": undefined, "ca_sha256": undefined, "hosts": Array [ "http://es-monitoring.co:9201", @@ -178,7 +174,6 @@ Object { }, "outputs": Object { "data-output-id": Object { - "api_key": undefined, "ca_sha256": undefined, "hosts": Array [ "http://es-data.co:9201", @@ -186,7 +181,6 @@ Object { "type": "elasticsearch", }, "monitoring-output-id": Object { - "api_key": undefined, "ca_sha256": undefined, "hosts": Array [ "http://es-monitoring.co:9201", diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 1bc1919226248..67be0b6914537 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -145,7 +145,6 @@ describe('getFullAgentPolicy', () => { type: 'elasticsearch', hosts: ['http://127.0.0.1:9201'], ca_sha256: undefined, - api_key: undefined, }, }, inputs: [], @@ -178,7 +177,6 @@ describe('getFullAgentPolicy', () => { type: 'elasticsearch', hosts: ['http://127.0.0.1:9201'], ca_sha256: undefined, - api_key: undefined, }, }, inputs: [], @@ -213,7 +211,6 @@ describe('getFullAgentPolicy', () => { type: 'elasticsearch', hosts: ['http://127.0.0.1:9201'], ca_sha256: undefined, - api_key: undefined, }, }, inputs: [], @@ -315,12 +312,10 @@ describe('transformOutputToFullPolicyOutput', () => { is_default_monitoring: false, name: 'test output', type: 'elasticsearch', - api_key: 'apikey123', }); expect(policyOutput).toMatchInlineSnapshot(` Object { - "api_key": "apikey123", "ca_sha256": undefined, "hosts": Array [ "http://host.fr", @@ -337,7 +332,6 @@ describe('transformOutputToFullPolicyOutput', () => { is_default_monitoring: false, name: 'test output', type: 'elasticsearch', - api_key: 'apikey123', ca_trusted_fingerprint: 'fingerprint123', config_yaml: ` test: 1234 @@ -347,7 +341,6 @@ ssl.test: 123 expect(policyOutput).toMatchInlineSnapshot(` Object { - "api_key": "apikey123", "ca_sha256": undefined, "hosts": Array [ "http://host.fr", diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index 0bb6b4a33f6a3..9f522875544e1 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -34,7 +34,7 @@ export async function getFullAgentPolicy( options?: { standalone: boolean } ): Promise { let agentPolicy; - const standalone = options?.standalone; + const standalone = options?.standalone ?? false; try { agentPolicy = await agentPolicyService.get(soClient, id); @@ -171,19 +171,17 @@ export function transformOutputToFullPolicyOutput( standalone = false ): FullAgentPolicyOutput { // eslint-disable-next-line @typescript-eslint/naming-convention - const { config_yaml, type, hosts, ca_sha256, ca_trusted_fingerprint, api_key } = output; + const { config_yaml, type, hosts, ca_sha256, ca_trusted_fingerprint } = output; const configJs = config_yaml ? safeLoad(config_yaml) : {}; const newOutput: FullAgentPolicyOutput = { ...configJs, type, hosts, ca_sha256, - api_key, ...(ca_trusted_fingerprint ? { 'ssl.ca_trusted_fingerprint': ca_trusted_fingerprint } : {}), }; if (standalone) { - delete newOutput.api_key; newOutput.username = '{ES_USERNAME}'; newOutput.password = '{ES_PASSWORD}'; } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 6bd346f3aff89..1303db1a36c0e 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -16,6 +16,7 @@ import type { RegistryElasticsearch, InstallablePackage, IndexTemplate, + PackageInfo, } from '../../../../types'; import { loadFieldsFromYaml, processFields } from '../../fields/field'; import type { Field } from '../../fields/field'; @@ -31,6 +32,8 @@ import type { ESAssetMetadata } from '../meta'; import { getESAssetMetadata } from '../meta'; import { retryTransientEsErrors } from '../retry'; +import { getPackageInfo } from '../../packages'; + import { generateMappings, generateTemplateName, @@ -62,10 +65,16 @@ export const installTemplates = async ( const dataStreams = installablePackage.data_streams; if (!dataStreams) return []; + const packageInfo = await getPackageInfo({ + savedObjectsClient, + pkgName: installablePackage.name, + pkgVersion: installablePackage.version, + }); + const installedTemplatesNested = await Promise.all( dataStreams.map((dataStream) => installTemplateForDataStream({ - pkg: installablePackage, + pkg: packageInfo, esClient, logger, dataStream, @@ -177,7 +186,7 @@ export async function installTemplateForDataStream({ logger, dataStream, }: { - pkg: InstallablePackage; + pkg: PackageInfo; esClient: ElasticsearchClient; logger: Logger; dataStream: RegistryDataStream; diff --git a/x-pack/plugins/fleet/server/services/epm/fields/field.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.ts index d854a0fe8e74d..06ff858df6786 100644 --- a/x-pack/plugins/fleet/server/services/epm/fields/field.ts +++ b/x-pack/plugins/fleet/server/services/epm/fields/field.ts @@ -7,7 +7,7 @@ import { safeLoad } from 'js-yaml'; -import type { InstallablePackage } from '../../../types'; +import type { PackageInfo } from '../../../types'; import { getAssetsData } from '../packages/assets'; // This should become a copy of https://github.com/elastic/beats/blob/d9a4c9c240a9820fab15002592e5bb6db318543b/libbeat/mapping/field.go#L39 @@ -261,7 +261,7 @@ const isFields = (path: string) => { */ export const loadFieldsFromYaml = async ( - pkg: InstallablePackage, + pkg: PackageInfo, datasetName?: string ): Promise => { // Fetch all field definition files diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts index 97ee5acc71023..5c48ddb050ff2 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts @@ -91,7 +91,7 @@ function getTest( test = { method: mocks.packageClient.fetchFindLatestPackage.bind(mocks.packageClient), args: ['package name'], - spy: jest.spyOn(epmRegistry, 'fetchFindLatestPackage'), + spy: jest.spyOn(epmRegistry, 'fetchFindLatestPackageOrThrow'), spyArgs: ['package name'], spyResponse: { name: 'fetchFindLatestPackage test' }, }; diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index 0d9b8cb74b503..cac69fe4bd3b8 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -19,13 +19,13 @@ import type { InstallablePackage, Installation, RegistryPackage, - RegistrySearchResult, + BundledPackage, } from '../../types'; import { checkSuperuser } from '../../routes/security'; import { FleetUnauthorizedError } from '../../errors'; import { installTransform, isTransform } from './elasticsearch/transform/install'; -import { fetchFindLatestPackage, getRegistryPackage } from './registry'; +import { fetchFindLatestPackageOrThrow, getRegistryPackage } from './registry'; import { ensureInstalledPackage, getInstallation } from './packages'; export type InstalledAssetType = EsAssetReference; @@ -44,7 +44,7 @@ export interface PackageClient { spaceId?: string; }): Promise; - fetchFindLatestPackage(packageName: string): Promise; + fetchFindLatestPackage(packageName: string): Promise; getRegistryPackage( packageName: string, @@ -117,7 +117,7 @@ class PackageClientImpl implements PackageClient { public async fetchFindLatestPackage(packageName: string) { await this.#runPreflight(); - return fetchFindLatestPackage(packageName); + return fetchFindLatestPackageOrThrow(packageName); } public async getRegistryPackage(packageName: string, packageVersion: string) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts index c5b104696aaf4..b019729b65eb1 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { InstallablePackage } from '../../../types'; +import type { PackageInfo } from '../../../types'; import { getArchiveFilelist } from '../archive/cache'; @@ -66,7 +66,7 @@ const tests = [ test('testGetAssets', () => { for (const value of tests) { // as needed to pretend it is an InstallablePackage - const assets = getAssets(value.package as InstallablePackage, value.filter, value.dataset); + const assets = getAssets(value.package as PackageInfo, value.filter, value.dataset); expect(assets).toStrictEqual(value.expected); } }); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/assets.ts index c28c982f4ea4c..c939ce093a65c 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/assets.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { InstallablePackage } from '../../../types'; +import type { PackageInfo } from '../../../types'; import { getArchiveFilelist, getAsset } from '../archive'; import type { ArchiveEntry } from '../archive'; @@ -17,7 +17,7 @@ import type { ArchiveEntry } from '../archive'; // and different package and version structure export function getAssets( - packageInfo: InstallablePackage, + packageInfo: PackageInfo, filter = (path: string): boolean => true, datasetName?: string ): string[] { @@ -52,7 +52,7 @@ export function getAssets( // ASK: Does getAssetsData need an installSource now? // if so, should it be an Installation vs InstallablePackage or add another argument? export async function getAssetsData( - packageInfo: InstallablePackage, + packageInfo: PackageInfo, filter = (path: string): boolean => true, datasetName?: string ): Promise { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts index a32809672e1b4..d68b2f67e3295 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts @@ -14,7 +14,6 @@ import type { InstallResult } from '../../../types'; import { installPackage, isPackageVersionOrLaterInstalled } from './install'; import type { BulkInstallResponse, IBulkInstallPackageError } from './install'; -import { getBundledPackages } from './get_bundled_packages'; interface BulkInstallPackagesParams { savedObjectsClient: SavedObjectsClientContract; @@ -31,23 +30,23 @@ export async function bulkInstallPackages({ esClient, spaceId, force, - preferredSource = 'registry', }: BulkInstallPackagesParams): Promise { const logger = appContextService.getLogger(); - const bundledPackages = await getBundledPackages(); - const packagesResults = await Promise.allSettled( - packagesToInstall.map((pkg) => { - if (typeof pkg === 'string') return Registry.fetchFindLatestPackage(pkg); - return Promise.resolve(pkg); + packagesToInstall.map(async (pkg) => { + if (typeof pkg !== 'string') { + return Promise.resolve(pkg); + } + + return Registry.fetchFindLatestPackageOrThrow(pkg); }) ); logger.debug( - `kicking off bulk install of ${packagesToInstall.join( - ', ' - )} with preferred source of "${preferredSource}"` + `kicking off bulk install of ${packagesToInstall + .map((pkg) => (typeof pkg === 'string' ? pkg : pkg.name)) + .join(', ')}` ); const bulkInstallResults = await Promise.allSettled( @@ -83,61 +82,16 @@ export async function bulkInstallPackages({ }; } - let installResult: InstallResult; const pkgkey = Registry.pkgToPkgKey(pkgKeyProps); - const bundledPackage = bundledPackages.find((pkg) => pkg.name === pkgkey); - - // If preferred source is bundled packages on disk, attempt to install from disk first, then fall back to registry - if (preferredSource === 'bundled') { - if (bundledPackage) { - logger.debug( - `kicking off install of ${pkgKeyProps.name}-${pkgKeyProps.version} from bundled package on disk` - ); - installResult = await installPackage({ - savedObjectsClient, - esClient, - installSource: 'upload', - archiveBuffer: bundledPackage.buffer, - contentType: 'application/zip', - spaceId, - }); - } else { - installResult = await installPackage({ - savedObjectsClient, - esClient, - pkgkey, - installSource: 'registry', - spaceId, - force, - }); - } - } else { - // If preferred source is registry, attempt to install from registry first, then fall back to bundled packages on disk - installResult = await installPackage({ - savedObjectsClient, - esClient, - pkgkey, - installSource: 'registry', - spaceId, - force, - }); - - // If we initially errored, try to install from bundled package on disk - if (installResult.error && bundledPackage) { - logger.debug( - `kicking off install of ${pkgKeyProps.name}-${pkgKeyProps.version} from bundled package on disk` - ); - installResult = await installPackage({ - savedObjectsClient, - esClient, - installSource: 'upload', - archiveBuffer: bundledPackage.buffer, - contentType: 'application/zip', - spaceId, - }); - } - } + const installResult = await installPackage({ + savedObjectsClient, + esClient, + pkgkey, + installSource: 'registry', + spaceId, + force, + }); if (installResult.error) { return { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_bundled_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts similarity index 69% rename from x-pack/plugins/fleet/server/services/epm/packages/get_bundled_packages.ts rename to x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts index a9f9b754640cb..8ccd2006ad846 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_bundled_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts @@ -5,18 +5,15 @@ * 2.0. */ -import path from 'path'; import fs from 'fs/promises'; +import path from 'path'; +import type { BundledPackage } from '../../../types'; import { appContextService } from '../../app_context'; +import { splitPkgKey } from '../registry'; const BUNDLED_PACKAGE_DIRECTORY = path.join(__dirname, '../../../bundled_packages'); -interface BundledPackage { - name: string; - buffer: Buffer; -} - export async function getBundledPackages(): Promise { try { const dirContents = await fs.readdir(BUNDLED_PACKAGE_DIRECTORY); @@ -26,8 +23,11 @@ export async function getBundledPackages(): Promise { zipFiles.map(async (zipFile) => { const file = await fs.readFile(path.join(BUNDLED_PACKAGE_DIRECTORY, zipFile)); + const { pkgName, pkgVersion } = splitPkgKey(zipFile.replace(/\.zip$/, '')); + return { - name: zipFile.replace(/\.zip$/, ''), + name: pkgName, + version: pkgVersion, buffer: file, }; }) @@ -41,3 +41,10 @@ export async function getBundledPackages(): Promise { return []; } } + +export async function getBundledPackageByName(name: string): Promise { + const bundledPackages = await getBundledPackages(); + const bundledPackage = bundledPackages.find((pkg) => pkg.name === name); + + return bundledPackage; +} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts index 53b4d341beec2..b15c61cebd778 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts @@ -186,7 +186,7 @@ describe('When using EPM `get` services', () => { beforeEach(() => { const mockContract = createAppContextStartContractMock(); appContextService.start(mockContract); - MockRegistry.fetchFindLatestPackage.mockResolvedValue({ + MockRegistry.fetchFindLatestPackageOrUndefined.mockResolvedValue({ name: 'my-package', version: '1.0.0', } as RegistryPackage); @@ -283,8 +283,8 @@ describe('When using EPM `get` services', () => { }); describe('registry fetch errors', () => { - it('throws when a package that is not installed is not available in the registry', async () => { - MockRegistry.fetchFindLatestPackage.mockResolvedValue(undefined); + it('throws when a package that is not installed is not available in the registry and not bundled', async () => { + MockRegistry.fetchFindLatestPackageOrUndefined.mockResolvedValue(undefined); const soClient = savedObjectsClientMock.create(); soClient.get.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError()); @@ -298,7 +298,7 @@ describe('When using EPM `get` services', () => { }); it('sets the latestVersion to installed version when an installed package is not available in the registry', async () => { - MockRegistry.fetchFindLatestPackage.mockResolvedValue(undefined); + MockRegistry.fetchFindLatestPackageOrUndefined.mockResolvedValue(undefined); const soClient = savedObjectsClientMock.create(); soClient.get.mockResolvedValue({ id: 'my-package', diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index c78f107cce715..fd24b3f438319 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -106,7 +106,7 @@ export async function getPackageInfoFromRegistry(options: { const { savedObjectsClient, pkgName, pkgVersion } = options; const [savedObject, latestPackage] = await Promise.all([ getInstallationObject({ savedObjectsClient, pkgName }), - Registry.fetchFindLatestPackage(pkgName), + Registry.fetchFindLatestPackageOrThrow(pkgName), ]); // If no package version is provided, use the installed version in the response @@ -143,9 +143,10 @@ export async function getPackageInfo(options: { pkgVersion: string; }): Promise { const { savedObjectsClient, pkgName, pkgVersion } = options; + const [savedObject, latestPackage] = await Promise.all([ getInstallationObject({ savedObjectsClient, pkgName }), - Registry.fetchFindLatestPackage(pkgName, { throwIfNotFound: false }), + Registry.fetchFindLatestPackageOrUndefined(pkgName), ]); if (!savedObject && !latestPackage) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts index b74466bc6271a..1a1f1aa617f54 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts @@ -20,6 +20,7 @@ import { licenseService } from '../../license'; import { installPackage } from './install'; import * as install from './_install_package'; import * as obj from './index'; +import { getBundledPackages } from './bundled_packages'; jest.mock('../../app_context', () => { return { @@ -40,6 +41,7 @@ jest.mock('../../upgrade_sender'); jest.mock('../../license'); jest.mock('../../upgrade_sender'); jest.mock('./cleanup'); +jest.mock('./bundled_packages'); jest.mock('./_install_package', () => { return { _installPackage: jest.fn(() => Promise.resolve()), @@ -60,6 +62,8 @@ jest.mock('../archive', () => { }; }); +const mockGetBundledPackages = getBundledPackages as jest.MockedFunction; + describe('install', () => { beforeEach(() => { jest.spyOn(Registry, 'splitPkgKey').mockImplementation((pkgKey: string) => { @@ -67,14 +71,25 @@ describe('install', () => { return { pkgName, pkgVersion }; }); jest - .spyOn(Registry, 'fetchFindLatestPackage') + .spyOn(Registry, 'pkgToPkgKey') + .mockImplementation((pkg: { name: string; version: string }) => { + return `${pkg.name}-${pkg.version}`; + }); + jest + .spyOn(Registry, 'fetchFindLatestPackageOrThrow') .mockImplementation(() => Promise.resolve({ version: '1.3.0' } as any)); jest .spyOn(Registry, 'getRegistryPackage') .mockImplementation(() => Promise.resolve({ packageInfo: { license: 'basic' } } as any)); + + mockGetBundledPackages.mockReset(); }); describe('registry', () => { + beforeEach(() => { + mockGetBundledPackages.mockResolvedValue([]); + }); + it('should send telemetry on install failure, out of date', async () => { await installPackage({ spaceId: DEFAULT_SPACE_ID, @@ -187,6 +202,28 @@ describe('install', () => { status: 'failure', }); }); + + it('should install from bundled package if one exists', async () => { + mockGetBundledPackages.mockResolvedValue([ + { + name: 'test_package', + version: '1.0.0', + buffer: Buffer.from('test_package'), + }, + ]); + + await installPackage({ + spaceId: DEFAULT_SPACE_ID, + installSource: 'registry', + pkgkey: 'test_package-1.0.0', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(install._installPackage).toHaveBeenCalledWith( + expect.objectContaining({ installSource: 'upload' }) + ); + }); }); describe('upload', () => { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 9ffae48cb02d8..107b906a969c8 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -44,6 +44,7 @@ import { removeInstallation } from './remove'; import { getPackageSavedObjects } from './get'; import { _installPackage } from './_install_package'; import { removeOldAssets } from './cleanup'; +import { getBundledPackages } from './bundled_packages'; export async function isPackageInstalled(options: { savedObjectsClient: SavedObjectsClientContract; @@ -88,7 +89,7 @@ export async function ensureInstalledPackage(options: { // If pkgVersion isn't specified, find the latest package version const pkgKeyProps = pkgVersion ? { name: pkgName, version: pkgVersion } - : await Registry.fetchFindLatestPackage(pkgName); + : await Registry.fetchFindLatestPackageOrThrow(pkgName); const installedPackageResult = await isPackageVersionOrLaterInstalled({ savedObjectsClient, @@ -251,7 +252,9 @@ async function installPackageFromRegistry({ installType = getInstallType({ pkgVersion, installedPkg }); // get latest package version - const latestPackage = await Registry.fetchFindLatestPackage(pkgName, { ignoreConstraints }); + const latestPackage = await Registry.fetchFindLatestPackageOrThrow(pkgName, { + ignoreConstraints, + }); // let the user install if using the force flag or needing to reinstall or install a previous version due to failed update const installOutOfDateVersionOk = @@ -470,8 +473,31 @@ export async function installPackage(args: InstallPackageParams) { const logger = appContextService.getLogger(); const { savedObjectsClient, esClient } = args; + const bundledPackages = await getBundledPackages(); + if (args.installSource === 'registry') { const { pkgkey, force, ignoreConstraints, spaceId } = args; + + const matchingBundledPackage = bundledPackages.find( + (pkg) => Registry.pkgToPkgKey(pkg) === pkgkey + ); + + if (matchingBundledPackage) { + logger.debug( + `found bundled package for requested install of ${pkgkey} - installing from bundled package archive` + ); + + const response = installPackageByUpload({ + savedObjectsClient, + esClient, + archiveBuffer: matchingBundledPackage.buffer, + contentType: 'application/zip', + spaceId, + }); + + return response; + } + logger.debug(`kicking off install of ${pkgkey} from registry`); const response = installPackageFromRegistry({ savedObjectsClient, diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 12712905b1d36..c70b064684a96 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -21,7 +21,6 @@ import type { InstallSource, RegistryPackage, RegistrySearchResults, - RegistrySearchResult, GetCategoriesRequest, } from '../../../types'; import { @@ -35,6 +34,8 @@ import { streamToBuffer } from '../streams'; import { appContextService } from '../..'; import { PackageNotFoundError, PackageCacheError, RegistryResponseError } from '../../../errors'; +import { getBundledPackageByName } from '../packages/bundled_packages'; + import { fetchUrl, getResponse, getResponseStream } from './requests'; import { getRegistryUrl } from './registry_url'; @@ -65,20 +66,16 @@ export async function fetchList(params?: SearchParams): Promise; -export async function fetchFindLatestPackage( - packageName: string, - options: { ignoreConstraints?: boolean; throwIfNotFound: false } -): Promise; -export async function fetchFindLatestPackage( +interface FetchFindLatestPackageOptions { + ignoreConstraints?: boolean; +} + +async function _fetchFindLatestPackage( packageName: string, - options?: { ignoreConstraints?: boolean; throwIfNotFound?: boolean } -): Promise { - const { ignoreConstraints = false, throwIfNotFound = true } = options ?? {}; + options?: FetchFindLatestPackageOptions +) { + const { ignoreConstraints = false } = options ?? {}; + const registryUrl = getRegistryUrl(); const url = new URL(`${registryUrl}/search?package=${packageName}&experimental=true`); @@ -86,12 +83,55 @@ export async function fetchFindLatestPackage( setKibanaVersion(url); } - const res = await fetchUrl(url.toString()); - const searchResults = JSON.parse(res); - if (searchResults.length) { + const res = await fetchUrl(url.toString(), 1); + const searchResults: RegistryPackage[] = JSON.parse(res); + + return searchResults; +} + +export async function fetchFindLatestPackageOrThrow( + packageName: string, + options?: FetchFindLatestPackageOptions +) { + try { + const searchResults = await _fetchFindLatestPackage(packageName, options); + + if (!searchResults.length) { + throw new PackageNotFoundError(`[${packageName}] package not found in registry`); + } + + return searchResults[0]; + } catch (error) { + const bundledPackage = await getBundledPackageByName(packageName); + + if (!bundledPackage) { + throw error; + } + + return bundledPackage; + } +} + +export async function fetchFindLatestPackageOrUndefined( + packageName: string, + options?: FetchFindLatestPackageOptions +) { + try { + const searchResults = await _fetchFindLatestPackage(packageName, options); + + if (!searchResults.length) { + return undefined; + } + return searchResults[0]; - } else if (throwIfNotFound) { - throw new PackageNotFoundError(`[${packageName}] package not found in registry`); + } catch (error) { + const bundledPackage = await getBundledPackageByName(packageName); + + if (!bundledPackage) { + return undefined; + } + + return bundledPackage; } } diff --git a/x-pack/plugins/fleet/server/services/epm/registry/requests.ts b/x-pack/plugins/fleet/server/services/epm/registry/requests.ts index f5cabadc5c60d..47084b601a27e 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/requests.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/requests.ts @@ -34,13 +34,13 @@ async function registryFetch(url: string) { } } -export async function getResponse(url: string): Promise { +export async function getResponse(url: string, retries: number = 5): Promise { try { // we only want to retry certain failures like network issues // the rest should only try the one time then fail as they do now const response = await pRetry(() => registryFetch(url), { factor: 2, - retries: 5, + retries, onFailedAttempt: (error) => { // we only want to retry certain types of errors, like `ECONNREFUSED` and other operational errors // and let the others through without retrying @@ -67,13 +67,16 @@ export async function getResponse(url: string): Promise { } } -export async function getResponseStream(url: string): Promise { - const res = await getResponse(url); +export async function getResponseStream( + url: string, + retries?: number +): Promise { + const res = await getResponse(url, retries); return res.body; } -export async function fetchUrl(url: string): Promise { - return getResponseStream(url).then(streamToString); +export async function fetchUrl(url: string, retries?: number): Promise { + return getResponseStream(url, retries).then(streamToString); } // node-fetch throws a FetchError for those types of errors and diff --git a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts index 7ccfeb4fe7641..402b65334121a 100644 --- a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts +++ b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts @@ -7,11 +7,9 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; -import type { Installation, PackageInfo } from '../../common'; - -import { shouldUpgradePolicies, upgradeManagedPackagePolicies } from './managed_package_policies'; +import { upgradeManagedPackagePolicies } from './managed_package_policies'; import { packagePolicyService } from './package_policy'; -import { getPackageInfo, getInstallation } from './epm/packages'; +import { getInstallations } from './epm/packages'; jest.mock('./package_policy'); jest.mock('./epm/packages'); @@ -20,7 +18,7 @@ jest.mock('./app_context', () => { ...jest.requireActual('./app_context'), appContextService: { getLogger: jest.fn(() => { - return { error: jest.fn() }; + return { error: jest.fn(), debug: jest.fn() }; }), }, }; @@ -28,21 +26,30 @@ jest.mock('./app_context', () => { describe('upgradeManagedPackagePolicies', () => { afterEach(() => { - (packagePolicyService.get as jest.Mock).mockReset(); - (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockReset(); - (getPackageInfo as jest.Mock).mockReset(); - (getInstallation as jest.Mock).mockReset(); - (packagePolicyService.upgrade as jest.Mock).mockReset(); + jest.clearAllMocks(); }); it('should not upgrade policies for non-managed package', async () => { const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const soClient = savedObjectsClientMock.create(); - (packagePolicyService.get as jest.Mock).mockImplementationOnce( - (savedObjectsClient: any, id: string) => { - return { - id, + (getInstallations as jest.Mock).mockResolvedValueOnce({ + saved_objects: [], + }); + + await upgradeManagedPackagePolicies(soClient, esClient); + + expect(packagePolicyService.upgrade).not.toBeCalled(); + }); + + it('should upgrade policies for managed package', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const soClient = savedObjectsClientMock.create(); + + (packagePolicyService.list as jest.Mock).mockResolvedValueOnce({ + items: [ + { + id: 'managed-package-id', inputs: {}, version: '', revision: 1, @@ -51,50 +58,48 @@ describe('upgradeManagedPackagePolicies', () => { created_at: '', created_by: '', package: { - name: 'non-managed-package', - title: 'Non-Managed Package', - version: '1.0.0', + name: 'managed-package', + title: 'Managed Package', + version: '0.0.1', }, - }; - } - ); - - (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockImplementationOnce( - (savedObjectsClient: any, id: string) => { - return { - name: 'non-managed-package-policy', - diff: [{ id: 'foo' }, { id: 'bar' }], - hasErrors: false, - }; - } - ); + }, + ], + }); - (getPackageInfo as jest.Mock).mockImplementationOnce( - ({ savedObjectsClient, pkgName, pkgVersion }) => ({ - name: pkgName, - version: pkgVersion, - keepPoliciesUpToDate: false, - }) - ); + (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockResolvedValueOnce({ + name: 'non-managed-package-policy', + diff: [{ id: 'foo' }, { id: 'bar' }], + hasErrors: false, + }); - (getInstallation as jest.Mock).mockResolvedValueOnce({ - id: 'test-installation', - version: '0.0.1', + (getInstallations as jest.Mock).mockResolvedValueOnce({ + saved_objects: [ + { + attributes: { + id: 'test-installation', + version: '1.0.0', + keep_policies_up_to_date: true, + }, + }, + ], }); - await upgradeManagedPackagePolicies(soClient, esClient, ['non-managed-package-id']); + const results = await upgradeManagedPackagePolicies(soClient, esClient); + expect(results).toEqual([ + { packagePolicyId: 'managed-package-id', diff: [{ id: 'foo' }, { id: 'bar' }], errors: [] }, + ]); - expect(packagePolicyService.upgrade).not.toBeCalled(); + expect(packagePolicyService.upgrade).toBeCalledWith(soClient, esClient, ['managed-package-id']); }); - it('should upgrade policies for managed package', async () => { + it('should not upgrade policy if newer than installed package version', async () => { const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const soClient = savedObjectsClientMock.create(); - (packagePolicyService.get as jest.Mock).mockImplementationOnce( - (savedObjectsClient: any, id: string) => { - return { - id, + (packagePolicyService.list as jest.Mock).mockResolvedValueOnce({ + items: [ + { + id: 'managed-package-id', inputs: {}, version: '', revision: 1, @@ -105,38 +110,28 @@ describe('upgradeManagedPackagePolicies', () => { package: { name: 'managed-package', title: 'Managed Package', - version: '0.0.1', + version: '1.0.1', }, - }; - } - ); - - (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockImplementationOnce( - (savedObjectsClient: any, id: string) => { - return { - name: 'non-managed-package-policy', - diff: [{ id: 'foo' }, { id: 'bar' }], - hasErrors: false, - }; - } - ); - - (getPackageInfo as jest.Mock).mockImplementationOnce( - ({ savedObjectsClient, pkgName, pkgVersion }) => ({ - name: pkgName, - version: pkgVersion, - keepPoliciesUpToDate: true, - }) - ); + }, + ], + }); - (getInstallation as jest.Mock).mockResolvedValueOnce({ - id: 'test-installation', - version: '1.0.0', + (getInstallations as jest.Mock).mockResolvedValueOnce({ + saved_objects: [ + { + attributes: { + id: 'test-installation', + version: '1.0.0', + keep_policies_up_to_date: true, + }, + }, + ], }); - await upgradeManagedPackagePolicies(soClient, esClient, ['managed-package-id']); + await upgradeManagedPackagePolicies(soClient, esClient); - expect(packagePolicyService.upgrade).toBeCalledWith(soClient, esClient, ['managed-package-id']); + expect(packagePolicyService.getUpgradeDryRunDiff).not.toHaveBeenCalled(); + expect(packagePolicyService.upgrade).not.toHaveBeenCalled(); }); describe('when dry run reports conflicts', () => { @@ -144,10 +139,10 @@ describe('upgradeManagedPackagePolicies', () => { const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const soClient = savedObjectsClientMock.create(); - (packagePolicyService.get as jest.Mock).mockImplementationOnce( - (savedObjectsClient: any, id: string) => { - return { - id, + (packagePolicyService.list as jest.Mock).mockResolvedValueOnce({ + items: [ + { + id: 'conflicting-package-policy', inputs: {}, version: '', revision: 1, @@ -160,39 +155,32 @@ describe('upgradeManagedPackagePolicies', () => { title: 'Conflicting Package', version: '0.0.1', }, - }; - } - ); - - (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockImplementationOnce( - (savedObjectsClient: any, id: string) => { - return { - name: 'conflicting-package-policy', - diff: [ - { id: 'foo' }, - { id: 'bar', errors: [{ key: 'some.test.value', message: 'Conflict detected' }] }, - ], - hasErrors: true, - }; - } - ); + }, + ], + }); - (getPackageInfo as jest.Mock).mockImplementationOnce( - ({ savedObjectsClient, pkgName, pkgVersion }) => ({ - name: pkgName, - version: pkgVersion, - keepPoliciesUpToDate: true, - }) - ); + (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockResolvedValueOnce({ + name: 'conflicting-package-policy', + diff: [ + { id: 'foo' }, + { id: 'bar', errors: [{ key: 'some.test.value', message: 'Conflict detected' }] }, + ], + hasErrors: true, + }); - (getInstallation as jest.Mock).mockResolvedValueOnce({ - id: 'test-installation', - version: '1.0.0', + (getInstallations as jest.Mock).mockResolvedValueOnce({ + saved_objects: [ + { + attributes: { + id: 'test-installation', + version: '1.0.0', + keep_policies_up_to_date: true, + }, + }, + ], }); - const result = await upgradeManagedPackagePolicies(soClient, esClient, [ - 'conflicting-package-policy', - ]); + const result = await upgradeManagedPackagePolicies(soClient, esClient); expect(result).toEqual([ { @@ -224,89 +212,3 @@ describe('upgradeManagedPackagePolicies', () => { }); }); }); - -describe('shouldUpgradePolicies', () => { - describe('package policy is up-to-date', () => { - describe('keep_policies_up_to_date is true', () => { - it('returns false', () => { - const packageInfo = { - version: '1.0.0', - keepPoliciesUpToDate: true, - }; - - const installedPackage = { - version: '1.0.0', - }; - - const result = shouldUpgradePolicies( - packageInfo as PackageInfo, - installedPackage as Installation - ); - - expect(result).toBe(false); - }); - }); - - describe('keep_policies_up_to_date is false', () => { - it('returns false', () => { - const packageInfo = { - version: '1.0.0', - keepPoliciesUpToDate: false, - }; - - const installedPackage = { - version: '1.0.0', - }; - - const result = shouldUpgradePolicies( - packageInfo as PackageInfo, - installedPackage as Installation - ); - - expect(result).toBe(false); - }); - }); - }); - - describe('package policy is out-of-date', () => { - describe('keep_policies_up_to_date is true', () => { - it('returns true', () => { - const packageInfo = { - version: '1.0.0', - keepPoliciesUpToDate: true, - }; - - const installedPackage = { - version: '1.1.0', - }; - - const result = shouldUpgradePolicies( - packageInfo as PackageInfo, - installedPackage as Installation - ); - - expect(result).toBe(true); - }); - }); - - describe('keep_policies_up_to_date is false', () => { - it('returns false', () => { - const packageInfo = { - version: '1.0.0', - keepPoliciesUpToDate: false, - }; - - const installedPackage = { - version: '1.1.0', - }; - - const result = shouldUpgradePolicies( - packageInfo as PackageInfo, - installedPackage as Installation - ); - - expect(result).toBe(false); - }); - }); - }); -}); diff --git a/x-pack/plugins/fleet/server/services/managed_package_policies.ts b/x-pack/plugins/fleet/server/services/managed_package_policies.ts index c702cfe96d986..2c4b326d56532 100644 --- a/x-pack/plugins/fleet/server/services/managed_package_policies.ts +++ b/x-pack/plugins/fleet/server/services/managed_package_policies.ts @@ -6,16 +6,16 @@ */ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; -import semverGte from 'semver/functions/gte'; +import semverLt from 'semver/functions/lt'; -import type { - Installation, - PackageInfo, - UpgradePackagePolicyDryRunResponseItem, -} from '../../common'; +import type { UpgradePackagePolicyDryRunResponseItem } from '../../common'; + +import { PACKAGES_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; + +import type { Installation, PackagePolicy } from '../types'; import { appContextService } from './app_context'; -import { getInstallation, getPackageInfo } from './epm/packages'; +import { getInstallations } from './epm/packages'; import { packagePolicyService } from './package_policy'; export interface UpgradeManagedPackagePoliciesResult { @@ -30,84 +30,87 @@ export interface UpgradeManagedPackagePoliciesResult { */ export const upgradeManagedPackagePolicies = async ( soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - packagePolicyIds: string[] + esClient: ElasticsearchClient ): Promise => { + appContextService + .getLogger() + .debug('Running required package policies upgrades for managed policies'); const results: UpgradeManagedPackagePoliciesResult[] = []; - for (const packagePolicyId of packagePolicyIds) { - const packagePolicy = await packagePolicyService.get(soClient, packagePolicyId); - - if (!packagePolicy || !packagePolicy.package) { - continue; - } - - const packageInfo = await getPackageInfo({ - savedObjectsClient: soClient, - pkgName: packagePolicy.package.name, - pkgVersion: packagePolicy.package.version, - }); - - const installedPackage = await getInstallation({ - savedObjectsClient: soClient, - pkgName: packagePolicy.package.name, - }); + const installedPackages = await getInstallations(soClient, { + filter: `${PACKAGES_SAVED_OBJECT_TYPE}.attributes.install_status:installed AND ${PACKAGES_SAVED_OBJECT_TYPE}.attributes.keep_policies_up_to_date:true`, + }); - if (!installedPackage) { - results.push({ - packagePolicyId, - errors: [`${packagePolicy.package.name} is not installed`], - }); - - continue; - } - - if (shouldUpgradePolicies(packageInfo, installedPackage)) { - // Since upgrades don't report diffs/errors, we need to perform a dry run first in order - // to notify the user of any granular policy upgrade errors that occur during Fleet's - // preconfiguration check - const dryRunResults = await packagePolicyService.getUpgradeDryRunDiff( - soClient, - packagePolicyId - ); - - if (dryRunResults.hasErrors) { - const errors = dryRunResults.diff - ? dryRunResults.diff?.[1].errors - : [dryRunResults.body?.message]; - - appContextService - .getLogger() - .error( - new Error( - `Error upgrading package policy ${packagePolicyId}: ${JSON.stringify(errors)}` - ) - ); - - results.push({ packagePolicyId, diff: dryRunResults.diff, errors }); - continue; - } + for (const { attributes: installedPackage } of installedPackages.saved_objects) { + const packagePolicies = await getPackagePoliciesNotMatchingVersion( + soClient, + installedPackage.name, + installedPackage.version + ); - try { - await packagePolicyService.upgrade(soClient, esClient, [packagePolicyId]); - results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [] }); - } catch (error) { - results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [error] }); + for (const packagePolicy of packagePolicies) { + if (isPolicyVersionLtInstalledVersion(packagePolicy, installedPackage)) { + await upgradePackagePolicy(soClient, esClient, packagePolicy.id, results); } } } - return results; }; -export function shouldUpgradePolicies( - packageInfo: PackageInfo, +async function getPackagePoliciesNotMatchingVersion( + soClient: SavedObjectsClientContract, + pkgName: string, + pkgVersion: string +): Promise { + return ( + await packagePolicyService.list(soClient, { + page: 1, + perPage: 1000, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${pkgName} AND NOT ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.version:${pkgVersion}`, + }) + ).items; +} + +function isPolicyVersionLtInstalledVersion( + packagePolicy: PackagePolicy, installedPackage: Installation ): boolean { - const isPolicyVersionGteInstalledVersion = semverGte( - packageInfo.version, - installedPackage.version + return ( + packagePolicy.package !== undefined && + semverLt(packagePolicy.package.version, installedPackage.version) ); +} + +async function upgradePackagePolicy( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + packagePolicyId: string, + results: UpgradeManagedPackagePoliciesResult[] +) { + // Since upgrades don't report diffs/errors, we need to perform a dry run first in order + // to notify the user of any granular policy upgrade errors that occur during Fleet's + // preconfiguration check + const dryRunResults = await packagePolicyService.getUpgradeDryRunDiff(soClient, packagePolicyId); + + if (dryRunResults.hasErrors) { + const errors = dryRunResults.diff + ? dryRunResults.diff?.[1].errors + : [dryRunResults.body?.message]; + + appContextService + .getLogger() + .error( + new Error(`Error upgrading package policy ${packagePolicyId}: ${JSON.stringify(errors)}`) + ); - return !isPolicyVersionGteInstalledVersion && !!packageInfo.keepPoliciesUpToDate; + results.push({ packagePolicyId, diff: dryRunResults.diff, errors }); + return; + } + + try { + await packagePolicyService.upgrade(soClient, esClient, [packagePolicyId]); + results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [] }); + } catch (error) { + results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [error] }); + } } diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 641136b89fb30..13a0f452fe9f5 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -41,6 +41,7 @@ import type { ListResult, UpgradePackagePolicyDryRunResponseItem, RegistryDataStream, + InstallablePackage, } from '../../common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; import { @@ -55,7 +56,6 @@ import type { UpdatePackagePolicy, PackagePolicy, PackagePolicySOAttributes, - RegistryPackage, DryRunPackagePolicy, } from '../types'; import type { ExternalCallback } from '..'; @@ -71,6 +71,7 @@ import { appContextService } from '.'; import { removeOldAssets } from './epm/packages/cleanup'; import type { PackageUpdateEvent, UpdateEventType } from './upgrade_sender'; import { sendTelemetryEvents } from './upgrade_sender'; +import { getArchivePackage } from './epm/archive'; export type InputsOverride = Partial & { vars?: Array; @@ -134,7 +135,8 @@ class PackagePolicyService { pkgVersion: packagePolicy.package.version, }); - let pkgInfo; + let pkgInfo: PackageInfo; + if (options?.skipEnsureInstalled) pkgInfo = await pkgInfoPromise; else { const [, packageInfo] = await Promise.all([ @@ -162,16 +164,21 @@ class PackagePolicyService { } validatePackagePolicyOrThrow(packagePolicy, pkgInfo); - const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version); + let installablePackage: InstallablePackage | undefined = + getArchivePackage(pkgInfo)?.packageInfo; + + if (!installablePackage) { + installablePackage = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version); + } inputs = await this._compilePackagePolicyInputs( - registryPkgInfo, + installablePackage, pkgInfo, packagePolicy.vars || {}, inputs ); - elasticsearch = registryPkgInfo.elasticsearch; + elasticsearch = installablePackage.elasticsearch; } const isoDate = new Date().toISOString(); @@ -400,14 +407,20 @@ class PackagePolicyService { validatePackagePolicyOrThrow(packagePolicy, pkgInfo); - const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version); + let installablePackage: InstallablePackage | undefined = + getArchivePackage(pkgInfo)?.packageInfo; + + if (!installablePackage) { + installablePackage = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version); + } + inputs = await this._compilePackagePolicyInputs( - registryPkgInfo, + installablePackage, pkgInfo, packagePolicy.vars || {}, inputs ); - elasticsearch = registryPkgInfo.elasticsearch; + elasticsearch = installablePackage.elasticsearch; } await soClient.update( @@ -799,14 +812,19 @@ class PackagePolicyService { } public async _compilePackagePolicyInputs( - registryPkgInfo: RegistryPackage, + installablePackage: InstallablePackage, pkgInfo: PackageInfo, vars: PackagePolicy['vars'], inputs: PackagePolicyInput[] ): Promise { const inputsPromises = inputs.map(async (input) => { - const compiledInput = await _compilePackagePolicyInput(registryPkgInfo, pkgInfo, vars, input); - const compiledStreams = await _compilePackageStreams(registryPkgInfo, pkgInfo, vars, input); + const compiledInput = await _compilePackagePolicyInput(pkgInfo, vars, input); + const compiledStreams = await _compilePackageStreams( + installablePackage, + pkgInfo, + vars, + input + ); return { ...input, compiled_input: compiledInput, @@ -917,7 +935,6 @@ function assignStreamIdToInput(packagePolicyId: string, input: NewPackagePolicyI } async function _compilePackagePolicyInput( - registryPkgInfo: RegistryPackage, pkgInfo: PackageInfo, vars: PackagePolicy['vars'], input: PackagePolicyInput @@ -942,7 +959,7 @@ async function _compilePackagePolicyInput( return undefined; } - const [pkgInputTemplate] = await getAssetsData(registryPkgInfo, (path: string) => + const [pkgInputTemplate] = await getAssetsData(pkgInfo, (path: string) => path.endsWith(`/agent/input/${packageInput.template_path!}`) ); @@ -958,13 +975,13 @@ async function _compilePackagePolicyInput( } async function _compilePackageStreams( - registryPkgInfo: RegistryPackage, + installablePackage: InstallablePackage, pkgInfo: PackageInfo, vars: PackagePolicy['vars'], input: PackagePolicyInput ) { const streamsPromises = input.streams.map((stream) => - _compilePackageStream(registryPkgInfo, pkgInfo, vars, input, stream) + _compilePackageStream(pkgInfo, vars, input, stream) ); return await Promise.all(streamsPromises); @@ -1007,7 +1024,6 @@ export function _applyIndexPrivileges( } async function _compilePackageStream( - registryPkgInfo: RegistryPackage, pkgInfo: PackageInfo, vars: PackagePolicy['vars'], input: PackagePolicyInput, @@ -1050,7 +1066,7 @@ async function _compilePackageStream( const datasetPath = packageDataStream.path; const [pkgStreamTemplate] = await getAssetsData( - registryPkgInfo, + pkgInfo, (path: string) => path.endsWith(streamFromPkg.template_path), datasetPath ); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 6d6d641381da2..518b79b9e8547 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -33,12 +33,12 @@ import { } from './preconfiguration'; import { outputService } from './output'; import { packagePolicyService } from './package_policy'; -import { getBundledPackages } from './epm/packages/get_bundled_packages'; +import { getBundledPackages } from './epm/packages/bundled_packages'; import type { InstallPackageParams } from './epm/packages/install'; jest.mock('./agent_policy_update'); jest.mock('./output'); -jest.mock('./epm/packages/get_bundled_packages'); +jest.mock('./epm/packages/bundled_packages'); jest.mock('./epm/archive'); const mockedOutputService = outputService as jest.Mocked; @@ -121,7 +121,7 @@ function getPutPreconfiguredPackagesMock() { jest.mock('./epm/registry', () => ({ ...jest.requireActual('./epm/registry'), - async fetchFindLatestPackage(packageName: string): Promise { + async fetchFindLatestPackageOrThrow(packageName: string): Promise { return { name: packageName, version: '1.0.0', @@ -164,12 +164,6 @@ jest.mock('./epm/packages/install', () => ({ // Treat the buffer value passed in tests as the package's name for simplicity const pkgName = archiveBuffer.toString('utf8'); - const installedPackage = mockInstalledPackages.get(pkgName); - - if (installedPackage) { - return installedPackage; - } - // Just install every bundled package at version '1.0.0' const packageInstallation = { name: pkgName, version: '1.0.0', title: pkgName }; mockInstalledPackages.set(pkgName, packageInstallation); @@ -743,11 +737,13 @@ describe('policy preconfiguration', () => { mockedGetBundledPackages.mockResolvedValue([ { name: 'test_package', + version: '1.0.0', buffer: Buffer.from('test_package'), }, { name: 'test_package_2', + version: '1.0.0', buffer: Buffer.from('test_package_2'), }, ]); @@ -784,6 +780,7 @@ describe('policy preconfiguration', () => { mockedGetBundledPackages.mockResolvedValue([ { name: 'test_package', + version: '1.0.0', buffer: Buffer.from('test_package'), }, ]); @@ -823,6 +820,7 @@ describe('policy preconfiguration', () => { mockedGetBundledPackages.mockResolvedValue([ { name: 'test_package', + version: '1.0.0', buffer: Buffer.from('test_package'), }, ]); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index e9d97856a926f..2e0c3c7722b13 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -22,7 +22,7 @@ import type { PackagePolicy, } from '../../common'; import { PRECONFIGURATION_LATEST_KEYWORD } from '../../common'; -import { SO_SEARCH_LIMIT, normalizeHostsForAgents } from '../../common'; +import { normalizeHostsForAgents } from '../../common'; import { PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE } from '../constants'; import { escapeSearchQueryPhrase } from './saved_object'; @@ -32,10 +32,9 @@ import { ensurePackagesCompletedInstall } from './epm/packages/install'; import { bulkInstallPackages } from './epm/packages/bulk_install_packages'; import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy'; import type { InputsOverride } from './package_policy'; -import { preconfigurePackageInputs, packagePolicyService } from './package_policy'; +import { preconfigurePackageInputs } from './package_policy'; import { appContextService } from './app_context'; import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; -import { upgradeManagedPackagePolicies } from './managed_package_policies'; import { outputService } from './output'; interface PreconfigurationResult { @@ -181,9 +180,6 @@ export async function ensurePreconfiguredPackagesAndPolicies( packagesToInstall, force: true, // Always force outdated packages to be installed if a later version isn't installed spaceId, - // During setup, we'll try to install preconfigured packages from the versions bundled with Kibana - // whenever possible - preferredSource: 'bundled', }); const fulfilledPackages = []; @@ -360,18 +356,6 @@ export async function ensurePreconfiguredPackagesAndPolicies( } } - // Handle automatic package policy upgrades for managed packages and package with - // the `keep_policies_up_to_date` setting enabled - const allPackagePolicyIds = await packagePolicyService.listIds(soClient, { - page: 1, - perPage: SO_SEARCH_LIMIT, - }); - const packagePolicyUpgradeResults = await upgradeManagedPackagePolicies( - soClient, - esClient, - allPackagePolicyIds.items - ); - return { policies: fulfilledPolicies.map((p) => p.policy @@ -388,7 +372,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( } ), packages: fulfilledPackages.map((pkg) => ('version' in pkg ? pkgToPkgKey(pkg) : pkg.name)), - nonFatalErrors: [...rejectedPackages, ...rejectedPolicies, ...packagePolicyUpgradeResults], + nonFatalErrors: [...rejectedPackages, ...rejectedPolicies], }; } diff --git a/x-pack/plugins/fleet/server/services/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts index caa2e1b3ffcee..02972648e80d2 100644 --- a/x-pack/plugins/fleet/server/services/setup.test.ts +++ b/x-pack/plugins/fleet/server/services/setup.test.ts @@ -5,29 +5,56 @@ * 2.0. */ +import type { SavedObjectsClientContract } from 'kibana/server'; +import type { ElasticsearchClientMock } from 'src/core/server/mocks'; + import { createAppContextStartContractMock, xpackMocks } from '../mocks'; +import { ensurePreconfiguredPackagesAndPolicies } from '.'; + import { appContextService } from './app_context'; +import { getInstallations } from './epm/packages'; +import { upgradeManagedPackagePolicies } from './managed_package_policies'; import { setupFleet } from './setup'; -const mockedMethodThrowsError = () => - jest.fn().mockImplementation(() => { +jest.mock('./preconfiguration'); +jest.mock('./settings'); +jest.mock('./output'); +jest.mock('./epm/packages'); +jest.mock('./managed_package_policies'); + +const mockedMethodThrowsError = (mockFn: jest.Mock) => + mockFn.mockImplementation(() => { throw new Error('SO method mocked to throw'); }); class CustomTestError extends Error {} -const mockedMethodThrowsCustom = () => - jest.fn().mockImplementation(() => { +const mockedMethodThrowsCustom = (mockFn: jest.Mock) => + mockFn.mockImplementation(() => { throw new CustomTestError('method mocked to throw'); }); describe('setupFleet', () => { let context: ReturnType; + let soClient: jest.Mocked; + let esClient: ElasticsearchClientMock; beforeEach(async () => { context = xpackMocks.createRequestHandlerContext(); // prevents `Logger not set.` and other appContext errors appContextService.start(createAppContextStartContractMock()); + soClient = context.core.savedObjects.client; + esClient = context.core.elasticsearch.client.asInternalUser; + + (getInstallations as jest.Mock).mockResolvedValueOnce({ + saved_objects: [], + }); + + (ensurePreconfiguredPackagesAndPolicies as jest.Mock).mockResolvedValue({ + nonFatalErrors: [], + }); + + (upgradeManagedPackagePolicies as jest.Mock).mockResolvedValue([]); }); afterEach(async () => { @@ -37,12 +64,7 @@ describe('setupFleet', () => { describe('should reject with any error thrown underneath', () => { it('SO client throws plain Error', async () => { - const soClient = context.core.savedObjects.client; - soClient.create = mockedMethodThrowsError(); - soClient.find = mockedMethodThrowsError(); - soClient.get = mockedMethodThrowsError(); - soClient.update = mockedMethodThrowsError(); - const esClient = context.core.elasticsearch.client.asInternalUser; + mockedMethodThrowsError(upgradeManagedPackagePolicies as jest.Mock); const setupPromise = setupFleet(soClient, esClient); await expect(setupPromise).rejects.toThrow('SO method mocked to throw'); @@ -50,16 +72,53 @@ describe('setupFleet', () => { }); it('SO client throws other error', async () => { - const soClient = context.core.savedObjects.client; - soClient.create = mockedMethodThrowsCustom(); - soClient.find = mockedMethodThrowsCustom(); - soClient.get = mockedMethodThrowsCustom(); - soClient.update = mockedMethodThrowsCustom(); - const esClient = context.core.elasticsearch.client.asInternalUser; + mockedMethodThrowsCustom(upgradeManagedPackagePolicies as jest.Mock); const setupPromise = setupFleet(soClient, esClient); await expect(setupPromise).rejects.toThrow('method mocked to throw'); await expect(setupPromise).rejects.toThrow(CustomTestError); }); }); + + it('should not return non fatal errors when upgrade result has no errors', async () => { + (upgradeManagedPackagePolicies as jest.Mock).mockResolvedValue([ + { + errors: [], + packagePolicyId: '1', + }, + ]); + + const result = await setupFleet(soClient, esClient); + + expect(result).toEqual({ + isInitialized: true, + nonFatalErrors: [], + }); + }); + + it('should return non fatal errors when upgrade result has errors', async () => { + (upgradeManagedPackagePolicies as jest.Mock).mockResolvedValue([ + { + errors: [{ key: 'key', message: 'message' }], + packagePolicyId: '1', + }, + ]); + + const result = await setupFleet(soClient, esClient); + + expect(result).toEqual({ + isInitialized: true, + nonFatalErrors: [ + { + errors: [ + { + key: 'key', + message: 'message', + }, + ], + packagePolicyId: '1', + }, + ], + }); + }); }); diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index b99b73be8588c..e7ba627f5cbdf 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -33,6 +33,7 @@ import { getInstallations, installPackage } from './epm/packages'; import { isPackageInstalled } from './epm/packages/install'; import { pkgToPkgKey } from './epm/registry'; import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; +import { upgradeManagedPackagePolicies } from './managed_package_policies'; export interface SetupStatus { isInitialized: boolean; @@ -98,14 +99,21 @@ async function createSetupSideEffects( logger.debug('Setting up initial Fleet packages'); - const { nonFatalErrors } = await ensurePreconfiguredPackagesAndPolicies( - soClient, - esClient, - policies, - packages, - defaultOutput, - DEFAULT_SPACE_ID - ); + const { nonFatalErrors: preconfiguredPackagesNonFatalErrors } = + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + policies, + packages, + defaultOutput, + DEFAULT_SPACE_ID + ); + + const packagePolicyUpgradeErrors = ( + await upgradeManagedPackagePolicies(soClient, esClient) + ).filter((result) => (result.errors ?? []).length > 0); + + const nonFatalErrors = [...preconfiguredPackagesNonFatalErrors, ...packagePolicyUpgradeErrors]; logger.debug('Cleaning up Fleet outputs'); await cleanPreconfiguredOutputs(soClient, outputsOrUndefined ?? []); diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index 9d3e912864785..91303046485d9 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -50,6 +50,7 @@ export type { EsAssetReference, KibanaAssetReference, RegistryPackage, + BundledPackage, InstallablePackage, AssetType, Installable, diff --git a/x-pack/plugins/fleet/server/types/models/output.ts b/x-pack/plugins/fleet/server/types/models/output.ts index 83119657ac209..7d9232862092e 100644 --- a/x-pack/plugins/fleet/server/types/models/output.ts +++ b/x-pack/plugins/fleet/server/types/models/output.ts @@ -13,7 +13,6 @@ const OutputBaseSchema = { name: schema.string(), type: schema.oneOf([schema.literal(outputType.Elasticsearch)]), hosts: schema.maybe(schema.arrayOf(schema.string())), - api_key: schema.maybe(schema.string()), config: schema.maybe(schema.recordOf(schema.string(), schema.any())), config_yaml: schema.maybe(schema.string()), }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts index bcce2fa2f6f69..515f5a40fd58a 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts @@ -82,7 +82,6 @@ describe('Table actions', () => { }, ], negate: false, - timeFieldName: 'a', }); }); @@ -102,7 +101,6 @@ describe('Table actions', () => { }, ], negate: true, - timeFieldName: 'a', }); }); @@ -122,7 +120,6 @@ describe('Table actions', () => { }, ], negate: false, - timeFieldName: 'a', }); }); @@ -142,7 +139,6 @@ describe('Table actions', () => { }, ], negate: true, - timeFieldName: undefined, }); }); }); @@ -173,7 +169,6 @@ describe('Table actions', () => { }, ], negate: false, - timeFieldName: 'a', }); }); @@ -202,7 +197,6 @@ describe('Table actions', () => { }, ], negate: true, - timeFieldName: undefined, }); }); @@ -274,7 +268,6 @@ describe('Table actions', () => { }, ], negate: false, - timeFieldName: undefined, }); }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts index 3c1297e864553..c37ab22002c1c 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts @@ -75,10 +75,6 @@ export const createGridFilterHandler = onClickValue: (data: LensFilterEvent['data']) => void ) => (field: string, value: unknown, colIndex: number, rowIndex: number, negate: boolean = false) => { - const col = tableRef.current.columns[colIndex]; - const isDate = col.meta?.type === 'date'; - const timeFieldName = negate && isDate ? undefined : col?.meta?.field; - const data: LensFilterEvent['data'] = { negate, data: [ @@ -89,7 +85,6 @@ export const createGridFilterHandler = table: tableRef.current, }, ], - timeFieldName, }; onClickValue(data); @@ -106,11 +101,6 @@ export const createTransposeColumnFilterHandler = ) => { if (!untransposedDataRef.current) return; const originalTable = Object.values(untransposedDataRef.current.tables)[0]; - const timeField = bucketValues.find( - ({ originalBucketColumn }) => originalBucketColumn.meta.type === 'date' - )?.originalBucketColumn; - const isDate = Boolean(timeField); - const timeFieldName = negate && isDate ? undefined : timeField?.meta?.field; const data: LensFilterEvent['data'] = { negate, @@ -126,7 +116,6 @@ export const createTransposeColumnFilterHandler = table: originalTable, }; }), - timeFieldName, }; onClickValue(data); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index 8c75ee9efcc6b..6cab22cd08ccd 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -210,7 +210,6 @@ describe('DatatableComponent', () => { }, ], negate: true, - timeFieldName: 'a', }, }); }); @@ -256,7 +255,6 @@ describe('DatatableComponent', () => { }, ], negate: false, - timeFieldName: 'b', }, }); }); @@ -341,7 +339,6 @@ describe('DatatableComponent', () => { }, ], negate: false, - timeFieldName: 'a', }, }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx index 3bb47d0ccbb81..0e4340300f8b2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx @@ -107,9 +107,9 @@ export function AddLayerButton({ title: i18n.translate('xpack.lens.configPanel.selectLayerType', { defaultMessage: 'Select layer type', }), - items: supportedLayers.map(({ type, label, icon, disabled, tooltipContent }) => { + items: supportedLayers.map(({ type, label, icon, disabled, toolTipContent }) => { return { - tooltipContent, + toolTipContent, disabled, name: label, icon: icon && , diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 5d475be7bb83f..ccd9e8aace2ab 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -204,11 +204,11 @@ describe('workspace_panel', () => { const onEvent = expressionRendererMock.mock.calls[0][0].onEvent!; - const eventData = {}; + const eventData = { myData: true, table: { rows: [], columns: [] }, column: 0 }; onEvent({ name: 'brush', data: eventData }); expect(uiActionsMock.getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.brush); - expect(trigger.exec).toHaveBeenCalledWith({ data: eventData }); + expect(trigger.exec).toHaveBeenCalledWith({ data: { ...eventData, timeFieldName: undefined } }); }); it('should push add current data table to state on data$ emitting value', async () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 3554f77047577..a26d72f1b4fc2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -68,6 +68,7 @@ import { selectSearchSessionId, } from '../../../state_management'; import type { LensInspector } from '../../../lens_inspector_service'; +import { inferTimeField } from '../../../utils'; export interface WorkspacePanelProps { visualizationMap: VisualizationMap; @@ -250,12 +251,18 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ } if (isLensBrushEvent(event)) { plugins.uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ - data: event.data, + data: { + ...event.data, + timeFieldName: inferTimeField(event.data), + }, }); } if (isLensFilterEvent(event)) { plugins.uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ - data: event.data, + data: { + ...event.data, + timeFieldName: inferTimeField(event.data), + }, }); } if (isLensEditEvent(event) && activeVisualization?.onEditAction) { diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index 5ae3cb571bdbb..c1c86367ee211 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -821,12 +821,15 @@ describe('embeddable', () => { const onEvent = expressionRenderer.mock.calls[0][0].onEvent!; - const eventData = {}; + const eventData = { myData: true, table: { rows: [], columns: [] }, column: 0 }; onEvent({ name: 'brush', data: eventData }); expect(getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.brush); expect(trigger.exec).toHaveBeenCalledWith( - expect.objectContaining({ data: eventData, embeddable: expect.anything() }) + expect.objectContaining({ + data: { ...eventData, timeFieldName: undefined }, + embeddable: expect.anything(), + }) ); }); @@ -1006,7 +1009,10 @@ describe('embeddable', () => { expressionRenderer = jest.fn(({ onEvent }) => { setTimeout(() => { - onEvent?.({ name: 'filter', data: { pings: false } }); + onEvent?.({ + name: 'filter', + data: { pings: false, table: { rows: [], columns: [] }, column: 0 }, + }); }, 10); return null; @@ -1048,7 +1054,7 @@ describe('embeddable', () => { await new Promise((resolve) => setTimeout(resolve, 20)); - expect(onFilter).toHaveBeenCalledWith({ pings: false }); + expect(onFilter).toHaveBeenCalledWith(expect.objectContaining({ pings: false })); expect(onFilter).toHaveBeenCalledTimes(1); }); @@ -1057,7 +1063,10 @@ describe('embeddable', () => { expressionRenderer = jest.fn(({ onEvent }) => { setTimeout(() => { - onEvent?.({ name: 'brush', data: { range: [0, 1] } }); + onEvent?.({ + name: 'brush', + data: { range: [0, 1], table: { rows: [], columns: [] }, column: 0 }, + }); }, 10); return null; @@ -1099,7 +1108,7 @@ describe('embeddable', () => { await new Promise((resolve) => setTimeout(resolve, 20)); - expect(onBrushEnd).toHaveBeenCalledWith({ range: [0, 1] }); + expect(onBrushEnd).toHaveBeenCalledWith(expect.objectContaining({ range: [0, 1] })); expect(onBrushEnd).toHaveBeenCalledTimes(1); }); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 712e9f9f7f476..aa0a9de248c1b 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { render, unmountComponentAtNode } from 'react-dom'; import { Filter } from '@kbn/es-query'; -import type { +import { ExecutionContextSearch, Query, TimefilterContract, @@ -70,6 +70,7 @@ import type { ErrorMessage } from '../editor_frame_service/types'; import { getLensInspectorService, LensInspector } from '../lens_inspector_service'; import { SharingSavedObjectProps } from '../types'; import type { SpacesPluginStart } from '../../../spaces/public'; +import { inferTimeField } from '../utils'; export type LensSavedObjectAttributes = Omit; @@ -529,7 +530,10 @@ export class Embeddable } if (isLensBrushEvent(event)) { this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ - data: event.data, + data: { + ...event.data, + timeFieldName: event.data.timeFieldName || inferTimeField(event.data), + }, embeddable: this, }); @@ -539,7 +543,10 @@ export class Embeddable } if (isLensFilterEvent(event)) { this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ - data: event.data, + data: { + ...event.data, + timeFieldName: event.data.timeFieldName || inferTimeField(event.data), + }, embeddable: this, }); if (this.input.onFilter) { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 6f991bb3f27c2..24f3a5a65c8d9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -52,11 +52,11 @@ import { formulaOperationName, DimensionEditorTabs, CalloutWarning, - LabelInput, DimensionEditorTab, } from './dimensions_editor_helpers'; import type { TemporaryState } from './dimensions_editor_helpers'; import { FieldInput } from './field_input'; +import { NameInput } from '../../shared_components'; const operationPanels = getOperationDisplay(); @@ -775,7 +775,7 @@ export function DimensionEditor(props: DimensionEditorProps) { {!isFullscreen && !currentFieldIsInvalid && (
{!incompleteInfo && selectedColumn && temporaryState === 'none' && ( - void; - defaultValue?: string; -}) => { - const { inputValue, handleInputChange, initialValue } = useDebouncedValue({ - onChange, - value, - defaultValue, - }); - - return ( - - { - handleInputChange(e.target.value); - }} - placeholder={initialValue} - /> - - ); -}; - export function getParamEditor( temporaryStaticValue: boolean, selectedOperationDefinition: typeof operationDefinitionMap[string] | undefined, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 404c31010278b..2c74f8468e52b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -491,13 +491,13 @@ describe('IndexPattern Data Source', () => { `); }); - it('should put all time fields used in date_histograms to the esaggs timeFields parameter', async () => { + it('should put all time fields used in date_histograms to the esaggs timeFields parameter if not ignoring global time range', async () => { const queryBaseState: DataViewBaseState = { currentIndexPatternId: '1', layers: { first: { indexPatternId: '1', - columnOrder: ['col1', 'col2', 'col3'], + columnOrder: ['col1', 'col2', 'col3', 'col4'], columns: { col1: { label: 'Count of records', @@ -526,6 +526,17 @@ describe('IndexPattern Data Source', () => { interval: 'auto', }, } as DateHistogramIndexPatternColumn, + col4: { + label: 'Date 3', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'yet_another_datefield', + params: { + interval: '2d', + ignoreTimeRange: true, + }, + } as DateHistogramIndexPatternColumn, }, }, }, @@ -1633,6 +1644,63 @@ describe('IndexPattern Data Source', () => { }); expect(indexPatternDatasource.isTimeBased(state)).toEqual(true); }); + it('should return false if date histogram exists but is detached from global time range in every layer', () => { + const state = enrichBaseState({ + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columnOrder: ['metric'], + columns: { + metric: { + label: 'Count of records2', + dataType: 'number', + isBucketed: false, + sourceField: '___records___', + operationType: 'count', + }, + }, + }, + second: { + indexPatternId: '1', + columnOrder: ['bucket1', 'bucket2', 'metric2'], + columns: { + metric2: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: '___records___', + operationType: 'count', + }, + bucket1: { + label: 'Date', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: '1d', + ignoreTimeRange: true, + }, + } as DateHistogramIndexPatternColumn, + bucket2: { + label: 'Terms', + dataType: 'string', + isBucketed: true, + operationType: 'terms', + sourceField: 'geo.src', + params: { + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + size: 10, + }, + } as TermsIndexPatternColumn, + }, + }, + }, + }); + expect(indexPatternDatasource.isTimeBased(state)).toEqual(false); + }); it('should return false if date histogram does not exist in any layer', () => { const state = enrichBaseState({ currentIndexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 2a44550af2b58..0ac77696d5987 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -48,7 +48,12 @@ import { import { getVisualDefaultsForLayer, isColumnInvalid } from './utils'; import { normalizeOperationDataType, isDraggedField } from './pure_utils'; import { LayerPanel } from './layerpanel'; -import { GenericIndexPatternColumn, getErrorMessages, insertNewColumn } from './operations'; +import { + DateHistogramIndexPatternColumn, + GenericIndexPatternColumn, + getErrorMessages, + insertNewColumn, +} from './operations'; import { IndexPatternField, IndexPatternPrivateState, @@ -70,6 +75,7 @@ import { GeoFieldWorkspacePanel } from '../editor_frame_service/editor_frame/wor import { DraggingIdentifier } from '../drag_drop'; import { getStateTimeShiftWarningMessages } from './time_shift_utils'; import { getPrecisionErrorWarningMessages } from './utils'; +import { isColumnOfType } from './operations/definitions/helpers'; export type { OperationType, GenericIndexPatternColumn } from './operations'; export { deleteColumn } from './operations'; @@ -561,7 +567,13 @@ export function getIndexPatternDatasource({ Boolean(layers) && Object.values(layers).some((layer) => { const buckets = layer.columnOrder.filter((colId) => layer.columns[colId].isBucketed); - return buckets.some((colId) => layer.columns[colId].operationType === 'date_histogram'); + return buckets.some((colId) => { + const column = layer.columns[colId]; + return ( + isColumnOfType('date_histogram', column) && + !column.params.ignoreTimeRange + ); + }); }) ); }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index 26cbd2a990978..beca7cfa4c39f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -423,6 +423,92 @@ describe('date_histogram', () => { expect(newLayer).toHaveProperty('columns.col1.params.interval', '30d'); }); + it('should allow turning off time range sync', () => { + const thirdLayer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Value of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + params: { + interval: '1h', + }, + sourceField: 'timestamp', + } as DateHistogramIndexPatternColumn, + }, + }; + + const updateLayerSpy = jest.fn(); + const instance = shallow( + + ); + instance + .find(EuiSwitch) + .at(1) + .simulate('change', { + target: { checked: false }, + }); + expect(updateLayerSpy).toHaveBeenCalled(); + const newLayer = updateLayerSpy.mock.calls[0][0]; + expect(newLayer).toHaveProperty('columns.col1.params.ignoreTimeRange', true); + }); + + it('turns off time range ignore on switching to auto interval', () => { + const thirdLayer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Value of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + params: { + interval: '1h', + ignoreTimeRange: true, + }, + sourceField: 'timestamp', + } as DateHistogramIndexPatternColumn, + }, + }; + + const updateLayerSpy = jest.fn(); + const instance = shallow( + + ); + instance + .find(EuiSwitch) + .at(0) + .simulate('change', { + target: { checked: false }, + }); + expect(updateLayerSpy).toHaveBeenCalled(); + const newLayer = updateLayerSpy.mock.calls[0][0]; + expect(newLayer).toHaveProperty('columns.col1.params.ignoreTimeRange', false); + expect(newLayer).toHaveProperty('columns.col1.params.interval', 'auto'); + }); + it('should force calendar values to 1', () => { const updateLayerSpy = jest.fn(); const instance = shallow( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index ea43766464cf5..e269778b5ad53 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -16,6 +16,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiIconTip, EuiSelect, EuiSpacer, EuiSwitch, @@ -46,6 +47,7 @@ export interface DateHistogramIndexPatternColumn extends FieldBasedIndexPatternC operationType: 'date_histogram'; params: { interval: string; + ignoreTimeRange?: boolean; }; } @@ -189,7 +191,14 @@ export const dateHistogramOperation: OperationDefinition< const value = ev.target.checked ? data.search.aggs.calculateAutoTimeExpression({ from: fromDate, to: toDate }) || '1h' : autoInterval; - updateLayer(updateColumnParam({ layer, columnId, paramName: 'interval', value })); + updateLayer( + updateColumnParam({ + layer: updateColumnParam({ layer, columnId, paramName: 'interval', value }), + columnId, + paramName: 'ignoreTimeRange', + value: false, + }) + ); } const setInterval = (newInterval: typeof interval) => { @@ -214,128 +223,176 @@ export const dateHistogramOperation: OperationDefinition< )} {currentColumn.params.interval !== autoInterval && ( - - {intervalIsRestricted ? ( - - ) : ( - <> - - - + + {intervalIsRestricted ? ( + + ) : ( + <> + + + { + const newInterval = { + ...interval, + value: e.target.value, + }; + setInterval(newInterval); + }} + step={1} + /> + + + { + const newInterval = { + ...interval, + unit: e.target.value, + }; + setInterval(newInterval); + }} + isInvalid={!isValid} + options={[ + { + value: 'ms', + text: i18n.translate( + 'xpack.lens.indexPattern.dateHistogram.milliseconds', + { + defaultMessage: 'milliseconds', + } + ), + }, + { + value: 's', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.seconds', { + defaultMessage: 'seconds', + }), + }, + { + value: 'm', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.minutes', { + defaultMessage: 'minutes', + }), + }, + { + value: 'h', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.hours', { + defaultMessage: 'hours', + }), + }, + { + value: 'd', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.days', { + defaultMessage: 'days', + }), + }, + { + value: 'w', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.week', { + defaultMessage: 'week', + }), + }, + { + value: 'M', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.month', { + defaultMessage: 'month', + }), + }, + // Quarterly intervals appear to be unsupported by esaggs + { + value: 'y', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.year', { + defaultMessage: 'year', + }), + }, + ]} + /> + + + {!isValid && ( + <> + + + {i18n.translate('xpack.lens.indexPattern.invalidInterval', { + defaultMessage: 'Invalid interval value', + })} + + + )} + + )} + + + + {i18n.translate( + 'xpack.lens.indexPattern.dateHistogram.bindToGlobalTimePicker', + { + defaultMessage: 'Bind to global time picker', } - disabled={calendarOnlyIntervals.has(interval.unit)} - isInvalid={!isValid} - onChange={(e) => { - const newInterval = { - ...interval, - value: e.target.value, - }; - setInterval(newInterval); - }} - step={1} - /> - - - { - const newInterval = { - ...interval, - unit: e.target.value, - }; - setInterval(newInterval); - }} - isInvalid={!isValid} - options={[ - { - value: 'ms', - text: i18n.translate( - 'xpack.lens.indexPattern.dateHistogram.milliseconds', - { - defaultMessage: 'milliseconds', - } - ), - }, - { - value: 's', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.seconds', { - defaultMessage: 'seconds', - }), - }, + )}{' '} + - - - {!isValid && ( - <> - - - {i18n.translate('xpack.lens.indexPattern.invalidInterval', { - defaultMessage: 'Invalid interval value', - })} - - )} - - )} - + } + disabled={indexPattern.timeFieldName === field?.name} + checked={ + indexPattern.timeFieldName === field?.name || + !currentColumn.params.ignoreTimeRange + } + onChange={() => { + updateLayer( + updateColumnParam({ + layer, + columnId, + paramName: 'ignoreTimeRange', + value: !currentColumn.params.ignoreTimeRange, + }) + ); + }} + compressed + /> + + )} ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 1743bb057ba9b..3b31844bc4aed 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -166,6 +166,7 @@ export const lastValueOperation: OperationDefinition f.type === 'date')?.name; @@ -188,7 +189,7 @@ export const lastValueOperation: OperationDefinition - isColumnOfType('date_histogram', column) + isColumnOfType('date_histogram', column) && + !column.params.ignoreTimeRange ? column.sourceField : null ) diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index 82363911040b7..6140e54b43dc7 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -14,6 +14,7 @@ export * from './coloring'; export { useDebouncedValue } from './debounced_value'; export * from './helpers'; export { LegendActionPopover } from './legend_action_popover'; +export { NameInput } from './name_input'; export { ValueLabelsSettings } from './value_labels_settings'; export { AxisTitleSettings } from './axis_title_settings'; export * from './static_header'; diff --git a/x-pack/plugins/lens/public/shared_components/name_input.tsx b/x-pack/plugins/lens/public/shared_components/name_input.tsx new file mode 100644 index 0000000000000..b59946ec42aa9 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/name_input.tsx @@ -0,0 +1,48 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { useDebouncedValue } from '../shared_components'; + +export const NameInput = ({ + value, + onChange, + defaultValue, +}: { + value: string; + onChange: (value: string) => void; + defaultValue?: string; +}) => { + const { inputValue, handleInputChange, initialValue } = useDebouncedValue({ + onChange, + value, + defaultValue, + }); + + return ( + + { + handleInputChange(e.target.value); + }} + placeholder={initialValue} + /> + + ); +}; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 483da14207516..276c31328bb05 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -752,7 +752,7 @@ export interface Visualization { label: string; icon?: IconType; disabled?: boolean; - tooltipContent?: string; + toolTipContent?: string; initialDimensions?: Array<{ groupId: string; columnId: string; diff --git a/x-pack/plugins/lens/public/utils.test.ts b/x-pack/plugins/lens/public/utils.test.ts new file mode 100644 index 0000000000000..857f30e692305 --- /dev/null +++ b/x-pack/plugins/lens/public/utils.test.ts @@ -0,0 +1,119 @@ +/* + * 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 { Datatable } from 'src/plugins/expressions/public'; +import { inferTimeField } from './utils'; + +const table: Datatable = { + type: 'datatable', + rows: [], + columns: [ + { + id: '1', + name: '', + meta: { + type: 'date', + field: 'abc', + source: 'esaggs', + sourceParams: { + type: 'date_histogram', + params: {}, + appliedTimeRange: { + from: '2021-01-01', + to: '2022-01-01', + }, + }, + }, + }, + ], +}; + +const tableWithoutAppliedTimeRange = { + ...table, + columns: [ + { + ...table.columns[0], + meta: { + ...table.columns[0].meta, + sourceParams: { + ...table.columns[0].meta.sourceParams, + appliedTimeRange: undefined, + }, + }, + }, + ], +}; + +describe('utils', () => { + describe('inferTimeField', () => { + test('infer time field for brush event', () => { + expect( + inferTimeField({ + table, + column: 0, + range: [1, 2], + }) + ).toEqual('abc'); + }); + + test('do not return time field if time range is not bound', () => { + expect( + inferTimeField({ + table: tableWithoutAppliedTimeRange, + column: 0, + range: [1, 2], + }) + ).toEqual(undefined); + }); + + test('infer time field for click event', () => { + expect( + inferTimeField({ + data: [ + { + table, + column: 0, + row: 0, + value: 1, + }, + ], + }) + ).toEqual('abc'); + }); + + test('do not return time field for negated click event', () => { + expect( + inferTimeField({ + data: [ + { + table, + column: 0, + row: 0, + value: 1, + }, + ], + negate: true, + }) + ).toEqual(undefined); + }); + + test('do not return time field for click event without bound time field', () => { + expect( + inferTimeField({ + data: [ + { + table: tableWithoutAppliedTimeRange, + column: 0, + row: 0, + value: 1, + }, + ], + }) + ).toEqual(undefined); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 921cc8fb364a2..f71f7a128934a 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -16,7 +16,14 @@ import type { import type { IUiSettingsClient } from 'kibana/public'; import type { SavedObjectReference } from 'kibana/public'; import type { Document } from './persistence/saved_object_store'; -import type { Datasource, DatasourceMap, Visualization } from './types'; +import type { + Datasource, + DatasourceMap, + LensBrushEvent, + LensFilterEvent, + Visualization, +} from './types'; +import { search } from '../../../../src/plugins/data/public'; import type { DatasourceStates, VisualizationState } from './state_management'; export function getVisualizeGeoFieldMessage(fieldType: string) { @@ -107,3 +114,24 @@ export function getRemoveOperation( // fallback to generic count check return layerCount === 1 ? 'clear' : 'remove'; } + +export function inferTimeField(context: LensBrushEvent['data'] | LensFilterEvent['data']) { + const tablesAndColumns = + 'table' in context + ? [{ table: context.table, column: context.column }] + : !context.negate + ? context.data + : // if it's a negated filter, never respect bound time field + []; + return tablesAndColumns + .map(({ table, column }) => { + const tableColumn = table.columns[column]; + const hasTimeRange = Boolean( + tableColumn && search.aggs.getDateHistogramMetaDataByDatatableColumn(tableColumn)?.timeRange + ); + if (hasTimeRange) { + return tableColumn.meta.field; + } + }) + .find(Boolean); +} diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx index 690b9e56a2470..e1885fafab5e0 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx @@ -21,8 +21,8 @@ import { getMaxValue, getMinValue, getValueFromAccessor, - GaugeIconVertical, - GaugeIconHorizontal, + VerticalBulletIcon, + HorizontalBulletIcon, } from '../../../../../../src/plugins/chart_expressions/expression_gauge/public'; import { PaletteRegistry } from '../../../../../../src/plugins/charts/public'; import type { DatasourcePublicAPI, OperationMetadata, Visualization } from '../../types'; @@ -56,14 +56,14 @@ export const isNumericDynamicMetric = (op: OperationMetadata) => export const CHART_NAMES = { horizontalBullet: { - icon: GaugeIconHorizontal, + icon: HorizontalBulletIcon, label: i18n.translate('xpack.lens.gaugeHorizontal.gaugeLabel', { defaultMessage: 'Gauge horizontal', }), groupLabel: groupLabelForGauge, }, verticalBullet: { - icon: GaugeIconVertical, + icon: VerticalBulletIcon, label: i18n.translate('xpack.lens.gaugeVertical.gaugeLabel', { defaultMessage: 'Gauge vertical', }), diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index cb80271f6842d..e1e2ba75b50c4 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -11,8 +11,9 @@ import type { Datatable } from 'src/plugins/expressions'; import { euiLightVars } from '@kbn/ui-theme'; import type { AccessorConfig, FramePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; -import { FormatFactory, LayerType, layerTypes } from '../../common'; +import { FormatFactory, LayerType } from '../../common'; import type { XYLayerConfig } from '../../common/expressions'; +import { isDataLayer, isReferenceLayer } from './visualization_helpers'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; @@ -42,7 +43,7 @@ export function getColorAssignments( const layersPerPalette: Record = {}; layers - .filter(({ layerType }) => layerType === layerTypes.DATA) + .filter((layer) => isDataLayer(layer)) .forEach((layer) => { const palette = layer.palette?.name || 'default'; if (!layersPerPalette[palette]) { @@ -100,12 +101,26 @@ export function getColorAssignments( }); } +const getReferenceLineAccessorColorConfig = (layer: XYLayerConfig) => { + return layer.accessors.map((accessor) => { + const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); + return { + columnId: accessor, + triggerIcon: 'color' as const, + color: currentYConfig?.color || defaultReferenceLineColor, + }; + }); +}; + export function getAccessorColorConfig( colorAssignments: ColorAssignments, frame: Pick, layer: XYLayerConfig, paletteService: PaletteRegistry ): AccessorConfig[] { + if (isReferenceLayer(layer)) { + return getReferenceLineAccessorColorConfig(layer); + } const layerContainsSplits = Boolean(layer.splitAccessor); const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; const totalSeriesCount = colorAssignments[currentPalette.name]?.totalSeriesCount; @@ -117,13 +132,6 @@ export function getAccessorColorConfig( triggerIcon: 'disabled', }; } - if (layer.layerType === layerTypes.REFERENCELINE) { - return { - columnId: accessor as string, - triggerIcon: 'color', - color: currentYConfig?.color || defaultReferenceLineColor, - }; - } const columnToLabel = getColumnToLabelMap(layer, frame.datasourceLayers[layer.layerId]); const rank = colorAssignments[currentPalette.name].getRank( layer, diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index bc57547bc0ee6..6bee021b36de6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -134,6 +134,10 @@ const dateHistogramData: LensMultiTable = { sourceParams: { indexPatternId: 'indexPatternId', type: 'date_histogram', + appliedTimeRange: { + from: '2020-04-01T16:14:16.246Z', + to: '2020-04-01T17:15:41.263Z', + }, params: { field: 'order_date', timeRange: { from: '2020-04-01T16:14:16.246Z', to: '2020-04-01T17:15:41.263Z' }, @@ -582,9 +586,29 @@ describe('xy_expression', () => { {...defaultProps} data={{ ...data, - dateRange: { - fromDate: new Date('2019-01-02T05:00:00.000Z'), - toDate: new Date('2019-01-03T05:00:00.000Z'), + tables: { + first: { + ...data.tables.first, + columns: data.tables.first.columns.map((c) => + c.id !== 'c' + ? c + : { + ...c, + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: 'date_histogram', + params: {}, + appliedTimeRange: { + from: '2019-01-02T05:00:00.000Z', + to: '2019-01-03T05:00:00.000Z', + }, + }, + }, + } + ), + }, }, }} args={{ @@ -612,25 +636,13 @@ describe('xy_expression', () => { }, }; - const component = shallow( - - ); + const component = shallow(); // real auto interval is 30mins = 1800000 expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` Object { - "max": 1546491600000, - "min": 1546405200000, + "max": NaN, + "min": NaN, "minInterval": 50, } `); @@ -749,14 +761,36 @@ describe('xy_expression', () => { }); describe('endzones', () => { const { args } = sampleArgs(); + const table = createSampleDatatableWithRows([ + { a: 1, b: 2, c: new Date('2021-04-22').valueOf(), d: 'Foo' }, + { a: 1, b: 2, c: new Date('2021-04-23').valueOf(), d: 'Foo' }, + { a: 1, b: 2, c: new Date('2021-04-24').valueOf(), d: 'Foo' }, + ]); const data: LensMultiTable = { type: 'lens_multitable', tables: { - first: createSampleDatatableWithRows([ - { a: 1, b: 2, c: new Date('2021-04-22').valueOf(), d: 'Foo' }, - { a: 1, b: 2, c: new Date('2021-04-23').valueOf(), d: 'Foo' }, - { a: 1, b: 2, c: new Date('2021-04-24').valueOf(), d: 'Foo' }, - ]), + first: { + ...table, + columns: table.columns.map((c) => + c.id !== 'c' + ? c + : { + ...c, + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: 'date_histogram', + params: {}, + appliedTimeRange: { + from: '2021-04-22T12:00:00.000Z', + to: '2021-04-24T12:00:00.000Z', + }, + }, + }, + } + ), + }, }, dateRange: { // first and last bucket are partial @@ -1187,7 +1221,6 @@ describe('xy_expression', () => { column: 0, table: dateHistogramData.tables.timeLayer, range: [1585757732783, 1585758880838], - timeFieldName: 'order_date', }); }); @@ -1267,7 +1300,6 @@ describe('xy_expression', () => { column: 0, table: numberHistogramData.tables.numberLayer, range: [5, 8], - timeFieldName: undefined, }); }); @@ -1398,7 +1430,6 @@ describe('xy_expression', () => { value: 1585758120000, }, ], - timeFieldName: 'order_date', }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 6bfba926ff85b..ea0e336ff2f08 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -51,7 +51,6 @@ import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; import type { ILensInterpreterRenderHandlers, LensFilterEvent, LensBrushEvent } from '../types'; import type { LensMultiTable, FormatFactory } from '../../common'; -import { layerTypes } from '../../common'; import type { LayerArgs, SeriesType, XYChartProps } from '../../common/expressions'; import { visualizationTypes } from './types'; import { VisualizationContainer } from '../visualization_container'; @@ -76,6 +75,7 @@ import { ReferenceLineAnnotations, } from './expression_reference_lines'; import { computeOverallDataDomain } from './reference_line_helpers'; +import { isDataLayer, isReferenceLayer } from './visualization_helpers'; declare global { interface Window { @@ -106,7 +106,7 @@ export type XYChartRenderProps = XYChartProps & { export function calculateMinInterval({ args: { layers }, data }: XYChartProps) { const filteredLayers = getFilteredLayers(layers, data); if (filteredLayers.length === 0) return; - const isTimeViz = data.dateRange && filteredLayers.every((l) => l.xScaleType === 'time'); + const isTimeViz = filteredLayers.every((l) => l.xScaleType === 'time'); const xColumn = data.tables[filteredLayers[0].layerId].columns.find( (column) => column.id === filteredLayers[0].xAccessor ); @@ -263,9 +263,6 @@ export function XYChart({ const icon: IconType = layers.length > 0 ? getIconForSeriesType(layers[0].seriesType) : 'bar'; return ; } - const referenceLineLayers = layers.filter( - (layer) => layer.layerType === layerTypes.REFERENCELINE - ); // use formatting hint of first x axis column to format ticks const xAxisColumn = data.tables[filteredLayers[0].layerId].columns.find( @@ -318,7 +315,7 @@ export function XYChart({ filteredBarLayers.some((layer) => layer.accessors.length > 1) || filteredBarLayers.some((layer) => layer.splitAccessor); - const isTimeViz = Boolean(data.dateRange && filteredLayers.every((l) => l.xScaleType === 'time')); + const isTimeViz = Boolean(filteredLayers.every((l) => l.xScaleType === 'time')); const isHistogramViz = filteredLayers.every((l) => l.isHistogram); const { baseDomain: rawXDomain, extendedDomain: xDomain } = getXDomain( @@ -333,7 +330,6 @@ export function XYChart({ left: yAxesConfiguration.find(({ groupId }) => groupId === 'left'), right: yAxesConfiguration.find(({ groupId }) => groupId === 'right'), }; - const referenceLinePaddings = getReferenceLineRequiredPaddings(referenceLineLayers, yAxesMap); const getYAxesTitles = ( axisSeries: Array<{ layer: string; accessor: string }>, @@ -351,6 +347,9 @@ export function XYChart({ ); }; + const referenceLineLayers = layers.filter((layer) => isReferenceLayer(layer)); + const referenceLinePaddings = getReferenceLineRequiredPaddings(referenceLineLayers, yAxesMap); + const getYAxesStyle = (groupId: 'left' | 'right') => { const tickVisible = groupId === 'right' @@ -513,10 +512,6 @@ export function XYChart({ value: pointValue, }); } - const currentColumnMeta = table.columns.find((el) => el.id === layer.xAccessor)?.meta; - const xAxisFieldName = currentColumnMeta?.field; - const isDateField = currentColumnMeta?.type === 'date'; - const context: LensFilterEvent['data'] = { data: points.map((point) => ({ row: point.row, @@ -524,7 +519,6 @@ export function XYChart({ value: point.value, table, })), - timeFieldName: xDomain && isDateField ? xAxisFieldName : undefined, }; onClickValue(context); }; @@ -542,13 +536,10 @@ export function XYChart({ const xAxisColumnIndex = table.columns.findIndex((el) => el.id === filteredLayers[0].xAccessor); - const timeFieldName = isTimeViz ? table.columns[xAxisColumnIndex]?.meta?.field : undefined; - const context: LensBrushEvent['data'] = { range: [min, max], table, column: xAxisColumnIndex, - timeFieldName, }; onSelectRange(context); }; @@ -987,9 +978,10 @@ export function XYChart({ } function getFilteredLayers(layers: LayerArgs[], data: LensMultiTable) { - return layers.filter(({ layerId, xAccessor, accessors, splitAccessor, layerType }) => { + return layers.filter((layer) => { + const { layerId, xAccessor, accessors, splitAccessor } = layer; return ( - layerType === layerTypes.DATA && + isDataLayer(layer) && !( !accessors.length || !data.tables[layerId] || diff --git a/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx b/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx index 7408987261b41..a20f70ad6f4eb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx @@ -15,7 +15,7 @@ import type { FieldFormat } from 'src/plugins/field_formats/common'; import { euiLightVars } from '@kbn/ui-theme'; import type { LayerArgs, YConfig } from '../../common/expressions'; import type { LensMultiTable } from '../../common/types'; -import { hasIcon } from './xy_config_panel/reference_line_panel'; +import { hasIcon } from './xy_config_panel/shared/icon_select'; export const REFERENCE_LINE_MARKER_SIZE = 20; diff --git a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx index 127bf02b81f89..42e46b65f5c1f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx @@ -5,15 +5,23 @@ * 2.0. */ -import { partition } from 'lodash'; +import { groupBy, partition } from 'lodash'; +import { i18n } from '@kbn/i18n'; import { layerTypes } from '../../common'; import type { XYLayerConfig, YConfig } from '../../common/expressions'; import { Datatable } from '../../../../../src/plugins/expressions/public'; -import type { DatasourcePublicAPI, FramePublicAPI } from '../types'; +import type { AccessorConfig, DatasourcePublicAPI, FramePublicAPI, Visualization } from '../types'; import { groupAxesByType } from './axes_configuration'; -import { isPercentageSeries, isStackedChart } from './state_helpers'; +import { isHorizontalChart, isPercentageSeries, isStackedChart } from './state_helpers'; import type { XYState } from './types'; -import { checkScaleOperation } from './visualization_helpers'; +import { + checkScaleOperation, + getAxisName, + isDataLayer, + isNumericMetric, +} from './visualization_helpers'; +import { generateId } from '../id_generator'; +import { LensIconChartBarReferenceLine } from '../assets/chart_bar_reference_line'; export interface ReferenceLineBase { label: 'x' | 'yRight' | 'yLeft'; @@ -33,8 +41,8 @@ export function getGroupsToShow layerType === layerTypes.DATA + const dataLayers = state.layers.filter(({ layerType = layerTypes.DATA }) => + isDataLayer({ layerType }) ); const groupsAvailable = getGroupsAvailableInData(dataLayers, datasourceLayers, tables); return referenceLayers @@ -54,8 +62,8 @@ export function getGroupsRelatedToData( if (!state) { return []; } - const dataLayers = state.layers.filter( - ({ layerType = layerTypes.DATA }) => layerType === layerTypes.DATA + const dataLayers = state.layers.filter(({ layerType = layerTypes.DATA }) => + isDataLayer({ layerType }) ); const groupsAvailable = getGroupsAvailableInData(dataLayers, datasourceLayers, tables); return referenceLayers.filter(({ label }: T) => groupsAvailable[label]); @@ -242,3 +250,205 @@ function computeStaticValueForGroup( } } } + +export const getReferenceSupportedLayer = ( + state?: XYState, + frame?: Pick +) => { + const referenceLineGroupIds = [ + { + id: 'yReferenceLineLeft', + label: 'yLeft' as const, + }, + { + id: 'yReferenceLineRight', + label: 'yRight' as const, + }, + { + id: 'xReferenceLine', + label: 'x' as const, + }, + ]; + const referenceLineGroups = getGroupsRelatedToData( + referenceLineGroupIds, + state, + frame?.datasourceLayers || {}, + frame?.activeData + ); + const dataLayers = + state?.layers.filter(({ layerType = layerTypes.DATA }) => isDataLayer({ layerType })) || []; + const filledDataLayers = dataLayers.filter( + ({ accessors, xAccessor }) => accessors.length || xAccessor + ); + const layerHasNumberHistogram = checkScaleOperation( + 'interval', + 'number', + frame?.datasourceLayers || {} + ); + + const initialDimensions = state + ? referenceLineGroups.map(({ id, label }) => ({ + groupId: id, + columnId: generateId(), + dataType: 'number', + label: getAxisName(label, { isHorizontal: isHorizontalChart(state?.layers || []) }), + staticValue: getStaticValue( + dataLayers, + label, + { activeData: frame?.activeData }, + layerHasNumberHistogram + ), + })) + : undefined; + + return { + type: layerTypes.REFERENCELINE, + label: i18n.translate('xpack.lens.xyChart.addReferenceLineLayerLabel', { + defaultMessage: 'Reference lines', + }), + icon: LensIconChartBarReferenceLine, + disabled: + !filledDataLayers.length || + (!dataLayers.some(layerHasNumberHistogram) && + dataLayers.every(({ accessors }) => !accessors.length)), + toolTipContent: filledDataLayers.length + ? undefined + : i18n.translate('xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp', { + defaultMessage: 'Add some data to enable reference layer', + }), + initialDimensions, + }; +}; +export const setReferenceDimension: Visualization['setDimension'] = ({ + prevState, + layerId, + columnId, + groupId, + previousColumn, +}) => { + const foundLayer = prevState.layers.find((l) => l.layerId === layerId); + if (!foundLayer) { + return prevState; + } + const newLayer = { ...foundLayer }; + + newLayer.accessors = [...newLayer.accessors.filter((a) => a !== columnId), columnId]; + const hasYConfig = newLayer.yConfig?.some(({ forAccessor }) => forAccessor === columnId); + const previousYConfig = previousColumn + ? newLayer.yConfig?.find(({ forAccessor }) => forAccessor === previousColumn) + : false; + if (!hasYConfig) { + newLayer.yConfig = [ + ...(newLayer.yConfig || []), + { + // override with previous styling, + ...previousYConfig, + // but keep the new group & id config + forAccessor: columnId, + axisMode: + groupId === 'xReferenceLine' + ? 'bottom' + : groupId === 'yReferenceLineRight' + ? 'right' + : 'left', + }, + ]; + } + return { + ...prevState, + layers: prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)), + }; +}; + +export const getReferenceConfiguration = ({ + state, + frame, + layer, + sortedAccessors, + mappedAccessors, +}: { + state: XYState; + frame: FramePublicAPI; + layer: XYLayerConfig; + sortedAccessors: string[]; + mappedAccessors: AccessorConfig[]; +}) => { + const idToIndex = sortedAccessors.reduce>((memo, id, index) => { + memo[id] = index; + return memo; + }, {}); + const { bottom, left, right } = groupBy( + [...(layer.yConfig || [])].sort( + ({ forAccessor: forA }, { forAccessor: forB }) => idToIndex[forA] - idToIndex[forB] + ), + ({ axisMode }) => { + return axisMode; + } + ); + const groupsToShow = getGroupsToShow( + [ + // When a reference layer panel is added, a static reference line should automatically be included by default + // in the first available axis, in the following order: vertical left, vertical right, horizontal. + { + config: left, + id: 'yReferenceLineLeft', + label: 'yLeft', + dataTestSubj: 'lnsXY_yReferenceLineLeftPanel', + }, + { + config: right, + id: 'yReferenceLineRight', + label: 'yRight', + dataTestSubj: 'lnsXY_yReferenceLineRightPanel', + }, + { + config: bottom, + id: 'xReferenceLine', + label: 'x', + dataTestSubj: 'lnsXY_xReferenceLinePanel', + }, + ], + state, + frame.datasourceLayers, + frame?.activeData + ); + const isHorizontal = isHorizontalChart(state.layers); + return { + // Each reference lines layer panel will have sections for each available axis + // (horizontal axis, vertical axis left, vertical axis right). + // Only axes that support numeric reference lines should be shown + groups: groupsToShow.map(({ config = [], id, label, dataTestSubj, valid }) => ({ + groupId: id, + groupLabel: getAxisName(label, { isHorizontal }), + accessors: config.map(({ forAccessor, color }) => ({ + columnId: forAccessor, + color: color || mappedAccessors.find(({ columnId }) => columnId === forAccessor)?.color, + triggerIcon: 'color' as const, + })), + filterOperations: isNumericMetric, + supportsMoreColumns: true, + required: false, + enableDimensionEditor: true, + supportStaticValue: true, + paramEditorCustomProps: { + label: i18n.translate('xpack.lens.indexPattern.staticValue.label', { + defaultMessage: 'Reference line value', + }), + }, + supportFieldFormat: false, + dataTestSubj, + invalid: !valid, + invalidMessage: + label === 'x' + ? i18n.translate('xpack.lens.configure.invalidBottomReferenceLineDimension', { + defaultMessage: + 'This reference line is assigned to an axis that no longer exists or is no longer valid. You may move this reference line to another available axis or remove it.', + }) + : i18n.translate('xpack.lens.configure.invalidReferenceLineDimension', { + defaultMessage: + 'This reference line is assigned to an axis that no longer exists. You may move this reference line to another available axis or remove it.', + }), + requiresPreviousColumnOnDuplicate: true, + })), + }; +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index b593b6e294844..8fa76d0b997d2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -13,9 +13,10 @@ import { OperationMetadata, DatasourcePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; import type { ValidLayer, XYLayerConfig } from '../../common/expressions'; import { layerTypes } from '../../common'; -import { hasIcon } from './xy_config_panel/reference_line_panel'; +import { hasIcon } from './xy_config_panel/shared/icon_select'; import { defaultReferenceLineColor } from './color_assignment'; import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values'; +import { isDataLayer, isReferenceLayer } from './visualization_helpers'; export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: XYLayerConfig) => { const originalOrder = datasource @@ -58,7 +59,7 @@ export function toPreviewExpression( { ...state, layers: state.layers.map((layer) => - layer.layerType === layerTypes.DATA + isDataLayer(layer) ? { ...layer, hide: true } : // cap the reference line to 1px { @@ -341,12 +342,11 @@ export const buildExpression = ( arguments: { forAccessor: [yConfig.forAccessor], axisMode: yConfig.axisMode ? [yConfig.axisMode] : [], - color: - layer.layerType === layerTypes.REFERENCELINE - ? [yConfig.color || defaultReferenceLineColor] - : yConfig.color - ? [yConfig.color] - : [], + color: isReferenceLayer(layer) + ? [yConfig.color || defaultReferenceLineColor] + : yConfig.color + ? [yConfig.color] + : [], lineStyle: [yConfig.lineStyle || 'solid'], lineWidth: [yConfig.lineWidth || 1], fill: [yConfig.fill || 'none'], diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 9a84304bcfb34..54fc4e0594a7f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -17,88 +17,40 @@ import { ThemeServiceStart } from 'kibana/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; import { getSuggestions } from './xy_suggestions'; -import { XyToolbar, DimensionEditor } from './xy_config_panel'; +import { XyToolbar } from './xy_config_panel'; +import { DimensionEditor } from './xy_config_panel/dimension_editor'; import { LayerHeader } from './xy_config_panel/layer_header'; -import type { Visualization, OperationMetadata, VisualizationType, AccessorConfig } from '../types'; +import type { Visualization, AccessorConfig, FramePublicAPI } from '../types'; import { State, visualizationTypes, XYSuggestion } from './types'; import { SeriesType, XYLayerConfig, YAxisMode } from '../../common/expressions'; -import { LayerType, layerTypes } from '../../common'; +import { layerTypes } from '../../common'; import { isHorizontalChart } from './state_helpers'; import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression'; -import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; -import { LensIconChartMixedXy } from '../assets/chart_mixed_xy'; -import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; import { getColumnToLabelMap } from './state_helpers'; -import { LensIconChartBarReferenceLine } from '../assets/chart_bar_reference_line'; -import { generateId } from '../id_generator'; import { getGroupsAvailableInData, - getGroupsRelatedToData, - getGroupsToShow, - getStaticValue, + getReferenceConfiguration, + getReferenceSupportedLayer, + setReferenceDimension, } from './reference_line_helpers'; import { - checkScaleOperation, checkXAccessorCompatibility, + defaultSeriesType, getAxisName, + getDescription, + getLayersByType, + getVisualizationType, + isBucketed, + isDataLayer, + isNumericDynamicMetric, + isReferenceLayer, + newLayerState, + supportedDataLayer, + validateLayersForDimension, } from './visualization_helpers'; import { groupAxesByType } from './axes_configuration'; - -const defaultIcon = LensIconChartBarStacked; -const defaultSeriesType = 'bar_stacked'; - -const isNumericMetric = (op: OperationMetadata) => !op.isBucketed && op.dataType === 'number'; -const isNumericDynamicMetric = (op: OperationMetadata) => isNumericMetric(op) && !op.isStaticValue; -const isBucketed = (op: OperationMetadata) => op.isBucketed; - -function getVisualizationType(state: State): VisualizationType | 'mixed' { - if (!state.layers.length) { - return ( - visualizationTypes.find((t) => t.id === state.preferredSeriesType) ?? visualizationTypes[0] - ); - } - const visualizationType = visualizationTypes.find((t) => t.id === state.layers[0].seriesType); - const seriesTypes = uniq(state.layers.map((l) => l.seriesType)); - - return visualizationType && seriesTypes.length === 1 ? visualizationType : 'mixed'; -} - -function getDescription(state?: State) { - if (!state) { - return { - icon: defaultIcon, - label: i18n.translate('xpack.lens.xyVisualization.xyLabel', { - defaultMessage: 'XY', - }), - }; - } - - const visualizationType = getVisualizationType(state); - - if (visualizationType === 'mixed' && isHorizontalChart(state.layers)) { - return { - icon: LensIconChartBarHorizontal, - label: i18n.translate('xpack.lens.xyVisualization.mixedBarHorizontalLabel', { - defaultMessage: 'Mixed bar horizontal', - }), - }; - } - - if (visualizationType === 'mixed') { - return { - icon: LensIconChartMixedXy, - label: i18n.translate('xpack.lens.xyVisualization.mixedLabel', { - defaultMessage: 'Mixed XY', - }), - }; - } - - return { - icon: visualizationType.icon, - label: visualizationType.fullLabel || visualizationType.label, - }; -} +import { XYState } from '..'; export const getXyVisualization = ({ paletteService, @@ -112,7 +64,6 @@ export const getXyVisualization = ({ kibanaTheme: ThemeServiceStart; }): Visualization => ({ id: 'lnsXY', - visualizationTypes, getVisualizationTypeId(state) { const type = getVisualizationType(state); @@ -159,14 +110,7 @@ export const getXyVisualization = ({ }; }, - getDescription(state) { - const { icon, label } = getDescription(state); - - return { - icon: icon || defaultIcon, - label, - }; - }, + getDescription, switchVisualizationType(seriesType: string, state: State) { return { @@ -206,80 +150,7 @@ export const getXyVisualization = ({ }, getSupportedLayers(state, frame) { - const referenceLineGroupIds = [ - { - id: 'yReferenceLineLeft', - label: 'yLeft' as const, - }, - { - id: 'yReferenceLineRight', - label: 'yRight' as const, - }, - { - id: 'xReferenceLine', - label: 'x' as const, - }, - ]; - - const dataLayers = - state?.layers.filter(({ layerType = layerTypes.DATA }) => layerType === layerTypes.DATA) || - []; - const filledDataLayers = dataLayers.filter( - ({ accessors, xAccessor }) => accessors.length || xAccessor - ); - const layerHasNumberHistogram = checkScaleOperation( - 'interval', - 'number', - frame?.datasourceLayers || {} - ); - const referenceLineGroups = getGroupsRelatedToData( - referenceLineGroupIds, - state, - frame?.datasourceLayers || {}, - frame?.activeData - ); - - const layers = [ - { - type: layerTypes.DATA, - label: i18n.translate('xpack.lens.xyChart.addDataLayerLabel', { - defaultMessage: 'Visualization', - }), - icon: LensIconChartMixedXy, - }, - { - type: layerTypes.REFERENCELINE, - label: i18n.translate('xpack.lens.xyChart.addReferenceLineLayerLabel', { - defaultMessage: 'Reference lines', - }), - icon: LensIconChartBarReferenceLine, - disabled: - !filledDataLayers.length || - (!dataLayers.some(layerHasNumberHistogram) && - dataLayers.every(({ accessors }) => !accessors.length)), - tooltipContent: filledDataLayers.length - ? undefined - : i18n.translate('xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp', { - defaultMessage: 'Add some data to enable reference layer', - }), - initialDimensions: state - ? referenceLineGroups.map(({ id, label }) => ({ - groupId: id, - columnId: generateId(), - dataType: 'number', - label: getAxisName(label, { isHorizontal: isHorizontalChart(state?.layers || []) }), - staticValue: getStaticValue( - dataLayers, - label, - { activeData: frame?.activeData }, - layerHasNumberHistogram - ), - })) - : undefined, - }, - ]; - - return layers; + return [supportedDataLayer, getReferenceSupportedLayer(state, frame)]; }, getConfiguration({ state, frame, layerId }) { @@ -288,113 +159,24 @@ export const getXyVisualization = ({ return { groups: [] }; } - const datasource = frame.datasourceLayers[layer.layerId]; - - const sortedAccessors: string[] = getSortedAccessors(datasource, layer); - let mappedAccessors: AccessorConfig[] = sortedAccessors.map((accessor) => ({ - columnId: accessor, - })); + const sortedAccessors: string[] = getSortedAccessors( + frame.datasourceLayers[layer.layerId], + layer + ); + const mappedAccessors = getMappedAccessors({ + state, + frame, + layer, + fieldFormats, + paletteService, + accessors: sortedAccessors, + }); - if (frame.activeData) { - const colorAssignments = getColorAssignments( - state.layers, - { tables: frame.activeData }, - fieldFormats.deserialize - ); - mappedAccessors = getAccessorColorConfig( - colorAssignments, - frame, - { - ...layer, - accessors: sortedAccessors.filter((sorted) => layer.accessors.includes(sorted)), - }, - paletteService - ); + if (isReferenceLayer(layer)) { + return getReferenceConfiguration({ state, frame, layer, sortedAccessors, mappedAccessors }); } const isHorizontal = isHorizontalChart(state.layers); - const isDataLayer = !layer.layerType || layer.layerType === layerTypes.DATA; - - if (!isDataLayer) { - const idToIndex = sortedAccessors.reduce>((memo, id, index) => { - memo[id] = index; - return memo; - }, {}); - const { bottom, left, right } = groupBy( - [...(layer.yConfig || [])].sort( - ({ forAccessor: forA }, { forAccessor: forB }) => idToIndex[forA] - idToIndex[forB] - ), - ({ axisMode }) => { - return axisMode; - } - ); - const groupsToShow = getGroupsToShow( - [ - // When a reference layer panel is added, a static reference line should automatically be included by default - // in the first available axis, in the following order: vertical left, vertical right, horizontal. - { - config: left, - id: 'yReferenceLineLeft', - label: 'yLeft', - dataTestSubj: 'lnsXY_yReferenceLineLeftPanel', - }, - { - config: right, - id: 'yReferenceLineRight', - label: 'yRight', - dataTestSubj: 'lnsXY_yReferenceLineRightPanel', - }, - { - config: bottom, - id: 'xReferenceLine', - label: 'x', - dataTestSubj: 'lnsXY_xReferenceLinePanel', - }, - ], - state, - frame.datasourceLayers, - frame?.activeData - ); - return { - // Each reference lines layer panel will have sections for each available axis - // (horizontal axis, vertical axis left, vertical axis right). - // Only axes that support numeric reference lines should be shown - groups: groupsToShow.map(({ config = [], id, label, dataTestSubj, valid }) => ({ - groupId: id, - groupLabel: getAxisName(label, { isHorizontal }), - accessors: config.map(({ forAccessor, color }) => ({ - columnId: forAccessor, - color: color || mappedAccessors.find(({ columnId }) => columnId === forAccessor)?.color, - triggerIcon: 'color', - })), - filterOperations: isNumericMetric, - supportsMoreColumns: true, - required: false, - enableDimensionEditor: true, - supportStaticValue: true, - paramEditorCustomProps: { - label: i18n.translate('xpack.lens.indexPattern.staticValue.label', { - defaultMessage: 'Reference line value', - }), - }, - supportFieldFormat: false, - dataTestSubj, - invalid: !valid, - invalidMessage: - label === 'x' - ? i18n.translate('xpack.lens.configure.invalidBottomReferenceLineDimension', { - defaultMessage: - 'This reference line is assigned to an axis that no longer exists or is no longer valid. You may move this reference line to another available axis or remove it.', - }) - : i18n.translate('xpack.lens.configure.invalidReferenceLineDimension', { - defaultMessage: - 'This reference line is assigned to an axis that no longer exists. You may move this reference line to another available axis or remove it.', - }), - requiresPreviousColumnOnDuplicate: true, - })), - }; - } - const { left, right } = groupAxesByType([layer], frame.activeData); // Check locally if it has one accessor OR one accessor per axis const layerHasOnlyOneAccessor = Boolean( @@ -458,7 +240,7 @@ export const getXyVisualization = ({ ? [ { columnId: layer.splitAccessor, - triggerIcon: 'colorBy', + triggerIcon: 'colorBy' as const, palette: paletteService .get(layer.palette?.name || 'default') .getCategoricalColors(10, layer.palette?.params), @@ -480,11 +262,17 @@ export const getXyVisualization = ({ return state.layers[0].palette; }, - setDimension({ prevState, layerId, columnId, groupId, previousColumn }) { + setDimension(props) { + const { prevState, layerId, columnId, groupId } = props; const foundLayer = prevState.layers.find((l) => l.layerId === layerId); if (!foundLayer) { return prevState; } + + if (isReferenceLayer(foundLayer)) { + return setReferenceDimension(props); + } + const newLayer = { ...foundLayer }; if (groupId === 'x') { newLayer.xAccessor = columnId; @@ -495,32 +283,6 @@ export const getXyVisualization = ({ if (groupId === 'breakdown') { newLayer.splitAccessor = columnId; } - - if (newLayer.layerType === layerTypes.REFERENCELINE) { - newLayer.accessors = [...newLayer.accessors.filter((a) => a !== columnId), columnId]; - const hasYConfig = newLayer.yConfig?.some(({ forAccessor }) => forAccessor === columnId); - const previousYConfig = previousColumn - ? newLayer.yConfig?.find(({ forAccessor }) => forAccessor === previousColumn) - : false; - if (!hasYConfig) { - newLayer.yConfig = [ - ...(newLayer.yConfig || []), - { - // override with previous styling, - ...previousYConfig, - // but keep the new group & id config - forAccessor: columnId, - axisMode: - groupId === 'xReferenceLine' - ? 'bottom' - : groupId === 'yReferenceLineRight' - ? 'right' - : 'left', - }, - ]; - } - } - return { ...prevState, layers: prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)), @@ -633,14 +395,13 @@ export const getXyVisualization = ({ frame.datasourceLayers, frame?.activeData ); + if ( (Object.keys(groupsAvailable) as Array<'x' | 'yLeft' | 'yRight'>).every( (id) => !groupsAvailable[id] ) ) { - newLayers = newLayers.filter( - ({ layerType, accessors }) => layerType === layerTypes.DATA || accessors.length - ); + newLayers = newLayers.filter((layer) => isDataLayer(layer) || layer.accessors.length); } return { @@ -713,7 +474,7 @@ export const getXyVisualization = ({ // filter out those layers with no accessors at all const filteredLayers = state.layers.filter( ({ accessors, xAccessor, splitAccessor, layerType }: XYLayerConfig) => - layerType === layerTypes.DATA && + isDataLayer({ layerType }) && (accessors.length > 0 || xAccessor != null || splitAccessor != null) ); for (const [dimension, criteria] of checks) { @@ -795,81 +556,40 @@ export const getXyVisualization = ({ }, }); -function validateLayersForDimension( - dimension: string, - layers: XYLayerConfig[], - missingCriteria: (layer: XYLayerConfig) => boolean -): - | { valid: true } - | { - valid: false; - payload: { shortMessage: string; longMessage: React.ReactNode }; - } { - // Multiple layers must be consistent: - // * either a dimension is missing in ALL of them - // * or should not miss on any - if (layers.every(missingCriteria) || !layers.some(missingCriteria)) { - return { valid: true }; - } - // otherwise it's an error and it has to be reported - const layerMissingAccessors = layers.reduce((missing: number[], layer, i) => { - if (missingCriteria(layer)) { - missing.push(i); - } - return missing; - }, []); - - return { - valid: false, - payload: getMessageIdsForDimension(dimension, layerMissingAccessors, isHorizontalChart(layers)), - }; -} - -// i18n ids cannot be dynamically generated, hence the function below -function getMessageIdsForDimension(dimension: string, layers: number[], isHorizontal: boolean) { - const layersList = layers.map((i: number) => i + 1).join(', '); - switch (dimension) { - case 'Break down': - return { - shortMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureSplitShort', { - defaultMessage: `Missing {axis}.`, - values: { axis: 'Break down by axis' }, - }), - longMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureSplitLong', { - defaultMessage: `{layers, plural, one {Layer} other {Layers}} {layersList} {layers, plural, one {requires} other {require}} a field for the {axis}.`, - values: { layers: layers.length, layersList, axis: 'Break down by axis' }, - }), - }; - case 'Y': - return { - shortMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureYShort', { - defaultMessage: `Missing {axis}.`, - values: { axis: getAxisName('y', { isHorizontal }) }, - }), - longMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureYLong', { - defaultMessage: `{layers, plural, one {Layer} other {Layers}} {layersList} {layers, plural, one {requires} other {require}} a field for the {axis}.`, - values: { layers: layers.length, layersList, axis: getAxisName('y', { isHorizontal }) }, - }), - }; +const getMappedAccessors = ({ + accessors, + frame, + fieldFormats, + paletteService, + state, + layer, +}: { + accessors: string[]; + frame: FramePublicAPI; + paletteService: PaletteRegistry; + fieldFormats: FieldFormatsStart; + state: XYState; + layer: XYLayerConfig; +}) => { + let mappedAccessors: AccessorConfig[] = accessors.map((accessor) => ({ + columnId: accessor, + })); + + if (frame.activeData) { + const colorAssignments = getColorAssignments( + state.layers, + { tables: frame.activeData }, + fieldFormats.deserialize + ); + mappedAccessors = getAccessorColorConfig( + colorAssignments, + frame, + { + ...layer, + accessors: accessors.filter((sorted) => layer.accessors.includes(sorted)), + }, + paletteService + ); } - return { shortMessage: '', longMessage: '' }; -} - -function newLayerState( - seriesType: SeriesType, - layerId: string, - layerType: LayerType = layerTypes.DATA -): XYLayerConfig { - return { - layerId, - seriesType, - accessors: [], - layerType, - }; -} - -function getLayersByType(state: State, byType?: string) { - return state.layers.filter(({ layerType = layerTypes.DATA }) => - byType ? layerType === byType : true - ); -} + return mappedAccessors; +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx index e6bf460adbd2c..6031ed7223039 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -6,10 +6,16 @@ */ import { i18n } from '@kbn/i18n'; -import { DatasourcePublicAPI } from '../types'; -import { XYState } from './types'; +import { uniq } from 'lodash'; +import { DatasourcePublicAPI, OperationMetadata, VisualizationType } from '../types'; +import { State, visualizationTypes, XYState } from './types'; import { isHorizontalChart } from './state_helpers'; -import { XYLayerConfig } from '../../common/expressions'; +import { SeriesType, XYLayerConfig } from '../../common/expressions'; +import { layerTypes } from '..'; +import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; +import { LensIconChartMixedXy } from '../assets/chart_mixed_xy'; +import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; +import { LayerType } from '../../common'; export function getAxisName( axis: 'x' | 'y' | 'yLeft' | 'yRight', @@ -114,3 +120,157 @@ export function checkScaleOperation( ); }; } + +export const isDataLayer = (layer: Pick) => + layer.layerType === layerTypes.DATA; + +export const isReferenceLayer = (layer: Pick) => + layer?.layerType === layerTypes.REFERENCELINE; + +export function getVisualizationType(state: State): VisualizationType | 'mixed' { + if (!state.layers.length) { + return ( + visualizationTypes.find((t) => t.id === state.preferredSeriesType) ?? visualizationTypes[0] + ); + } + const visualizationType = visualizationTypes.find((t) => t.id === state.layers[0].seriesType); + const seriesTypes = uniq(state.layers.map((l) => l.seriesType)); + + return visualizationType && seriesTypes.length === 1 ? visualizationType : 'mixed'; +} + +export function getDescription(state?: State) { + if (!state) { + return { + icon: defaultIcon, + label: i18n.translate('xpack.lens.xyVisualization.xyLabel', { + defaultMessage: 'XY', + }), + }; + } + + const visualizationType = getVisualizationType(state); + + if (visualizationType === 'mixed' && isHorizontalChart(state.layers)) { + return { + icon: LensIconChartBarHorizontal, + label: i18n.translate('xpack.lens.xyVisualization.mixedBarHorizontalLabel', { + defaultMessage: 'Mixed bar horizontal', + }), + }; + } + + if (visualizationType === 'mixed') { + return { + icon: LensIconChartMixedXy, + label: i18n.translate('xpack.lens.xyVisualization.mixedLabel', { + defaultMessage: 'Mixed XY', + }), + }; + } + + return { + icon: visualizationType.icon || defaultIcon, + label: visualizationType.fullLabel || visualizationType.label, + }; +} + +export const defaultIcon = LensIconChartBarStacked; +export const defaultSeriesType = 'bar_stacked'; + +export const supportedDataLayer = { + type: layerTypes.DATA, + label: i18n.translate('xpack.lens.xyChart.addDataLayerLabel', { + defaultMessage: 'Visualization', + }), + icon: LensIconChartMixedXy, +}; + +// i18n ids cannot be dynamically generated, hence the function below +export function getMessageIdsForDimension( + dimension: string, + layers: number[], + isHorizontal: boolean +) { + const layersList = layers.map((i: number) => i + 1).join(', '); + switch (dimension) { + case 'Break down': + return { + shortMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureSplitShort', { + defaultMessage: `Missing {axis}.`, + values: { axis: 'Break down by axis' }, + }), + longMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureSplitLong', { + defaultMessage: `{layers, plural, one {Layer} other {Layers}} {layersList} {layers, plural, one {requires} other {require}} a field for the {axis}.`, + values: { layers: layers.length, layersList, axis: 'Break down by axis' }, + }), + }; + case 'Y': + return { + shortMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureYShort', { + defaultMessage: `Missing {axis}.`, + values: { axis: getAxisName('y', { isHorizontal }) }, + }), + longMessage: i18n.translate('xpack.lens.xyVisualization.dataFailureYLong', { + defaultMessage: `{layers, plural, one {Layer} other {Layers}} {layersList} {layers, plural, one {requires} other {require}} a field for the {axis}.`, + values: { layers: layers.length, layersList, axis: getAxisName('y', { isHorizontal }) }, + }), + }; + } + return { shortMessage: '', longMessage: '' }; +} + +export function newLayerState( + seriesType: SeriesType, + layerId: string, + layerType: LayerType = layerTypes.DATA +): XYLayerConfig { + return { + layerId, + seriesType, + accessors: [], + layerType, + }; +} + +export function getLayersByType(state: State, byType?: string) { + return state.layers.filter(({ layerType = layerTypes.DATA }) => + byType ? layerType === byType : true + ); +} + +export function validateLayersForDimension( + dimension: string, + layers: XYLayerConfig[], + missingCriteria: (layer: XYLayerConfig) => boolean +): + | { valid: true } + | { + valid: false; + payload: { shortMessage: string; longMessage: React.ReactNode }; + } { + // Multiple layers must be consistent: + // * either a dimension is missing in ALL of them + // * or should not miss on any + if (layers.every(missingCriteria) || !layers.some(missingCriteria)) { + return { valid: true }; + } + // otherwise it's an error and it has to be reported + const layerMissingAccessors = layers.reduce((missing: number[], layer, i) => { + if (missingCriteria(layer)) { + missing.push(i); + } + return missing; + }, []); + + return { + valid: false, + payload: getMessageIdsForDimension(dimension, layerMissingAccessors, isHorizontalChart(layers)), + }; +} + +export const isNumericMetric = (op: OperationMetadata) => + !op.isBucketed && op.dataType === 'number'; +export const isNumericDynamicMetric = (op: OperationMetadata) => + isNumericMetric(op) && !op.isStaticValue; +export const isBucketed = (op: OperationMetadata) => op.isBucketed; diff --git a/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx b/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx index d5eb8ac1e92ba..81037418a8143 100644 --- a/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx @@ -7,9 +7,11 @@ import { uniq } from 'lodash'; import React from 'react'; +import moment from 'moment'; import { Endzones } from '../../../../../src/plugins/charts/public'; import type { LensMultiTable } from '../../common'; import type { LayerArgs } from '../../common/expressions'; +import { search } from '../../../../../src/plugins/data/public'; export interface XDomain { min?: number; @@ -17,6 +19,23 @@ export interface XDomain { minInterval?: number; } +export const getAppliedTimeRange = (layers: LayerArgs[], data: LensMultiTable) => { + return Object.entries(data.tables) + .map(([tableId, table]) => { + const layer = layers.find((l) => l.layerId === tableId); + const xColumn = table.columns.find((col) => col.id === layer?.xAccessor); + const timeRange = + xColumn && search.aggs.getDateHistogramMetaDataByDatatableColumn(xColumn)?.timeRange; + if (timeRange) { + return { + timeRange, + field: xColumn.meta.field, + }; + } + }) + .find(Boolean); +}; + export const getXDomain = ( layers: LayerArgs[], data: LensMultiTable, @@ -24,10 +43,13 @@ export const getXDomain = ( isTimeViz: boolean, isHistogram: boolean ) => { + const appliedTimeRange = getAppliedTimeRange(layers, data)?.timeRange; + const from = appliedTimeRange?.from; + const to = appliedTimeRange?.to; const baseDomain = isTimeViz ? { - min: data.dateRange?.fromDate.getTime() ?? NaN, - max: data.dateRange?.toDate.getTime() ?? NaN, + min: from ? moment(from).valueOf() : NaN, + max: to ? moment(to).valueOf() : NaN, minInterval, } : isHistogram diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx index 517f4bd378591..c59edfeab0fb8 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx @@ -12,7 +12,7 @@ import { EuiFormRow, EuiColorPicker, EuiColorPickerProps, EuiToolTip, EuiIcon } import type { PaletteRegistry } from 'src/plugins/charts/public'; import type { VisualizationDimensionEditorProps } from '../../types'; import { State } from '../types'; -import { FormatFactory, layerTypes } from '../../../common'; +import { FormatFactory } from '../../../common'; import { getSeriesColor } from '../state_helpers'; import { defaultReferenceLineColor, @@ -22,6 +22,7 @@ import { import { getSortedAccessors } from '../to_expression'; import { updateLayer } from '.'; import { TooltipWrapper } from '../../shared_components'; +import { isReferenceLayer } from '../visualization_helpers'; const tooltipContent = { auto: i18n.translate('xpack.lens.configPanel.color.tooltip.auto', { @@ -59,7 +60,7 @@ export const ColorPicker = ({ const overwriteColor = getSeriesColor(layer, accessor); const currentColor = useMemo(() => { if (overwriteColor || !frame.activeData) return overwriteColor; - if (layer.layerType === layerTypes.REFERENCELINE) { + if (isReferenceLayer(layer)) { return defaultReferenceLineColor; } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/dimension_editor.tsx new file mode 100644 index 0000000000000..dce32d1d6b116 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/dimension_editor.tsx @@ -0,0 +1,149 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiButtonGroup, EuiFormRow, htmlIdGenerator } from '@elastic/eui'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { VisualizationDimensionEditorProps } from '../../types'; +import { State } from '../types'; +import { FormatFactory } from '../../../common'; +import { YAxisMode } from '../../../common/expressions'; +import { isHorizontalChart } from '../state_helpers'; +import { ColorPicker } from './color_picker'; +import { ReferenceLinePanel } from './reference_line_panel'; +import { PalettePicker } from '../../shared_components'; +import { isReferenceLayer } from '../visualization_helpers'; + +type UnwrapArray = T extends Array ? P : T; + +export function updateLayer( + state: State, + layer: UnwrapArray, + index: number +): State { + const newLayers = [...state.layers]; + newLayers[index] = layer; + + return { + ...state, + layers: newLayers, + }; +} + +export const idPrefix = htmlIdGenerator()(); + +export function DimensionEditor( + props: VisualizationDimensionEditorProps & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; + } +) { + const { state, setState, layerId, accessor } = props; + const index = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[index]; + + if (isReferenceLayer(layer)) { + return ; + } + + const axisMode = + (layer.yConfig && + layer.yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode) || + 'auto'; + + if (props.groupId === 'breakdown') { + return ( + <> + { + setState(updateLayer(state, { ...layer, palette: newPalette }, index)); + }} + /> + + ); + } + + const isHorizontal = isHorizontalChart(state.layers); + + return ( + <> + + + + { + const newMode = id.replace(idPrefix, '') as YAxisMode; + const newYAxisConfigs = [...(layer.yConfig || [])]; + const existingIndex = newYAxisConfigs.findIndex( + (yAxisConfig) => yAxisConfig.forAccessor === accessor + ); + if (existingIndex !== -1) { + newYAxisConfigs[existingIndex] = { + ...newYAxisConfigs[existingIndex], + axisMode: newMode, + }; + } else { + newYAxisConfigs.push({ + forAccessor: accessor, + axisMode: newMode, + }); + } + setState(updateLayer(state, { ...layer, yConfig: newYAxisConfigs }, index)); + }} + /> + + + ); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/index.tsx index 820a61c1ee37a..94df921ba1e5d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/index.tsx @@ -8,38 +8,17 @@ import React, { memo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { Position, ScaleType, VerticalAlignment, HorizontalAlignment } from '@elastic/charts'; -import { - EuiButtonGroup, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - htmlIdGenerator, -} from '@elastic/eui'; -import type { PaletteRegistry } from 'src/plugins/charts/public'; -import type { - VisualizationLayerWidgetProps, - VisualizationToolbarProps, - VisualizationDimensionEditorProps, - FramePublicAPI, -} from '../../types'; -import { State, visualizationTypes, XYState } from '../types'; -import { FormatFactory, layerTypes } from '../../../common'; -import { - SeriesType, - YAxisMode, - AxesSettingsConfig, - AxisExtentConfig, -} from '../../../common/expressions'; -import { isHorizontalChart, isHorizontalSeries } from '../state_helpers'; -import { trackUiEvent } from '../../lens_ui_telemetry'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { VisualizationToolbarProps, FramePublicAPI } from '../../types'; +import { State, XYState } from '../types'; +import { AxesSettingsConfig, AxisExtentConfig } from '../../../common/expressions'; +import { isHorizontalChart } from '../state_helpers'; import { LegendSettingsPopover } from '../../shared_components'; import { AxisSettingsPopover } from './axis_settings_popover'; import { getAxesConfiguration, GroupsConfiguration } from '../axes_configuration'; import { VisualOptionsPopover } from './visual_options_popover'; import { getScaleType } from '../to_expression'; -import { ColorPicker } from './color_picker'; -import { ReferenceLinePanel } from './reference_line_panel'; -import { PalettePicker, TooltipWrapper } from '../../shared_components'; +import { TooltipWrapper } from '../../shared_components'; import { getDefaultVisualValuesForLayer } from '../../shared_components/datasource_default_values'; type UnwrapArray = T extends Array ? P : T; @@ -87,52 +66,6 @@ const legendOptions: Array<{ }, ]; -export function LayerContextMenu(props: VisualizationLayerWidgetProps) { - const { state, layerId } = props; - const horizontalOnly = isHorizontalChart(state.layers); - const index = state.layers.findIndex((l) => l.layerId === layerId); - const layer = state.layers[index]; - - if (!layer) { - return null; - } - - return ( - - isHorizontalSeries(t.id as SeriesType) === horizontalOnly) - .map((t) => ({ - className: `lnsLayerChartSwitch__item ${ - layer.seriesType === t.id ? 'lnsLayerChartSwitch__item-isSelected' : '' - }`, - id: t.id, - label: t.label, - iconType: t.icon || 'empty', - 'data-test-subj': `lnsXY_seriesType-${t.id}`, - }))} - idSelected={layer.seriesType} - onChange={(seriesType) => { - trackUiEvent('xy_change_layer_display'); - props.setState( - updateLayer(state, { ...layer, seriesType: seriesType as SeriesType }, index) - ); - }} - isIconOnly - /> - - ); -} - const getDataBounds = function ( activeData: FramePublicAPI['activeData'], axes: GroupsConfiguration @@ -565,114 +498,3 @@ export const XyToolbar = memo(function XyToolbar( ); }); - -export const idPrefix = htmlIdGenerator()(); - -export function DimensionEditor( - props: VisualizationDimensionEditorProps & { - formatFactory: FormatFactory; - paletteService: PaletteRegistry; - } -) { - const { state, setState, layerId, accessor } = props; - const index = state.layers.findIndex((l) => l.layerId === layerId); - const layer = state.layers[index]; - const isHorizontal = isHorizontalChart(state.layers); - const axisMode = - (layer.yConfig && - layer.yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode) || - 'auto'; - - if (props.groupId === 'breakdown') { - return ( - <> - { - setState(updateLayer(state, { ...layer, palette: newPalette }, index)); - }} - /> - - ); - } - - if (layer.layerType === layerTypes.REFERENCELINE) { - return ; - } - - return ( - <> - - - - { - const newMode = id.replace(idPrefix, '') as YAxisMode; - const newYAxisConfigs = [...(layer.yConfig || [])]; - const existingIndex = newYAxisConfigs.findIndex( - (yAxisConfig) => yAxisConfig.forAccessor === accessor - ); - if (existingIndex !== -1) { - newYAxisConfigs[existingIndex] = { - ...newYAxisConfigs[existingIndex], - axisMode: newMode, - }; - } else { - newYAxisConfigs.push({ - forAccessor: accessor, - axisMode: newMode, - }); - } - setState(updateLayer(state, { ...layer, yConfig: newYAxisConfigs }, index)); - }} - /> - - - ); -} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx index 5de54cecd2101..d2f54af8cf21a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx @@ -8,9 +8,8 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiIcon, EuiPopover, EuiSelectable, EuiText, EuiPopoverTitle } from '@elastic/eui'; -import type { VisualizationLayerWidgetProps } from '../../types'; +import type { VisualizationLayerWidgetProps, VisualizationType } from '../../types'; import { State, visualizationTypes } from '../types'; -import { layerTypes } from '../../../common'; import { SeriesType } from '../../../common/expressions'; import { isHorizontalChart, isHorizontalSeries } from '../state_helpers'; import { trackUiEvent } from '../../lens_ui_telemetry'; @@ -18,97 +17,115 @@ import { StaticHeader } from '../../shared_components'; import { ToolbarButton } from '../../../../../../src/plugins/kibana_react/public'; import { LensIconChartBarReferenceLine } from '../../assets/chart_bar_reference_line'; import { updateLayer } from '.'; +import { isReferenceLayer } from '../visualization_helpers'; export function LayerHeader(props: VisualizationLayerWidgetProps) { - const [isPopoverOpen, setPopoverIsOpen] = useState(false); - const { state, layerId } = props; - const horizontalOnly = isHorizontalChart(state.layers); - const index = state.layers.findIndex((l) => l.layerId === layerId); - const layer = state.layers[index]; + const layer = props.state.layers.find((l) => l.layerId === props.layerId); if (!layer) { return null; } - // if it's a reference line just draw a static text - if (layer.layerType === layerTypes.REFERENCELINE) { - return ( - - ); + if (isReferenceLayer(layer)) { + return ; } - const currentVisType = visualizationTypes.find(({ id }) => id === layer.seriesType)!; + return ; +} + +function ReferenceLayerHeader() { + return ( + + ); +} - const createTrigger = function () { - return ( - setPopoverIsOpen(!isPopoverOpen)} - fullWidth - size="s" - > - <> - - - {currentVisType.fullLabel || currentVisType.label} - - - - ); - }; +function DataLayerHeader(props: VisualizationLayerWidgetProps) { + const [isPopoverOpen, setPopoverIsOpen] = useState(false); + const { state, layerId } = props; + const index = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[index]; + const currentVisType = visualizationTypes.find(({ id }) => id === layer.seriesType)!; + const horizontalOnly = isHorizontalChart(state.layers); return ( - <> - setPopoverIsOpen(false)} - display="block" - panelPaddingSize="s" - ownFocus - > - - {i18n.translate('xpack.lens.layerPanel.layerVisualizationType', { - defaultMessage: 'Layer visualization type', - })} - -
- - singleSelection="always" - options={visualizationTypes - .filter((t) => isHorizontalSeries(t.id as SeriesType) === horizontalOnly) - .map((t) => ({ - value: t.id, - key: t.id, - checked: t.id === currentVisType.id ? 'on' : undefined, - prepend: , - label: t.fullLabel || t.label, - 'data-test-subj': `lnsXY_seriesType-${t.id}`, - }))} - onChange={(newOptions) => { - const chosenType = newOptions.find(({ checked }) => checked === 'on'); - if (!chosenType) { - return; - } - const id = chosenType.value!; - trackUiEvent('xy_change_layer_display'); - props.setState(updateLayer(state, { ...layer, seriesType: id as SeriesType }, index)); - setPopoverIsOpen(false); - }} - > - {(list) => <>{list}} - -
-
- + setPopoverIsOpen(!isPopoverOpen)} + currentVisType={currentVisType} + /> + } + isOpen={isPopoverOpen} + closePopover={() => setPopoverIsOpen(false)} + display="block" + panelPaddingSize="s" + ownFocus + > + + {i18n.translate('xpack.lens.layerPanel.layerVisualizationType', { + defaultMessage: 'Layer visualization type', + })} + +
+ + singleSelection="always" + options={visualizationTypes + .filter((t) => isHorizontalSeries(t.id as SeriesType) === horizontalOnly) + .map((t) => ({ + value: t.id, + key: t.id, + checked: t.id === currentVisType.id ? 'on' : undefined, + prepend: , + label: t.fullLabel || t.label, + 'data-test-subj': `lnsXY_seriesType-${t.id}`, + }))} + onChange={(newOptions) => { + const chosenType = newOptions.find(({ checked }) => checked === 'on'); + if (!chosenType) { + return; + } + const id = chosenType.value!; + trackUiEvent('xy_change_layer_display'); + props.setState(updateLayer(state, { ...layer, seriesType: id as SeriesType }, index)); + setPopoverIsOpen(false); + }} + > + {(list) => <>{list}} + +
+
); } + +const DataLayerHeaderTrigger = function ({ + currentVisType, + onClick, +}: { + currentVisType: VisualizationType; + onClick: () => void; +}) { + return ( + + <> + + + {currentVisType.fullLabel || currentVisType.label} + + + + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx index d547471873e54..02cd9d45a4190 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx @@ -5,218 +5,33 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonGroup, EuiComboBox, EuiFormRow, EuiIcon, EuiRange } from '@elastic/eui'; +import { EuiButtonGroup, EuiFormRow } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import type { VisualizationDimensionEditorProps } from '../../types'; import { State, XYState } from '../types'; import { FormatFactory } from '../../../common'; import { YConfig } from '../../../common/expressions'; -import { LineStyle, FillStyle, IconPosition } from '../../../common/expressions/xy_chart'; +import { FillStyle } from '../../../common/expressions/xy_chart'; import { ColorPicker } from './color_picker'; -import { updateLayer, idPrefix } from '.'; -import { TooltipWrapper, useDebouncedValue } from '../../shared_components'; - -const icons = [ - { - value: 'empty', - label: i18n.translate('xpack.lens.xyChart.referenceLine.noIconLabel', { - defaultMessage: 'None', - }), - }, - { - value: 'asterisk', - label: i18n.translate('xpack.lens.xyChart.referenceLine.asteriskIconLabel', { - defaultMessage: 'Asterisk', - }), - }, - { - value: 'bell', - label: i18n.translate('xpack.lens.xyChart.referenceLine.bellIconLabel', { - defaultMessage: 'Bell', - }), - }, - { - value: 'bolt', - label: i18n.translate('xpack.lens.xyChart.referenceLine.boltIconLabel', { - defaultMessage: 'Bolt', - }), - }, - { - value: 'bug', - label: i18n.translate('xpack.lens.xyChart.referenceLine.bugIconLabel', { - defaultMessage: 'Bug', - }), - }, - { - value: 'editorComment', - label: i18n.translate('xpack.lens.xyChart.referenceLine.commentIconLabel', { - defaultMessage: 'Comment', - }), - }, - { - value: 'alert', - label: i18n.translate('xpack.lens.xyChart.referenceLine.alertIconLabel', { - defaultMessage: 'Alert', - }), - }, - { - value: 'flag', - label: i18n.translate('xpack.lens.xyChart.referenceLine.flagIconLabel', { - defaultMessage: 'Flag', - }), - }, - { - value: 'tag', - label: i18n.translate('xpack.lens.xyChart.referenceLine.tagIconLabel', { - defaultMessage: 'Tag', - }), - }, -]; - -const IconView = (props: { value?: string; label: string }) => { - if (!props.value) return null; - return ( - - - {` ${props.label}`} - - ); -}; - -const IconSelect = ({ - value, - onChange, -}: { - value?: string; - onChange: (newIcon: string) => void; -}) => { - const selectedIcon = icons.find((option) => value === option.value) || icons[0]; - - return ( - { - onChange(selection[0].value!); - }} - singleSelection={{ asPlainText: true }} - renderOption={IconView} - compressed - prepend={hasIcon(selectedIcon.value) ? : undefined} - /> - ); -}; - -interface LabelConfigurationOptions { - isHorizontal: boolean; - axisMode: YConfig['axisMode']; -} - -function getFillPositionOptions({ isHorizontal, axisMode }: LabelConfigurationOptions) { - const aboveLabel = i18n.translate('xpack.lens.xyChart.referenceLineFill.above', { - defaultMessage: 'Above', - }); - const belowLabel = i18n.translate('xpack.lens.xyChart.referenceLineFill.below', { - defaultMessage: 'Below', - }); - const beforeLabel = i18n.translate('xpack.lens.xyChart.referenceLineFill.before', { - defaultMessage: 'Before', - }); - const afterLabel = i18n.translate('xpack.lens.xyChart.referenceLineFill.after', { - defaultMessage: 'After', - }); - - const aboveOptionLabel = axisMode !== 'bottom' && !isHorizontal ? aboveLabel : afterLabel; - const belowOptionLabel = axisMode !== 'bottom' && !isHorizontal ? belowLabel : beforeLabel; - - return [ - { - id: `${idPrefix}none`, - label: i18n.translate('xpack.lens.xyChart.referenceLineFill.none', { - defaultMessage: 'None', - }), - 'data-test-subj': 'lnsXY_referenceLine_fill_none', - }, - { - id: `${idPrefix}above`, - label: aboveOptionLabel, - 'data-test-subj': 'lnsXY_referenceLine_fill_above', - }, - { - id: `${idPrefix}below`, - label: belowOptionLabel, - 'data-test-subj': 'lnsXY_referenceLine_fill_below', - }, - ]; -} - -function getIconPositionOptions({ isHorizontal, axisMode }: LabelConfigurationOptions) { - const autoOption = { - id: `${idPrefix}auto`, - label: i18n.translate('xpack.lens.xyChart.referenceLineMarker.auto', { - defaultMessage: 'Auto', - }), - 'data-test-subj': 'lnsXY_referenceLine_markerPosition_auto', - }; - - const topLabel = i18n.translate('xpack.lens.xyChart.markerPosition.above', { - defaultMessage: 'Top', - }); - const bottomLabel = i18n.translate('xpack.lens.xyChart.markerPosition.below', { - defaultMessage: 'Bottom', - }); - const leftLabel = i18n.translate('xpack.lens.xyChart.markerPosition.left', { - defaultMessage: 'Left', - }); - const rightLabel = i18n.translate('xpack.lens.xyChart.markerPosition.right', { - defaultMessage: 'Right', - }); - if (axisMode === 'bottom') { - return [ - { - id: `${idPrefix}below`, - label: isHorizontal ? leftLabel : bottomLabel, - 'data-test-subj': 'lnsXY_referenceLine_markerPosition_below', - }, - autoOption, - { - id: `${idPrefix}above`, - label: isHorizontal ? rightLabel : topLabel, - 'data-test-subj': 'lnsXY_referenceLine_markerPosition_above', - }, - ]; - } - return [ - { - id: `${idPrefix}left`, - label: isHorizontal ? bottomLabel : leftLabel, - 'data-test-subj': 'lnsXY_referenceLine_markerPosition_left', - }, - autoOption, - { - id: `${idPrefix}right`, - label: isHorizontal ? topLabel : rightLabel, - 'data-test-subj': 'lnsXY_referenceLine_markerPosition_right', - }, - ]; -} - -export function hasIcon(icon: string | undefined): icon is string { - return icon != null && icon !== 'empty'; -} +import { updateLayer } from '.'; +import { useDebouncedValue } from '../../shared_components'; +import { idPrefix } from './dimension_editor'; +import { isHorizontalChart } from '../state_helpers'; +import { MarkerDecorationSettings } from './shared/marker_decoration_settings'; +import { LineStyleSettings } from './shared/line_style_settings'; export const ReferenceLinePanel = ( props: VisualizationDimensionEditorProps & { formatFactory: FormatFactory; paletteService: PaletteRegistry; - isHorizontal: boolean; } ) => { - const { state, setState, layerId, accessor, isHorizontal } = props; + const { state, setState, layerId, accessor } = props; + + const isHorizontal = isHorizontalChart(state.layers); const { inputValue: localState, handleInputChange: setLocalState } = useDebouncedValue({ value: state, @@ -226,7 +41,7 @@ export const ReferenceLinePanel = ( const index = localState.layers.findIndex((l) => l.layerId === layerId); const layer = localState.layers[index]; - const setYConfig = useCallback( + const setConfig = useCallback( (yConfig: Partial | undefined) => { if (yConfig == null) { return; @@ -248,192 +63,32 @@ export const ReferenceLinePanel = ( [accessor, index, localState, layer, setLocalState] ); - const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); + const currentConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); return ( <> - - { - setYConfig({ forAccessor: accessor, textVisibility: id === `${idPrefix}name` }); - }} - isFullWidth - /> - - - { - setYConfig({ forAccessor: accessor, icon: newIcon }); - }} - /> - - {hasIcon(currentYConfig?.icon) || currentYConfig?.textVisibility ? ( - - - { - const newMode = id.replace(idPrefix, '') as IconPosition; - setYConfig({ forAccessor: accessor, iconPosition: newMode }); - }} - /> - - - ) : null} - - - { - const newMode = id.replace(idPrefix, '') as LineStyle; - setYConfig({ forAccessor: accessor, lineStyle: newMode }); - }} - /> - - - { - setYConfig({ forAccessor: accessor, lineWidth: value }); - }} - /> - - - { - const newMode = id.replace(idPrefix, '') as FillStyle; - setYConfig({ forAccessor: accessor, fill: newMode }); - }} - /> - + + + @@ -441,57 +96,83 @@ export const ReferenceLinePanel = ( ); }; -const minRange = 1; -const maxRange = 10; +interface LabelConfigurationOptions { + isHorizontal: boolean; + axisMode: YConfig['axisMode']; +} -function getSafeValue(value: number | '', prevValue: number, min: number, max: number) { - if (value === '') { - return prevValue; - } - return Math.max(minRange, Math.min(value, maxRange)); +function getFillPositionOptions({ isHorizontal, axisMode }: LabelConfigurationOptions) { + const aboveLabel = i18n.translate('xpack.lens.xyChart.fill.above', { + defaultMessage: 'Above', + }); + const belowLabel = i18n.translate('xpack.lens.xyChart.fill.below', { + defaultMessage: 'Below', + }); + const beforeLabel = i18n.translate('xpack.lens.xyChart.fill.before', { + defaultMessage: 'Before', + }); + const afterLabel = i18n.translate('xpack.lens.xyChart.fill.after', { + defaultMessage: 'After', + }); + + const aboveOptionLabel = axisMode !== 'bottom' && !isHorizontal ? aboveLabel : afterLabel; + const belowOptionLabel = axisMode !== 'bottom' && !isHorizontal ? belowLabel : beforeLabel; + + return [ + { + id: `${idPrefix}none`, + label: i18n.translate('xpack.lens.xyChart.fill.none', { + defaultMessage: 'None', + }), + 'data-test-subj': 'lnsXY_fill_none', + }, + { + id: `${idPrefix}above`, + label: aboveOptionLabel, + 'data-test-subj': 'lnsXY_fill_above', + }, + { + id: `${idPrefix}below`, + label: belowOptionLabel, + 'data-test-subj': 'lnsXY_fill_below', + }, + ]; } -const LineThicknessSlider = ({ - value, - onChange, +export const FillSetting = ({ + currentConfig, + setConfig, + accessor, + isHorizontal, }: { - value: number; - onChange: (value: number) => void; + currentConfig?: YConfig; + setConfig: (yConfig: Partial | undefined) => void; + accessor: string; + isHorizontal: boolean; }) => { - const [unsafeValue, setUnsafeValue] = useState(String(value)); - return ( - { - setUnsafeValue(newValue); - const convertedValue = newValue === '' ? '' : Number(newValue); - const safeValue = getSafeValue(Number(newValue), Number(newValue), minRange, maxRange); - // only update onChange is the value is valid and in range - if (convertedValue === safeValue) { - onChange(safeValue); - } - }} - onBlur={() => { - if (unsafeValue !== String(value)) { - const safeValue = getSafeValue( - unsafeValue === '' ? unsafeValue : Number(unsafeValue), - value, - minRange, - maxRange - ); - onChange(safeValue); - setUnsafeValue(String(safeValue)); - } - }} - /> + label={i18n.translate('xpack.lens.xyChart.fill.label', { + defaultMessage: 'Fill', + })} + > + { + const newMode = id.replace(idPrefix, '') as FillStyle; + setConfig({ forAccessor: accessor, fill: newMode }); + }} + /> + ); }; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx new file mode 100644 index 0000000000000..fec1d8b37a0e4 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx @@ -0,0 +1,106 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiComboBox, EuiIcon } from '@elastic/eui'; + +export function hasIcon(icon: string | undefined): icon is string { + return icon != null && icon !== 'empty'; +} + +const icons = [ + { + value: 'empty', + label: i18n.translate('xpack.lens.xyChart.iconSelect.noIconLabel', { + defaultMessage: 'None', + }), + }, + { + value: 'asterisk', + label: i18n.translate('xpack.lens.xyChart.iconSelect.asteriskIconLabel', { + defaultMessage: 'Asterisk', + }), + }, + { + value: 'bell', + label: i18n.translate('xpack.lens.xyChart.iconSelect.bellIconLabel', { + defaultMessage: 'Bell', + }), + }, + { + value: 'bolt', + label: i18n.translate('xpack.lens.xyChart.iconSelect.boltIconLabel', { + defaultMessage: 'Bolt', + }), + }, + { + value: 'bug', + label: i18n.translate('xpack.lens.xyChart.iconSelect.bugIconLabel', { + defaultMessage: 'Bug', + }), + }, + { + value: 'editorComment', + label: i18n.translate('xpack.lens.xyChart.iconSelect.commentIconLabel', { + defaultMessage: 'Comment', + }), + }, + { + value: 'alert', + label: i18n.translate('xpack.lens.xyChart.iconSelect.alertIconLabel', { + defaultMessage: 'Alert', + }), + }, + { + value: 'flag', + label: i18n.translate('xpack.lens.xyChart.iconSelect.flagIconLabel', { + defaultMessage: 'Flag', + }), + }, + { + value: 'tag', + label: i18n.translate('xpack.lens.xyChart.iconSelect.tagIconLabel', { + defaultMessage: 'Tag', + }), + }, +]; + +const IconView = (props: { value?: string; label: string }) => { + if (!props.value) return null; + return ( + + + {` ${props.label}`} + + ); +}; + +export const IconSelect = ({ + value, + onChange, +}: { + value?: string; + onChange: (newIcon: string) => void; +}) => { + const selectedIcon = icons.find((option) => value === option.value) || icons[0]; + + return ( + { + onChange(selection[0].value!); + }} + singleSelection={{ asPlainText: true }} + renderOption={IconView} + compressed + prepend={hasIcon(selectedIcon.value) ? : undefined} + /> + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx new file mode 100644 index 0000000000000..33d1f5acaba50 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx @@ -0,0 +1,144 @@ +/* + * 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, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonGroup, EuiFormRow, EuiRange } from '@elastic/eui'; +import { YConfig } from '../../../../common/expressions'; +import { LineStyle } from '../../../../common/expressions/xy_chart'; +import { idPrefix } from '../dimension_editor'; + +export const LineStyleSettings = ({ + currentConfig, + setConfig, + accessor, + isHorizontal, +}: { + currentConfig?: Pick; + setConfig: (yConfig: Partial | undefined) => void; + accessor: string; + isHorizontal: boolean; +}) => { + return ( + <> + + { + const newMode = id.replace(idPrefix, '') as LineStyle; + setConfig({ forAccessor: accessor, lineStyle: newMode }); + }} + /> + + + { + setConfig({ forAccessor: accessor, lineWidth: value }); + }} + /> + + + ); +}; + +const minRange = 1; +const maxRange = 10; + +function getSafeValue(value: number | '', prevValue: number, min: number, max: number) { + if (value === '') { + return prevValue; + } + return Math.max(minRange, Math.min(value, maxRange)); +} + +const LineThicknessSlider = ({ + value, + onChange, +}: { + value: number; + onChange: (value: number) => void; +}) => { + const [unsafeValue, setUnsafeValue] = useState(String(value)); + + return ( + { + setUnsafeValue(newValue); + const convertedValue = newValue === '' ? '' : Number(newValue); + const safeValue = getSafeValue(Number(newValue), Number(newValue), minRange, maxRange); + // only update onChange is the value is valid and in range + if (convertedValue === safeValue) { + onChange(safeValue); + } + }} + onBlur={() => { + if (unsafeValue !== String(value)) { + const safeValue = getSafeValue( + unsafeValue === '' ? unsafeValue : Number(unsafeValue), + value, + minRange, + maxRange + ); + onChange(safeValue); + setUnsafeValue(String(safeValue)); + } + }} + /> + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx new file mode 100644 index 0000000000000..9579cbd1f0c0b --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx @@ -0,0 +1,181 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiButtonGroup, EuiFormRow } from '@elastic/eui'; +import { YConfig } from '../../../../common/expressions'; +import { IconPosition } from '../../../../common/expressions/xy_chart'; + +import { TooltipWrapper } from '../../../shared_components'; +import { hasIcon, IconSelect } from './icon_select'; +import { idPrefix } from '../dimension_editor'; + +interface LabelConfigurationOptions { + isHorizontal: boolean; + axisMode: YConfig['axisMode']; +} + +function getIconPositionOptions({ isHorizontal, axisMode }: LabelConfigurationOptions) { + const autoOption = { + id: `${idPrefix}auto`, + label: i18n.translate('xpack.lens.xyChart.lineMarker.auto', { + defaultMessage: 'Auto', + }), + 'data-test-subj': 'lnsXY_markerPosition_auto', + }; + + const topLabel = i18n.translate('xpack.lens.xyChart.markerPosition.above', { + defaultMessage: 'Top', + }); + const bottomLabel = i18n.translate('xpack.lens.xyChart.markerPosition.below', { + defaultMessage: 'Bottom', + }); + const leftLabel = i18n.translate('xpack.lens.xyChart.markerPosition.left', { + defaultMessage: 'Left', + }); + const rightLabel = i18n.translate('xpack.lens.xyChart.markerPosition.right', { + defaultMessage: 'Right', + }); + if (axisMode === 'bottom') { + return [ + { + id: `${idPrefix}below`, + label: isHorizontal ? leftLabel : bottomLabel, + 'data-test-subj': 'lnsXY_markerPosition_below', + }, + autoOption, + { + id: `${idPrefix}above`, + label: isHorizontal ? rightLabel : topLabel, + 'data-test-subj': 'lnsXY_markerPosition_above', + }, + ]; + } + return [ + { + id: `${idPrefix}left`, + label: isHorizontal ? bottomLabel : leftLabel, + 'data-test-subj': 'lnsXY_markerPosition_left', + }, + autoOption, + { + id: `${idPrefix}right`, + label: isHorizontal ? topLabel : rightLabel, + 'data-test-subj': 'lnsXY_markerPosition_right', + }, + ]; +} + +export const MarkerDecorationSettings = ({ + currentConfig, + setConfig, + accessor, + isHorizontal, +}: { + currentConfig?: Pick; + setConfig: (yConfig: Partial | undefined) => void; + accessor: string; + isHorizontal: boolean; +}) => { + return ( + <> + + { + setConfig({ forAccessor: accessor, textVisibility: id === `${idPrefix}name` }); + }} + isFullWidth + /> + + + { + setConfig({ forAccessor: accessor, icon: newIcon }); + }} + /> + + {hasIcon(currentConfig?.icon) || currentConfig?.textVisibility ? ( + + + { + const newMode = id.replace(idPrefix, '') as IconPosition; + setConfig({ forAccessor: accessor, iconPosition: newMode }); + }} + /> + + + ) : null} + + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx index 7ddb770c1a176..5dea4481a60e5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx @@ -8,7 +8,8 @@ import React from 'react'; import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { EuiButtonGroupProps, EuiButtonGroup } from '@elastic/eui'; -import { LayerContextMenu, XyToolbar, DimensionEditor } from '.'; +import { XyToolbar } from '.'; +import { DimensionEditor } from './dimension_editor'; import { AxisSettingsPopover } from './axis_settings_popover'; import { FramePublicAPI } from '../../types'; import { State } from '../types'; @@ -46,61 +47,6 @@ describe('XY Config panels', () => { }; }); - describe('LayerContextMenu', () => { - test('enables stacked chart types even when there is no split series', () => { - const state = testState(); - const component = mount( - - ); - - const options = component - .find(EuiButtonGroup) - .first() - .prop('options') as EuiButtonGroupProps['options']; - - expect(options!.map(({ id }) => id)).toEqual([ - 'bar', - 'bar_stacked', - 'bar_percentage_stacked', - 'area', - 'area_stacked', - 'area_percentage_stacked', - 'line', - ]); - - expect(options!.filter(({ isDisabled }) => isDisabled).map(({ id }) => id)).toEqual([]); - }); - - test('shows only horizontal bar options when in horizontal mode', () => { - const state = testState(); - const component = mount( - - ); - - const options = component - .find(EuiButtonGroup) - .first() - .prop('options') as EuiButtonGroupProps['options']; - - expect(options!.map(({ id }) => id)).toEqual([ - 'bar_horizontal', - 'bar_horizontal_stacked', - 'bar_horizontal_percentage_stacked', - ]); - expect(options!.filter(({ isDisabled }) => isDisabled).map(({ id }) => id)).toEqual([]); - }); - }); - describe('XyToolbar', () => { it('should disable the popover if there is no right axis', () => { const state = testState(); diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.test.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.test.ts index 684be2de2e030..37e3201dcfe42 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.test.ts @@ -24,6 +24,7 @@ import { PromiseStream } from '../../import_exception_list_and_items'; import { createExceptionsStreamFromNdjson, exceptionsChecksFromArray, + manageExceptionComments, } from './create_exceptions_stream_logic'; describe('create_exceptions_stream_logic', () => { @@ -338,4 +339,49 @@ describe('create_exceptions_stream_logic', () => { }); }); }); + + describe('manageExceptionComments', () => { + test('returns empty array if passed in "comments" undefined', () => { + const result = manageExceptionComments(undefined); + expect(result).toEqual([]); + }); + + test('returns empty array if passed in "comments" empty array', () => { + const result = manageExceptionComments([]); + expect(result).toEqual([]); + }); + + test('returns formatted existing comment', () => { + const result = manageExceptionComments([ + { + comment: 'some old comment', + created_at: '2020-04-20T15:25:31.830Z', + created_by: 'kibana', + id: 'uuid_here', + updated_at: '2020-05-20T15:25:31.830Z', + updated_by: 'lily', + }, + ]); + + expect(result).toEqual([ + { + comment: 'some old comment', + }, + ]); + }); + + test('returns formatted new comment', () => { + const result = manageExceptionComments([ + { + comment: 'some new comment', + }, + ]); + + expect(result).toEqual([ + { + comment: 'some new comment', + }, + ]); + }); + }); }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.ts index af39936b26142..eb009d2492b6a 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/import/create_exceptions_stream_logic.ts @@ -12,7 +12,9 @@ import { has } from 'lodash/fp'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { + CreateCommentsArray, ExportExceptionDetails, + ImportCommentsArray, ImportExceptionListItemSchema, ImportExceptionListItemSchemaDecoded, ImportExceptionListSchemaDecoded, @@ -120,6 +122,24 @@ export const sortExceptionsStream = (): Transform => { ); }; +/** + * Updates any comments associated with exception items to resemble + * comment creation schema. + * See issue for context https://github.com/elastic/kibana/issues/124742#issuecomment-1033082093 + * @returns {array} comments reformatted properly for import + */ +export const manageExceptionComments = ( + comments: ImportCommentsArray | undefined +): CreateCommentsArray => { + if (comments == null || !comments.length) { + return []; + } else { + return comments.map(({ comment }) => ({ + comment, + })); + } +}; + /** * * Validating exceptions logic @@ -206,8 +226,9 @@ export const validateExceptionsItems = ( return items.map((item: ImportExceptionListItemSchema | Error) => { if (!(item instanceof Error)) { - const decodedItem = importExceptionListItemSchema.decode(item); - const checkedItem = exactCheck(item, decodedItem); + const itemWithUpdatedComments = { ...item, comments: manageExceptionComments(item.comments) }; + const decodedItem = importExceptionListItemSchema.decode(itemWithUpdatedComments); + const checkedItem = exactCheck(itemWithUpdatedComments, decodedItem); return pipe(checkedItem, fold(onLeft, onRight)); } else { diff --git a/x-pack/plugins/maps/common/embeddable/extract.test.ts b/x-pack/plugins/maps/common/embeddable/extract.test.ts new file mode 100644 index 0000000000000..b7440d5f4a098 --- /dev/null +++ b/x-pack/plugins/maps/common/embeddable/extract.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { extract } from './extract'; + +test('Should return original state and empty references with by-reference embeddable state', () => { + const mapByReferenceInput = { + id: '2192e502-0ec7-4316-82fb-c9bbf78525c4', + type: 'map', + }; + + expect(extract!(mapByReferenceInput)).toEqual({ + state: mapByReferenceInput, + references: [], + }); +}); + +test('Should update state with refNames with by-value embeddable state', () => { + const mapByValueInput = { + id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20', + attributes: { + layerListJSON: + '[{"sourceDescriptor":{"indexPatternId":"90943e30-9a47-11e8-b64d-95841ca0b247"}}]', + }, + type: 'map', + }; + + expect(extract!(mapByValueInput)).toEqual({ + references: [ + { + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + name: 'layer_0_source_index_pattern', + type: 'index-pattern', + }, + ], + state: { + id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20', + attributes: { + layerListJSON: + '[{"sourceDescriptor":{"indexPatternRefName":"layer_0_source_index_pattern"}}]', + }, + type: 'map', + }, + }); +}); diff --git a/x-pack/plugins/maps/common/embeddable/extract.ts b/x-pack/plugins/maps/common/embeddable/extract.ts new file mode 100644 index 0000000000000..54cbefadaaede --- /dev/null +++ b/x-pack/plugins/maps/common/embeddable/extract.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server'; +import { MapEmbeddablePersistableState } from './types'; +import { MapSavedObjectAttributes } from '../map_saved_object_type'; +import { extractReferences } from '../migrations/references'; + +export const extract: EmbeddableRegistryDefinition['extract'] = (state) => { + const typedState = state as MapEmbeddablePersistableState; + + // by-reference embeddable + if (!('attributes' in typedState) || typedState.attributes === undefined) { + // No references to extract for by-reference embeddable since all references are stored with by-reference saved object + return { state, references: [] }; + } + + // by-value embeddable + const { attributes, references } = extractReferences({ + attributes: typedState.attributes as MapSavedObjectAttributes, + }); + + return { + state: { + ...state, + attributes, + }, + references, + }; +}; diff --git a/x-pack/plugins/maps/common/embeddable/index.ts b/x-pack/plugins/maps/common/embeddable/index.ts new file mode 100644 index 0000000000000..16577df879fbc --- /dev/null +++ b/x-pack/plugins/maps/common/embeddable/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 { extract } from './extract'; +export { inject } from './inject'; diff --git a/x-pack/plugins/maps/common/embeddable/inject.test.ts b/x-pack/plugins/maps/common/embeddable/inject.test.ts new file mode 100644 index 0000000000000..2a9a162c948dd --- /dev/null +++ b/x-pack/plugins/maps/common/embeddable/inject.test.ts @@ -0,0 +1,51 @@ +/* + * 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 { inject } from './inject'; + +test('Should return original state with by-reference embeddable state', () => { + const mapByReferenceInput = { + id: '2192e502-0ec7-4316-82fb-c9bbf78525c4', + type: 'map', + }; + + const refernces = [ + { + name: 'panel_2192e502-0ec7-4316-82fb-c9bbf78525c4', + type: 'map', + id: '7f92d7d0-8e5f-11ec-9477-312c8a6de896', + }, + ]; + + expect(inject!(mapByReferenceInput, refernces)).toEqual(mapByReferenceInput); +}); + +test('Should inject refNames with by-value embeddable state', () => { + const mapByValueInput = { + id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20', + attributes: { + layerListJSON: + '[{"sourceDescriptor":{"indexPatternRefName":"layer_0_source_index_pattern"}}]', + }, + type: 'map', + }; + const refernces = [ + { + name: 'layer_0_source_index_pattern', + type: 'index-pattern', + id: 'changed_index_pattern_id', + }, + ]; + + expect(inject!(mapByValueInput, refernces)).toEqual({ + id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20', + attributes: { + layerListJSON: '[{"sourceDescriptor":{"indexPatternId":"changed_index_pattern_id"}}]', + }, + type: 'map', + }); +}); diff --git a/x-pack/plugins/maps/common/embeddable/inject.ts b/x-pack/plugins/maps/common/embeddable/inject.ts new file mode 100644 index 0000000000000..d8d5da2568df9 --- /dev/null +++ b/x-pack/plugins/maps/common/embeddable/inject.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 { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server'; +import { MapEmbeddablePersistableState } from './types'; +import { MapSavedObjectAttributes } from '../map_saved_object_type'; +import { extractReferences, injectReferences } from '../migrations/references'; + +export const inject: EmbeddableRegistryDefinition['inject'] = (state, references) => { + const typedState = state as MapEmbeddablePersistableState; + + // by-reference embeddable + if (!('attributes' in typedState) || typedState.attributes === undefined) { + return typedState; + } + + // by-value embeddable + try { + // run embeddable state through extract logic to ensure any state with hard coded ids is replace with refNames + // refName generation will produce consistent values allowing inject logic to then replace refNames with current ids. + const { attributes: attributesWithNoHardCodedIds } = extractReferences({ + attributes: typedState.attributes as MapSavedObjectAttributes, + }); + + const { attributes: attributesWithInjectedIds } = injectReferences({ + attributes: attributesWithNoHardCodedIds, + references, + }); + return { + ...typedState, + attributes: attributesWithInjectedIds, + }; + } catch (error) { + // inject exception prevents entire dashboard from display + // Instead of throwing, swallow error and let dashboard display + // Errors will surface in map panel. Any layer that failed injection will surface the error in the legend + // Users can then manually edit map to resolve any problems. + return typedState; + } +}; diff --git a/x-pack/plugins/maps/common/embeddable/types.ts b/x-pack/plugins/maps/common/embeddable/types.ts new file mode 100644 index 0000000000000..5684ea83e0d54 --- /dev/null +++ b/x-pack/plugins/maps/common/embeddable/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SerializableRecord } from '@kbn/utility-types'; +import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; + +export type MapEmbeddablePersistableState = EmbeddableStateWithType & { + attributes: SerializableRecord; +}; diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts index a4ce76b702d13..4a2048fc40e63 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -6,16 +6,15 @@ */ import { i18n } from '@kbn/i18n'; -import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; import { EmbeddableFactoryDefinition, IContainer, } from '../../../../../src/plugins/embeddable/public'; import { MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; import { getMapEmbeddableDisplayName } from '../../common/i18n_getters'; -import { MapByReferenceInput, MapEmbeddableInput, MapByValueInput } from './types'; +import { extract, inject } from '../../common/embeddable'; +import { MapByReferenceInput, MapEmbeddableInput } from './types'; import { lazyLoadMapModules } from '../lazy_load_bundle'; -import { extractReferences } from '../../common/migrations/references'; export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { type = MAP_SAVED_OBJECT_TYPE; @@ -63,17 +62,7 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { ); }; - extract(state: EmbeddableStateWithType) { - const maybeMapByValueInput = state as EmbeddableStateWithType | MapByValueInput; + inject = inject; - if ((maybeMapByValueInput as MapByValueInput).attributes !== undefined) { - const { references } = extractReferences({ - attributes: (maybeMapByValueInput as MapByValueInput).attributes, - }); - - return { state, references }; - } - - return { state, references: [] }; - } + extract = extract; } diff --git a/x-pack/plugins/maps/public/util.ts b/x-pack/plugins/maps/public/util.ts index a1118c3e3cec6..4adb8b35bfcea 100644 --- a/x-pack/plugins/maps/public/util.ts +++ b/x-pack/plugins/maps/public/util.ts @@ -32,12 +32,20 @@ export async function getEmsTmsServices(): Promise { return (await getEMSClient()).getTMSServices(); } -let emsClient: EMSClient | null = null; +let emsClientPromise: Promise | null = null; let latestLicenseId: string | undefined; async function getEMSClient(): Promise { - if (!emsClient) { - emsClient = await getMapsEmsStart().createEMSClient(); + if (!emsClientPromise) { + emsClientPromise = new Promise(async (resolve, reject) => { + try { + const emsClient = await getMapsEmsStart().createEMSClient(); + resolve(emsClient); + } catch (error) { + reject(error); + } + }); } + const emsClient = await emsClientPromise; const licenseId = getLicenseId(); if (latestLicenseId !== licenseId) { latestLicenseId = licenseId; diff --git a/x-pack/plugins/maps/server/embeddable_migrations.test.ts b/x-pack/plugins/maps/server/embeddable_migrations.test.ts index 306f716d5171d..4cf2642bb545c 100644 --- a/x-pack/plugins/maps/server/embeddable_migrations.test.ts +++ b/x-pack/plugins/maps/server/embeddable_migrations.test.ts @@ -15,7 +15,10 @@ describe('saved object migrations and embeddable migrations', () => { const savedObjectMigrationVersions = Object.keys(savedObjectMigrations).filter((version) => { return semverGte(version, '7.13.0'); }); - const embeddableMigrationVersions = Object.keys(embeddableMigrations); + const embeddableMigrationVersions = Object.keys(embeddableMigrations).filter((key) => { + // filter out embeddable only migration keys + return !['8.0.1'].includes(key); + }); expect(savedObjectMigrationVersions.sort()).toEqual(embeddableMigrationVersions.sort()); }); }); diff --git a/x-pack/plugins/maps/server/embeddable_migrations.ts b/x-pack/plugins/maps/server/embeddable_migrations.ts index f5356e5eb29a5..9c17889e0c33c 100644 --- a/x-pack/plugins/maps/server/embeddable_migrations.ts +++ b/x-pack/plugins/maps/server/embeddable_migrations.ts @@ -10,6 +10,7 @@ import { MapSavedObjectAttributes } from '../common/map_saved_object_type'; import { moveAttribution } from '../common/migrations/move_attribution'; import { setEmsTmsDefaultModes } from '../common/migrations/set_ems_tms_default_modes'; import { renameLayerTypes } from '../common/migrations/rename_layer_types'; +import { extractReferences } from '../common/migrations/references'; /* * Embeddables such as Maps, Lens, and Visualize can be embedded by value or by reference on a dashboard. @@ -26,8 +27,8 @@ export const embeddableMigrations = { attributes: moveAttribution(state as { attributes: MapSavedObjectAttributes }), } as SerializableRecord; } catch (e) { - // Do not fail migration for invalid layerListJSON - // Maps application can display invalid layerListJSON error when saved object is viewed + // Do not fail migration + // Maps application can display error when viewed return state; } }, @@ -38,8 +39,21 @@ export const embeddableMigrations = { attributes: setEmsTmsDefaultModes(state as { attributes: MapSavedObjectAttributes }), } as SerializableRecord; } catch (e) { - // Do not fail migration for invalid layerListJSON - // Maps application can display invalid layerListJSON error when saved object is viewed + // Do not fail migration + // Maps application can display error when viewed + return state; + } + }, + '8.0.1': (state: SerializableRecord) => { + try { + const { attributes } = extractReferences(state as { attributes: MapSavedObjectAttributes }); + return { + ...state, + attributes, + } as SerializableRecord; + } catch (e) { + // Do not fail migration + // Maps application can display error when viewed return state; } }, @@ -50,8 +64,8 @@ export const embeddableMigrations = { attributes: renameLayerTypes(state as { attributes: MapSavedObjectAttributes }), } as SerializableRecord; } catch (e) { - // Do not fail migration for invalid layerListJSON - // Maps application can display invalid layerListJSON error when saved object is viewed + // Do not fail migration + // Maps application can display error when viewed return state; } }, diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 48b3969043566..92d0f08fb51ab 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -22,6 +22,7 @@ import { getFlightsSavedObjects } from './sample_data/flights_saved_objects.js'; import { getWebLogsSavedObjects } from './sample_data/web_logs_saved_objects.js'; import { registerMapsUsageCollector } from './maps_telemetry/collectors/register'; import { APP_ID, APP_ICON, MAP_SAVED_OBJECT_TYPE, getFullPath } from '../common/constants'; +import { extract, inject } from '../common/embeddable'; import { mapSavedObjects, mapsTelemetrySavedObjects } from './saved_objects'; import { MapsXPackConfig } from '../config'; import { setStartServices } from './kibana_server_services'; @@ -198,6 +199,8 @@ export class MapsPlugin implements Plugin { plugins.embeddable.registerEmbeddableFactory({ id: MAP_SAVED_OBJECT_TYPE, migrations: embeddableMigrations, + inject, + extract, }); return { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/create_dataset_query_filter.ts b/x-pack/plugins/monitoring/server/lib/alerts/create_dataset_query_filter.ts index 7d23240bc1e94..d4d02d4aec349 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/create_dataset_query_filter.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/create_dataset_query_filter.ts @@ -5,12 +5,33 @@ * 2.0. */ -export const createDatasetFilter = (legacyType: string, dataset: string) => ({ +/** + * We expect that metricset and dataset will be aligned where dataset + * is the full {product}.{metricset}, whereas metricset doesn't include + * the product, e.g. dataset is elasticsearch.cluster_stats and metricset is + * just cluster_stats. + * + * Unfortunately, this doesn't *always* seem to be the case, and sometimes + * the "metricset" value is different. For this reason, we've left these + * two as separate arguments to this function, at least until this is resolved. + * + * More info: https://github.com/elastic/kibana/pull/119112/files#r772605936 + * + * @param {string} type matches legacy data + * @param {string} metricset matches standalone beats + * @param {string} dataset matches agent integration data streams + */ +export const createDatasetFilter = (type: string, metricset: string, dataset: string) => ({ bool: { should: [ { term: { - type: legacyType, + type, + }, + }, + { + term: { + 'metricset.name': metricset, }, }, { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.test.ts index b399f6a876385..dbfebef6bfe42 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.test.ts @@ -47,15 +47,39 @@ describe('fetchCCReadExceptions', () => { bool: { filter: [ { - nested: { - path: 'ccr_stats.read_exceptions', - query: { exists: { field: 'ccr_stats.read_exceptions.exception' } }, + bool: { + should: [ + { + nested: { + ignore_unmapped: true, + path: 'ccr_stats.read_exceptions', + query: { + exists: { + field: 'ccr_stats.read_exceptions.exception', + }, + }, + }, + }, + { + nested: { + ignore_unmapped: true, + path: 'elasticsearch.ccr.read_exceptions', + query: { + exists: { + field: 'elasticsearch.ccr.read_exceptions.exception', + }, + }, + }, + }, + ], + minimum_should_match: 1, }, }, { bool: { should: [ { term: { type: 'ccr_stats' } }, + { term: { 'metricset.name': 'ccr' } }, { term: { 'data_stream.dataset': 'elasticsearch.ccr' } }, ], minimum_should_match: 1, @@ -82,9 +106,13 @@ describe('fetchCCReadExceptions', () => { _source: { includes: [ 'cluster_uuid', + 'elasticsearch.cluster.id', 'ccr_stats.read_exceptions', + 'elasticsearch.ccr.read_exceptions', 'ccr_stats.shard_id', + 'elasticsearch.ccr.shard_id', 'ccr_stats.leader_index', + 'elasticsearch.ccr.leader.index', ], }, size: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts index cdc136ffd5972..f6071d84f20d2 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts @@ -35,16 +35,35 @@ export async function fetchCCRReadExceptions( bool: { filter: [ { - nested: { - path: 'ccr_stats.read_exceptions', - query: { - exists: { - field: 'ccr_stats.read_exceptions.exception', + bool: { + should: [ + { + nested: { + ignore_unmapped: true, + path: 'ccr_stats.read_exceptions', + query: { + exists: { + field: 'ccr_stats.read_exceptions.exception', + }, + }, + }, }, - }, + { + nested: { + ignore_unmapped: true, + path: 'elasticsearch.ccr.read_exceptions', + query: { + exists: { + field: 'elasticsearch.ccr.read_exceptions.exception', + }, + }, + }, + }, + ], + minimum_should_match: 1, }, }, - createDatasetFilter('ccr_stats', 'elasticsearch.ccr'), + createDatasetFilter('ccr_stats', 'ccr', 'elasticsearch.ccr'), { range: { timestamp: { @@ -83,9 +102,13 @@ export async function fetchCCRReadExceptions( _source: { includes: [ 'cluster_uuid', + 'elasticsearch.cluster.id', 'ccr_stats.read_exceptions', + 'elasticsearch.ccr.read_exceptions', 'ccr_stats.shard_id', + 'elasticsearch.ccr.shard_id', 'ccr_stats.leader_index', + 'elasticsearch.ccr.leader.index', ], }, size: 1, @@ -123,15 +146,19 @@ export async function fetchCCRReadExceptions( for (const followerIndexBucket of followerIndicesBuckets) { const followerIndex = followerIndexBucket.key; - const { - _index: monitoringIndexName, - _source: { ccr_stats: ccrStats, cluster_uuid: clusterUuid }, - } = get(followerIndexBucket, 'hits.hits.hits[0]'); - const { - read_exceptions: readExceptions, - leader_index: leaderIndex, - shard_id: shardId, - } = ccrStats; + const clusterUuid = + get(followerIndexBucket, 'hits.hits.hits[0]._source.cluster_uuid') || + get(followerIndexBucket, 'hits.hits.hits[0]_source.elasticsearch.cluster.id'); + + const monitoringIndexName = get(followerIndexBucket, 'hits.hits.hits[0]._index'); + const ccrStats = + get(followerIndexBucket, 'hits.hits.hits[0]._source.ccr_stats') || + get(followerIndexBucket, 'hits.hits.hits[0]._source.elasticsearch.ccr'); + + const { read_exceptions: readExceptions, shard_id: shardId } = ccrStats; + + const leaderIndex = ccrStats.leaderIndex || ccrStats.leader.index; + const { exception: lastReadException } = readExceptions[readExceptions.length - 1]; stats.push({ diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts index 596d95cff93f8..bb2513112af47 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.test.ts @@ -65,7 +65,9 @@ describe('fetchClusterHealth', () => { '*:.monitoring-es-*,.monitoring-es-*,*:metrics-elasticsearch.cluster_stats-*,metrics-elasticsearch.cluster_stats-*', filter_path: [ 'hits.hits._source.cluster_state.status', + 'hits.hits._source.elasticsearch.cluster.stats.status', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', 'hits.hits._index', ], body: { @@ -79,6 +81,7 @@ describe('fetchClusterHealth', () => { bool: { should: [ { term: { type: 'cluster_stats' } }, + { term: { 'metricset.name': 'cluster_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.cluster_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts index 7103014cb199e..5156a865a80e8 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts @@ -27,7 +27,9 @@ export async function fetchClusterHealth( index: indexPatterns, filter_path: [ 'hits.hits._source.cluster_state.status', + 'hits.hits._source.elasticsearch.cluster.stats.status', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', 'hits.hits._index', ], body: { @@ -48,7 +50,7 @@ export async function fetchClusterHealth( cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, - createDatasetFilter('cluster_stats', 'elasticsearch.cluster_stats'), + createDatasetFilter('cluster_stats', 'cluster_stats', 'elasticsearch.cluster_stats'), { range: { timestamp: { @@ -77,8 +79,9 @@ export async function fetchClusterHealth( const response = await esClient.search(params); return (response.hits?.hits ?? []).map((hit) => { return { - health: hit._source!.cluster_state?.status, - clusterUuid: hit._source!.cluster_uuid, + health: + hit._source!.cluster_state?.status || hit._source!.elasticsearch?.cluster?.stats?.status, + clusterUuid: hit._source!.cluster_uuid || hit._source!.elasticsearch?.cluster?.id, ccs: hit._index.includes(':') ? hit._index.split(':')[0] : undefined, } as AlertClusterHealth; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts index 1eeedf3a7a574..85a027d9cde1d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts @@ -87,7 +87,9 @@ describe('fetchClusters', () => { filter_path: [ 'hits.hits._source.cluster_settings.cluster.metadata.display_name', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', 'hits.hits._source.cluster_name', + 'hits.hits._source.elasticsearch.cluster.name', ], body: { size: 1000, @@ -98,6 +100,7 @@ describe('fetchClusters', () => { bool: { should: [ { term: { type: 'cluster_stats' } }, + { term: { 'metricset.name': 'cluster_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.cluster_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts index ac6520eb3948d..81eb6b724834c 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -35,14 +35,16 @@ export async function fetchClusters( filter_path: [ 'hits.hits._source.cluster_settings.cluster.metadata.display_name', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', 'hits.hits._source.cluster_name', + 'hits.hits._source.elasticsearch.cluster.name', ], body: { size: 1000, query: { bool: { filter: [ - createDatasetFilter('cluster_stats', 'elasticsearch.cluster_stats'), + createDatasetFilter('cluster_stats', 'cluster_stats', 'elasticsearch.cluster_stats'), { range: rangeFilter, }, @@ -56,59 +58,16 @@ export async function fetchClusters( }; const response = await esClient.search(params); - return get(response, 'hits.hits', []).map((hit: any) => { - const clusterName: string = - get(hit, '_source.cluster_settings.cluster.metadata.display_name') || - get(hit, '_source.cluster_name') || - get(hit, '_source.cluster_uuid'); - return { - clusterUuid: get(hit, '_source.cluster_uuid'), - clusterName, - }; - }); -} -export async function fetchClustersLegacy( - callCluster: any, - index: string, - rangeFilter: RangeFilter = { timestamp: { gte: 'now-2m' } } -): Promise { - const params = { - index, - filter_path: [ - 'hits.hits._source.cluster_settings.cluster.metadata.display_name', - 'hits.hits._source.cluster_uuid', - 'hits.hits._source.cluster_name', - ], - body: { - size: 1000, - query: { - bool: { - filter: [ - { - term: { - type: 'cluster_stats', - }, - }, - { - range: rangeFilter, - }, - ], - }, - }, - collapse: { - field: 'cluster_uuid', - }, - }, - }; - const response = await callCluster('search', params); return get(response, 'hits.hits', []).map((hit: any) => { const clusterName: string = get(hit, '_source.cluster_settings.cluster.metadata.display_name') || get(hit, '_source.cluster_name') || - get(hit, '_source.cluster_uuid'); + get(hit, '_source.elasticsearch.cluster.name') || + get(hit, '_source.cluster_uuid') || + get(hit, '_source.elasticsearch.cluster.id'); return { - clusterUuid: get(hit, '_source.cluster_uuid'), + clusterUuid: get(hit, '_source.cluster_uuid') || get(hit, '_source.elasticsearch.cluster.id'), clusterName, }; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts index b77c2ff686ca8..028cff224afad 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts @@ -227,6 +227,7 @@ describe('fetchCpuUsageNodeStats', () => { bool: { should: [ { term: { type: 'node_stats' } }, + { term: { 'metricset.name': 'node_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.node_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts index aef39d09e26b2..b831db2315e58 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts @@ -58,7 +58,7 @@ export async function fetchCpuUsageNodeStats( cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, - createDatasetFilter('node_stats', 'elasticsearch.node_stats'), + createDatasetFilter('node_stats', 'node_stats', 'elasticsearch.node_stats'), { range: { timestamp: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts index 9e53c85e743fe..5126e2d2c408d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts @@ -101,6 +101,7 @@ describe('fetchDiskUsageNodeStats', () => { bool: { should: [ { term: { type: 'node_stats' } }, + { term: { 'metricset.name': 'node_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.node_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts index 6ea9ad5140158..26512b88a08a9 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts @@ -40,7 +40,7 @@ export async function fetchDiskUsageNodeStats( cluster_uuid: clustersIds, }, }, - createDatasetFilter('node_stats', 'elasticsearch.node_stats'), + createDatasetFilter('node_stats', 'node_stats', 'elasticsearch.node_stats'), { range: { timestamp: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts index 8f34136b70b68..b29dd722366e0 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.test.ts @@ -70,8 +70,10 @@ describe('fetchElasticsearchVersions', () => { '*:.monitoring-es-*,.monitoring-es-*,*:metrics-elasticsearch.cluster_stats-*,metrics-elasticsearch.cluster_stats-*', filter_path: [ 'hits.hits._source.cluster_stats.nodes.versions', + 'hits.hits._source.elasticsearch.cluster.stats.nodes.versions', 'hits.hits._index', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', ], body: { size: 1, @@ -84,6 +86,7 @@ describe('fetchElasticsearchVersions', () => { bool: { should: [ { term: { type: 'cluster_stats' } }, + { term: { 'metricset.name': 'cluster_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.cluster_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts index fc5d0cf1cf056..9816b6facc43f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts @@ -28,8 +28,10 @@ export async function fetchElasticsearchVersions( index: indexPatterns, filter_path: [ 'hits.hits._source.cluster_stats.nodes.versions', + 'hits.hits._source.elasticsearch.cluster.stats.nodes.versions', 'hits.hits._index', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', ], body: { size: clusters.length, @@ -49,7 +51,7 @@ export async function fetchElasticsearchVersions( cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, - createDatasetFilter('cluster_stats', 'elasticsearch.cluster_stats'), + createDatasetFilter('cluster_stats', 'cluster_stats', 'elasticsearch.cluster_stats'), { range: { timestamp: { @@ -77,10 +79,13 @@ export async function fetchElasticsearchVersions( const response = await esClient.search(params); return (response.hits?.hits ?? []).map((hit) => { - const versions = hit._source!.cluster_stats?.nodes?.versions ?? []; + const versions = + hit._source!.cluster_stats?.nodes?.versions ?? + hit._source!.elasticsearch?.cluster?.stats?.nodes?.versions ?? + []; return { versions, - clusterUuid: hit._source!.cluster_uuid, + clusterUuid: hit._source!.elasticsearch?.cluster?.id || hit._source!.cluster_uuid, ccs: hit._index.includes(':') ? hit._index.split(':')[0] : undefined, }; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.test.ts index e7020360113e8..41e84be80d2d5 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.test.ts @@ -161,6 +161,7 @@ describe('fetchIndexShardSize', () => { bool: { should: [ { term: { type: 'index_stats' } }, + { term: { 'metricset.name': 'index' } }, { term: { 'data_stream.dataset': 'elasticsearch.index' } }, ], minimum_should_match: 1, @@ -185,6 +186,8 @@ describe('fetchIndexShardSize', () => { '_index', 'index_stats.shards.primaries', 'index_stats.primaries.store.size_in_bytes', + 'elasticsearch.index.shards.primaries', + 'elasticsearch.index.primaries.store.size_in_bytes', ], }, size: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts index 539b6c20c1e37..5afa9ed007f66 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts @@ -50,7 +50,7 @@ export async function fetchIndexShardSize( query: { bool: { filter: [ - createDatasetFilter('index_stats', 'elasticsearch.index'), + createDatasetFilter('index_stats', 'index', 'elasticsearch.index'), { range: { timestamp: { @@ -90,6 +90,8 @@ export async function fetchIndexShardSize( '_index', 'index_stats.shards.primaries', 'index_stats.primaries.store.size_in_bytes', + 'elasticsearch.index.shards.primaries', + 'elasticsearch.index.primaries.store.size_in_bytes', ], }, size: 1, @@ -131,10 +133,8 @@ export async function fetchIndexShardSize( if (!topHit || !ESGlobPatterns.isValid(shardIndex, validIndexPatterns)) { continue; } - const { - _index: monitoringIndexName, - _source: { index_stats: indexStats }, - } = topHit; + const { _index: monitoringIndexName, _source } = topHit; + const indexStats = _source.index_stats || _source.elasticsearch?.index; if (!indexStats || !indexStats.primaries) { continue; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts index 54a7f308fea67..07403388c96f8 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts @@ -102,6 +102,7 @@ describe('fetchKibanaVersions', () => { bool: { should: [ { term: { type: 'kibana_stats' } }, + { term: { 'metricset.name': 'stats' } }, { term: { 'data_stream.dataset': 'kibana.stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts index 886e96ae3331d..b5ef5d1ade372 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts @@ -41,7 +41,7 @@ export async function fetchKibanaVersions( cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, - createDatasetFilter('kibana_stats', 'kibana.stats'), + createDatasetFilter('kibana_stats', 'stats', 'kibana.stats'), { range: { timestamp: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts index 98f52188f0fb6..35fc50a6014b6 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts @@ -84,7 +84,9 @@ describe('fetchLicenses', () => { '*:.monitoring-es-*,.monitoring-es-*,*:metrics-elasticsearch.cluster_stats-*,metrics-elasticsearch.cluster_stats-*', filter_path: [ 'hits.hits._source.license.*', + 'hits.hits._source.elasticsearch.cluster.stats.license.*', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', 'hits.hits._index', ], body: { @@ -98,6 +100,7 @@ describe('fetchLicenses', () => { bool: { should: [ { term: { type: 'cluster_stats' } }, + { term: { 'metricset.name': 'cluster_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.cluster_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 10537fdbaacee..32bbe271da76f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -27,7 +27,9 @@ export async function fetchLicenses( index: indexPatterns, filter_path: [ 'hits.hits._source.license.*', + 'hits.hits._source.elasticsearch.cluster.stats.license.*', 'hits.hits._source.cluster_uuid', + 'hits.hits._source.elasticsearch.cluster.id', 'hits.hits._index', ], body: { @@ -48,7 +50,7 @@ export async function fetchLicenses( cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, - createDatasetFilter('cluster_stats', 'elasticsearch.cluster_stats'), + createDatasetFilter('cluster_stats', 'cluster_stats', 'elasticsearch.cluster_stats'), { range: { timestamp: { @@ -77,12 +79,13 @@ export async function fetchLicenses( const response = await esClient.search(params); return ( response?.hits?.hits.map((hit) => { - const rawLicense = hit._source!.license ?? {}; + const rawLicense = + hit._source!.license ?? hit._source?.elasticsearch?.cluster?.stats?.license ?? {}; const license: AlertLicense = { status: rawLicense.status ?? '', type: rawLicense.type ?? '', expiryDateMS: rawLicense.expiry_date_in_millis ?? 0, - clusterUuid: hit._source!.cluster_uuid, + clusterUuid: hit._source?.elasticsearch?.cluster?.id || hit._source!.cluster_uuid, ccs: hit._index, }; return license; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts index 00f48f1ac27c7..cdb7287ccb77c 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts @@ -107,6 +107,7 @@ describe('fetchLogstashVersions', () => { bool: { should: [ { term: { type: 'logstash_stats' } }, + { term: { 'metricset.name': 'node_stats' } }, { term: { 'data_stream.dataset': 'logstash.node_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts index 7f36572326742..2a0a4a283a68d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts @@ -41,7 +41,7 @@ export async function fetchLogstashVersions( cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, - createDatasetFilter('logstash_stats', 'logstash.node_stats'), + createDatasetFilter('logstash_stats', 'node_stats', 'logstash.node_stats'), { range: { timestamp: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.test.ts index 488818b3167e4..64fbe542cd35b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.test.ts @@ -130,6 +130,7 @@ describe('fetchMemoryUsageNodeStats', () => { bool: { should: [ { term: { type: 'node_stats' } }, + { term: { 'metricset.name': 'node_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.node_stats' } }, ], minimum_should_match: 1, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts index 01d2b97ebac75..644ef03bd3ec4 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts @@ -41,7 +41,7 @@ export async function fetchMemoryUsageNodeStats( cluster_uuid: clustersIds, }, }, - createDatasetFilter('node_stats', 'elasticsearch.node_stats'), + createDatasetFilter('node_stats', 'node_stats', 'elasticsearch.node_stats'), { range: { timestamp: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts index 1d4f00e828c67..9b1f3f71e2a96 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts @@ -189,6 +189,7 @@ describe('fetchMissingMonitoringData', () => { bool: { should: [ { term: { type: 'node_stats' } }, + { term: { 'metricset.name': 'node_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.node_stats' } }, ], minimum_should_match: 1, @@ -210,7 +211,9 @@ describe('fetchMissingMonitoringData', () => { top_hits: { size: 1, sort: [{ timestamp: { order: 'desc', unmapped_type: 'long' } }], - _source: { includes: ['_index', 'source_node.name'] }, + _source: { + includes: ['source_node.name', 'elasticsearch.node.name'], + }, }, }, }, @@ -221,7 +224,7 @@ describe('fetchMissingMonitoringData', () => { }, }); }); - it('should call ES with correct query when ccs disabled', async () => { + it('should call ES with correct query when ccs disabled', async () => { const now = 10; const clusters = [ { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts index f97f0e749f420..60a5352b8e38f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts @@ -74,7 +74,7 @@ export async function fetchMissingMonitoringData( cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), }, }, - createDatasetFilter('node_stats', 'elasticsearch.node_stats'), + createDatasetFilter('node_stats', 'node_stats', 'elasticsearch.node_stats'), { range: { timestamp: { @@ -117,7 +117,7 @@ export async function fetchMissingMonitoringData( }, ], _source: { - includes: ['_index', 'source_node.name'], + includes: ['source_node.name', 'elasticsearch.node.name'], }, }, }, @@ -153,7 +153,10 @@ export async function fetchMissingMonitoringData( const nodeId = uuidBucket.key; const indexName = get(uuidBucket, `document.hits.hits[0]._index`); const differenceInMs = nowInMs - uuidBucket.most_recent.value; - const nodeName = get(uuidBucket, `document.hits.hits[0]._source.source_node.name`, nodeId); + const nodeName = + get(uuidBucket, `document.hits.hits[0]._source.source_node.name`) || + get(uuidBucket, `document.hits.hits[0]._source.elasticsearch.node.name`) || + nodeId; uniqueList[`${clusterUuid}${nodeId}`] = { nodeId, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.test.ts index 82b0c9910976f..328f8b12836f7 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.test.ts @@ -172,6 +172,7 @@ describe('fetchNodesFromClusterStats', () => { bool: { should: [ { term: { type: 'cluster_stats' } }, + { term: { 'metricset.name': 'cluster_stats' } }, { term: { 'data_stream.dataset': 'elasticsearch.cluster_stats' } }, ], minimum_should_match: 1, @@ -188,7 +189,9 @@ describe('fetchNodesFromClusterStats', () => { top: { top_hits: { sort: [{ timestamp: { order: 'desc', unmapped_type: 'long' } }], - _source: { includes: ['cluster_state.nodes_hash', 'cluster_state.nodes'] }, + _source: { + includes: ['cluster_state.nodes', 'elasticsearch.cluster.stats.nodes'], + }, size: 2, }, }, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts index 40de85226e1f9..0c367558732b5 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts @@ -54,7 +54,7 @@ export async function fetchNodesFromClusterStats( query: { bool: { filter: [ - createDatasetFilter('cluster_stats', 'elasticsearch.cluster_stats'), + createDatasetFilter('cluster_stats', 'cluster_stats', 'elasticsearch.cluster_stats'), { range: { timestamp: { @@ -83,7 +83,7 @@ export async function fetchNodesFromClusterStats( }, ], _source: { - includes: ['cluster_state.nodes_hash', 'cluster_state.nodes'], + includes: ['cluster_state.nodes', 'elasticsearch.cluster.stats.nodes'], }, size: 2, }, @@ -116,8 +116,12 @@ export async function fetchNodesFromClusterStats( const indexName = hits[0]._index; nodes.push({ clusterUuid, - recentNodes: formatNode(hits[0]._source.cluster_state?.nodes), - priorNodes: formatNode(hits[1]._source.cluster_state?.nodes), + recentNodes: formatNode( + hits[0]._source.cluster_state?.nodes || hits[0]._source.elasticsearch.cluster.stats.nodes + ), + priorNodes: formatNode( + hits[1]._source.cluster_state?.nodes || hits[1]._source.elasticsearch.cluster.stats.nodes + ), ccs: indexName.includes(':') ? indexName.split(':')[0] : undefined, }); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts index 8c3ec15e15407..8226f0dd012fd 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts @@ -28,7 +28,12 @@ const getTopHits = (threadType: string, order: 'asc' | 'desc') => ({ }, ], _source: { - includes: [`node_stats.thread_pool.${threadType}.rejected`, 'source_node.name'], + includes: [ + `node_stats.thread_pool.${threadType}.rejected`, + `elasticsearch.node.stats.thread_pool.${threadType}.rejected.count`, + 'source_node.name', + 'elasticsearch.node.name', + ], }, size: 1, }, @@ -62,7 +67,7 @@ export async function fetchThreadPoolRejectionStats( cluster_uuid: clustersIds, }, }, - createDatasetFilter('node_stats', 'elasticsearch.node_stats'), + createDatasetFilter('node_stats', 'node_stats', 'elasticsearch.node_stats'), { range: { timestamp: { @@ -131,8 +136,11 @@ export async function fetchThreadPoolRejectionStats( } const rejectedPath = `_source.node_stats.thread_pool.${threadType}.rejected`; - const newRejectionCount = Number(get(mostRecentDoc, rejectedPath)); - const oldRejectionCount = Number(get(leastRecentDoc, rejectedPath)); + const rejectedPathEcs = `_source.elasticsearch.node.stats.thread_pool.${threadType}.rejected.count`; + const newRejectionCount = + Number(get(mostRecentDoc, rejectedPath)) || Number(get(mostRecentDoc, rejectedPathEcs)); + const oldRejectionCount = + Number(get(leastRecentDoc, rejectedPath)) || Number(get(leastRecentDoc, rejectedPathEcs)); if (invalidNumberValue(newRejectionCount) || invalidNumberValue(oldRejectionCount)) { continue; @@ -143,7 +151,10 @@ export async function fetchThreadPoolRejectionStats( ? newRejectionCount : newRejectionCount - oldRejectionCount; const indexName = mostRecentDoc._index; - const nodeName = get(mostRecentDoc, '_source.source_node.name') || node.key; + const nodeName = + get(mostRecentDoc, '_source.source_node.name') || + get(mostRecentDoc, '_source.elasticsearch.node.name') || + node.key; const nodeStat = { rejectionCount, type: threadType, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts index 5d16d9f7c47aa..f2f36f7a22abc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts @@ -17,6 +17,7 @@ import { buildPhrasesFilter } from '../utils'; import { METRIC_SYSTEM_CPU_USAGE, METRIC_SYSTEM_MEMORY_USAGE, + PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_NAME, TRANSACTION_DURATION, @@ -55,16 +56,34 @@ export function getMobileKPIDistributionConfig({ indexPattern }: ConfigProps): S label: RESPONSE_LATENCY, field: TRANSACTION_DURATION, id: TRANSACTION_DURATION, + columnFilters: [ + { + language: 'kuery', + query: `${PROCESSOR_EVENT}: transaction`, + }, + ], }, { label: SYSTEM_MEMORY_USAGE, field: METRIC_SYSTEM_MEMORY_USAGE, id: METRIC_SYSTEM_MEMORY_USAGE, + columnFilters: [ + { + language: 'kuery', + query: `${PROCESSOR_EVENT}: metric`, + }, + ], }, { label: CPU_USAGE, field: METRIC_SYSTEM_CPU_USAGE, id: METRIC_SYSTEM_CPU_USAGE, + columnFilters: [ + { + language: 'kuery', + query: `${PROCESSOR_EVENT}: metric`, + }, + ], }, ], }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts index c689549e84940..28dbe74c2b700 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts @@ -18,6 +18,7 @@ import { buildPhrasesFilter } from '../utils'; import { METRIC_SYSTEM_CPU_USAGE, METRIC_SYSTEM_MEMORY_USAGE, + PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_NAME, TRANSACTION_DURATION, @@ -74,7 +75,7 @@ export function getMobileKPIConfig({ indexPattern }: ConfigProps): SeriesConfig columnFilters: [ { language: 'kuery', - query: `processor.event: transaction`, + query: `${PROCESSOR_EVENT}: transaction`, }, ], timeScale: 'm', @@ -84,12 +85,24 @@ export function getMobileKPIConfig({ indexPattern }: ConfigProps): SeriesConfig field: METRIC_SYSTEM_MEMORY_USAGE, id: METRIC_SYSTEM_MEMORY_USAGE, columnType: OPERATION_COLUMN, + columnFilters: [ + { + language: 'kuery', + query: `${PROCESSOR_EVENT}: metric`, + }, + ], }, { label: CPU_USAGE, field: METRIC_SYSTEM_CPU_USAGE, id: METRIC_SYSTEM_CPU_USAGE, columnType: OPERATION_COLUMN, + columnFilters: [ + { + language: 'kuery', + query: `${PROCESSOR_EVENT}: metric`, + }, + ], }, ], }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_kpi_config.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_kpi_config.test.ts new file mode 100644 index 0000000000000..b6f9a4f311342 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_kpi_config.test.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 { mockAppIndexPattern, mockIndexPattern } from '../../rtl_helpers'; +import { LensAttributes } from '../lens_attributes'; +import { METRIC_SYSTEM_MEMORY_USAGE, SERVICE_NAME } from '../constants/elasticsearch_fieldnames'; +import { obsvReportConfigMap } from '../../obsv_exploratory_view'; +import { testMobileKPIAttr } from '../test_data/mobile_test_attribute'; +import { getLayerConfigs } from '../../hooks/use_lens_attributes'; +import { IndexPatternState } from '../../hooks/use_app_index_pattern'; + +describe('Mobile kpi config test', function () { + mockAppIndexPattern(); + + let lnsAttr: LensAttributes; + + const layerConfigs = getLayerConfigs( + [ + { + time: { from: 'now-15m', to: 'now' }, + reportDefinitions: { [SERVICE_NAME]: ['ios-integration-testing'] }, + selectedMetricField: METRIC_SYSTEM_MEMORY_USAGE, + color: 'green', + name: 'test-series', + dataType: 'mobile', + }, + ], + 'kpi-over-time', + {} as any, + { mobile: mockIndexPattern } as IndexPatternState, + obsvReportConfigMap + ); + + beforeEach(() => { + lnsAttr = new LensAttributes(layerConfigs); + }); + it('should return expected json', function () { + expect(lnsAttr.getJSON()).toEqual(testMobileKPIAttr); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts new file mode 100644 index 0000000000000..d4b03459e8511 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts @@ -0,0 +1,86 @@ +/* + * 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 const testMobileKPIAttr = { + title: 'Prefilled from exploratory view app', + description: '', + references: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + ], + visualizationType: 'lnsXY', + state: { + datasourceStates: { + indexpattern: { + layers: { + layer0: { + columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'], + columns: { + 'x-axis-column-layer0': { + sourceField: '@timestamp', + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + }, + 'y-axis-column-layer0': { + isBucketed: false, + label: 'Median of System memory usage', + operationType: 'median', + scale: 'ratio', + sourceField: 'system.memory.usage', + dataType: 'number', + filter: { + query: + 'service.name: "ios-integration-testing" and agent.name: (iOS/swift or open-telemetry/swift) and processor.event: metric', + language: 'kuery', + }, + }, + }, + incompleteColumns: {}, + }, + }, + }, + }, + visualization: { + legend: { isVisible: true, showSingleSeries: true, position: 'right' }, + valueLabels: 'hide', + fittingFunction: 'Linear', + curveType: 'CURVE_MONOTONE_X', + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + preferredSeriesType: 'line', + layers: [ + { + accessors: ['y-axis-column-layer0'], + layerId: 'layer0', + layerType: 'data', + seriesType: 'line', + yConfig: [{ forAccessor: 'y-axis-column-layer0', color: 'green', axisMode: 'left' }], + xAccessor: 'x-axis-column-layer0', + }, + ], + }, + query: { + query: + 'service.name: "ios-integration-testing" and agent.name: (iOS/swift or open-telemetry/swift)', + language: 'kuery', + }, + filters: [], + }, +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_index_pattern.json b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_index_pattern.json index 31fec1fe8d4f4..75020f5fd30be 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_index_pattern.json +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"}}", - "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.build.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"as.organization.name\"}}},{\"name\":\"child.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.as.organization.name\"}}},{\"name\":\"client.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.user.full_name\"}}},{\"name\":\"client.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.user.name\"}}},{\"name\":\"client.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.availability_zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.image.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.machine.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.region\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen0size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen1size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen2size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen3size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.tag\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.runtime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.as.organization.name\"}}},{\"name\":\"destination.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.user.full_name\"}}},{\"name\":\"destination.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.user.name\"}}},{\"name\":\"destination.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.data\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.header_flags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.op_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.resolved_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.response_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ecs.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.culprit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.handled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.exception.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.grouping_key\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.logger_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.log.param_message\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.stack_trace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.stack_trace.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"error.stack_trace\"}}},{\"name\":\"error.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.ingested\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.outcome\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.reason\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score_norm\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.sequence\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.severity\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.timezone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.url\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.accessed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.attributes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.ctime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.device\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.drive_letter\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.gid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.group\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.inode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mtime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.owner\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"file.path\"}}},{\"name\":\"file.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"file.target_path\"}}},{\"name\":\"file.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.goroutines\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.active\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.allocated\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.frees\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.idle\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.mallocs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.cpu_fraction\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.next_gc_limit\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_pause.ns\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.obtained\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.released\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.stack\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.total\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.containerized\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.build\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.codename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.os.full\"}}},{\"name\":\"host.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.os.name\"}}},{\"name\":\"host.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.user.full_name\"}}},{\"name\":\"host.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.user.name\"}}},{\"name\":\"host.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"http.request.body.content\"}}},{\"name\":\"http.request.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.referrer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"http.response.body.content\"}}},{\"name\":\"http.response.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.finished\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.alloc\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.max\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.max\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.thread.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.image\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.city\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.country_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_tier\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.env\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_encoded\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_failed\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_original\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_published\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.foo\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.git_rev\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.in_eu\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.ip\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.kibana_uuid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.lang\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.lorem\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.multi-line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.plugin\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.productId\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.request_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.served_from_cache\",\"type\":\"conflict\",\"esTypes\":[\"boolean\",\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false,\"conflictDescriptions\":{\"boolean\":[\"apm-8.0.0-transaction-000001\"],\"keyword\":[\"apm-8.0.0-transaction-000002\"]}},{\"name\":\"labels.taskType\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.this-is-a-very-long-tag-name-without-any-spaces\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.u\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.worker\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.logger\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.file.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.facility.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.facility.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.severity.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.severity.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"metricset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"metricset.period\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.application\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.community_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.direction\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.forwarded_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.iana_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.inner.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.inner.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.transport\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.eventloop.delay.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.eventloop.delay.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.handles.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.arrayBuffers.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.external.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.heap.allocated.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.heap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.requests.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.listening\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"observer.os.full\"}}},{\"name\":\"observer.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"observer.os.name\"}}},{\"name\":\"observer.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version_major\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"organization.name\"}}},{\"name\":\"os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"os.full\"}}},{\"name\":\"os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"os.name\"}}},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.build_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.checksum\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.install_scope\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.installed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.license\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"parent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.command_line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.command_line.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.command_line\"}}},{\"name\":\"process.entity_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.executable\"}}},{\"name\":\"process.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.name\"}}},{\"name\":\"process.parent.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.args_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.command_line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.command_line.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.command_line\"}}},{\"name\":\"process.parent.entity_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.executable.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.executable\"}}},{\"name\":\"process.parent.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.name\"}}},{\"name\":\"process.parent.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.title.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.title\"}}},{\"name\":\"process.parent.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.working_directory.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.working_directory\"}}},{\"name\":\"process.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.working_directory\"}}},{\"name\":\"processor.event\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"processor.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.alloc_objects.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.alloc_space.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.cpu.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.inuse_objects.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.inuse_space.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.samples.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.filename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.filename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.bytes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.strings\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.hive\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.key\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.value\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.slots.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.slots.live\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.threads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.author\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.license\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.ruleset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.uuid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.as.organization.name\"}}},{\"name\":\"server.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.user.full_name\"}}},{\"name\":\"server.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.user.name\"}}},{\"name\":\"server.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.environment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.framework.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.framework.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.language.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.language.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.runtime.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.runtime.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.as.organization.name\"}}},{\"name\":\"source.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.user.full_name\"}}},{\"name\":\"source.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.user.name\"}}},{\"name\":\"source.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.bundle_filepath\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.db.link\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.db.rows_affected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.resource\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.response_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.response_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.duration.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.message.age.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.message.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.self_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.self_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.start.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.subtype\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.sync\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.inactive_file.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.system.norm.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.user.norm.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.framework\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"threat.technique.name\"}}},{\"name\":\"threat.technique.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"threat.technique.subtechnique.name\"}}},{\"name\":\"threat.technique.subtechnique.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timeseries.instance\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.cipher\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.certificate\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.certificate_chain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.issuer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.ja3\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.server_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.subject\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.supported_ciphers\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.established\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.next_protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.resumed\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.certificate\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.certificate_chain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.issuer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.ja3s\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.subject\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.version_protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"trace.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.breakdown.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.histogram\",\"type\":\"histogram\",\"esTypes\":[\"histogram\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.cls\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.fid\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.max\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.sum\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.tbt\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.domComplete\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.domInteractive\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.firstContentfulPaint\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.largestContentfulPaint\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.timeToFirstByte\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.connectEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.connectStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domComplete\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domContentLoadedEventEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domContentLoadedEventStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domInteractive\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domLoading\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domainLookupEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domainLookupStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.fetchStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.loadEventEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.loadEventStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.requestStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.responseEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.responseStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.message.age.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.message.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"transaction.name\"}}},{\"name\":\"transaction.result\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.root\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.sampled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.self_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.self_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.span_count.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.fragment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"url.original\"}}},{\"name\":\"url.password\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.query\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.scheme\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.username\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"user.full_name\"}}},{\"name\":\"user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.device.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"user_agent.original\"}}},{\"name\":\"user_agent.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.classification\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.description.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"vulnerability.description\"}}},{\"name\":\"vulnerability.enumeration\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.report_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.scanner.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.base\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.environmental\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.temporal\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.severity\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields": "[{\"name\":\"system.memory.usage\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"metadata_field\":false},{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.build.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"as.organization.name\"}}},{\"name\":\"child.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.as.organization.name\"}}},{\"name\":\"client.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.user.full_name\"}}},{\"name\":\"client.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.user.name\"}}},{\"name\":\"client.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.availability_zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.image.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.machine.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.region\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen0size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen1size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen2size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen3size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.tag\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.runtime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.as.organization.name\"}}},{\"name\":\"destination.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.user.full_name\"}}},{\"name\":\"destination.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.user.name\"}}},{\"name\":\"destination.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.data\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.header_flags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.op_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.resolved_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.response_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ecs.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.culprit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.handled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.exception.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.grouping_key\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.logger_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.log.param_message\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.stack_trace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.stack_trace.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"error.stack_trace\"}}},{\"name\":\"error.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.ingested\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.outcome\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.reason\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score_norm\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.sequence\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.severity\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.timezone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.url\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.accessed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.attributes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.ctime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.device\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.drive_letter\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.gid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.group\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.inode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mtime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.owner\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"file.path\"}}},{\"name\":\"file.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"file.target_path\"}}},{\"name\":\"file.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.goroutines\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.active\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.allocated\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.frees\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.idle\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.mallocs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.cpu_fraction\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.next_gc_limit\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_pause.ns\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.obtained\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.released\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.stack\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.total\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.containerized\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.build\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.codename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.os.full\"}}},{\"name\":\"host.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.os.name\"}}},{\"name\":\"host.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.user.full_name\"}}},{\"name\":\"host.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.user.name\"}}},{\"name\":\"host.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"http.request.body.content\"}}},{\"name\":\"http.request.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.referrer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"http.response.body.content\"}}},{\"name\":\"http.response.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.finished\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.alloc\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.max\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.max\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.thread.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.image\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.city\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.country_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_tier\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.env\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_encoded\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_failed\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_original\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_published\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.foo\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.git_rev\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.in_eu\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.ip\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.kibana_uuid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.lang\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.lorem\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.multi-line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.plugin\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.productId\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.request_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.served_from_cache\",\"type\":\"conflict\",\"esTypes\":[\"boolean\",\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false,\"conflictDescriptions\":{\"boolean\":[\"apm-8.0.0-transaction-000001\"],\"keyword\":[\"apm-8.0.0-transaction-000002\"]}},{\"name\":\"labels.taskType\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.this-is-a-very-long-tag-name-without-any-spaces\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.u\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.worker\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.logger\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.file.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.facility.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.facility.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.severity.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.severity.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"metricset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"metricset.period\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.application\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.community_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.direction\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.forwarded_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.iana_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.inner.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.inner.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.transport\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.eventloop.delay.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.eventloop.delay.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.handles.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.arrayBuffers.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.external.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.heap.allocated.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.heap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.requests.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.listening\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"observer.os.full\"}}},{\"name\":\"observer.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"observer.os.name\"}}},{\"name\":\"observer.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version_major\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"organization.name\"}}},{\"name\":\"os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"os.full\"}}},{\"name\":\"os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"os.name\"}}},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.build_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.checksum\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.install_scope\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.installed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.license\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"parent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.command_line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.command_line.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.command_line\"}}},{\"name\":\"process.entity_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.executable\"}}},{\"name\":\"process.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.name\"}}},{\"name\":\"process.parent.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.args_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.command_line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.command_line.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.command_line\"}}},{\"name\":\"process.parent.entity_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.executable.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.executable\"}}},{\"name\":\"process.parent.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.name\"}}},{\"name\":\"process.parent.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.title.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.title\"}}},{\"name\":\"process.parent.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.working_directory.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.working_directory\"}}},{\"name\":\"process.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.working_directory\"}}},{\"name\":\"processor.event\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"processor.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.alloc_objects.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.alloc_space.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.cpu.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.inuse_objects.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.inuse_space.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.samples.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.filename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.filename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.bytes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.strings\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.hive\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.key\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.value\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.slots.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.slots.live\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.threads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.author\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.license\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.ruleset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.uuid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.as.organization.name\"}}},{\"name\":\"server.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.user.full_name\"}}},{\"name\":\"server.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.user.name\"}}},{\"name\":\"server.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.environment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.framework.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.framework.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.language.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.language.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.runtime.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.runtime.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.as.organization.name\"}}},{\"name\":\"source.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.user.full_name\"}}},{\"name\":\"source.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.user.name\"}}},{\"name\":\"source.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.bundle_filepath\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.db.link\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.db.rows_affected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.resource\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.response_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.response_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.duration.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.message.age.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.message.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.self_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.self_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.start.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.subtype\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.sync\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.inactive_file.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.system.norm.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.user.norm.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.framework\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"threat.technique.name\"}}},{\"name\":\"threat.technique.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"threat.technique.subtechnique.name\"}}},{\"name\":\"threat.technique.subtechnique.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timeseries.instance\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.cipher\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.certificate\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.certificate_chain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.issuer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.ja3\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.server_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.subject\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.supported_ciphers\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.established\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.next_protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.resumed\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.certificate\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.certificate_chain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.issuer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.ja3s\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.subject\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.version_protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"trace.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.breakdown.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.histogram\",\"type\":\"histogram\",\"esTypes\":[\"histogram\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.cls\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.fid\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.max\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.sum\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.tbt\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.domComplete\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.domInteractive\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.firstContentfulPaint\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.largestContentfulPaint\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.timeToFirstByte\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.connectEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.connectStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domComplete\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domContentLoadedEventEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domContentLoadedEventStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domInteractive\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domLoading\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domainLookupEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domainLookupStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.fetchStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.loadEventEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.loadEventStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.requestStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.responseEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.responseStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.message.age.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.message.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"transaction.name\"}}},{\"name\":\"transaction.result\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.root\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.sampled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.self_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.self_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.span_count.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.fragment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"url.original\"}}},{\"name\":\"url.password\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.query\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.scheme\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.username\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"user.full_name\"}}},{\"name\":\"user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.device.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"user_agent.original\"}}},{\"name\":\"user_agent.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.classification\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.description.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"vulnerability.description\"}}},{\"name\":\"vulnerability.enumeration\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.report_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.scanner.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.base\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.environmental\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.temporal\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.severity\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx index 2688812f76c9a..7a370bc10d865 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx @@ -15,6 +15,7 @@ import { ObservabilityDataViews } from '../../../../utils/observability_data_vie import { getDataHandler } from '../../../../data_handler'; import { useExploratoryView } from '../contexts/exploratory_view_config'; import { DataViewInsufficientAccessError } from '../../../../../../../../src/plugins/data_views/common'; +import { getApmDataViewTitle } from '../utils/utils'; export interface IndexPatternContext { loading: boolean; @@ -75,9 +76,9 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { break; case 'apm': case 'mobile': - const resultApm = await getDataHandler('apm')?.hasData(); + const resultApm = await getDataHandler('apm')!.hasData(); hasDataT = Boolean(resultApm?.hasData); - indices = resultApm?.indices.transaction; + indices = getApmDataViewTitle(resultApm?.indices); break; } setHasAppData((prevState) => ({ ...prevState, [dataType]: hasDataT })); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.test.tsx index d855f648f5da2..3480662f13913 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.test.tsx @@ -35,8 +35,8 @@ describe('SeriesActions', function () { expect(screen.getByLabelText('Series actions list')).toBeVisible(); }); - it('should display a view sample link', function () { - expect(screen.getByLabelText('View sample documents')).toBeVisible(); + it('should display a view transaction link', function () { + expect(screen.getByLabelText('View transaction in Discover')).toBeVisible(); }); it('should display a hide series link', function () { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx index 22d87e3977abc..b3430da2d35e2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx @@ -182,7 +182,7 @@ const DELETE_SERIES_TOOLTIP_LABEL = i18n.translate( const VIEW_SAMPLE_DOCUMENTS_LABEL = i18n.translate( 'xpack.observability.seriesEditor.sampleDocuments', { - defaultMessage: 'View sample documents', + defaultMessage: 'View transaction in Discover', } ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/utils.ts new file mode 100644 index 0000000000000..65e763e148700 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/utils.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { uniq } from 'lodash'; +import { ApmIndicesConfig } from '../../../../../common/typings'; + +export function getApmDataViewTitle(apmIndicesConfig: ApmIndicesConfig) { + return uniq([apmIndicesConfig.transaction, apmIndicesConfig.metric]).join(','); +} diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx index a8e78410e13c5..e8f79ec4e6e27 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx @@ -13,7 +13,10 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { ALERT_STATUS, AlertStatus } from '@kbn/rule-data-utils'; +import { observabilityFeatureId } from '../../../../../common'; +import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { loadAlertAggregations as loadRuleAggregations } from '../../../../../../../plugins/triggers_actions_ui/public'; import { AlertStatusFilterButton } from '../../../../../common/typings'; import { ParsedTechnicalFields } from '../../../../../../rule_registry/common/parse_technical_fields'; @@ -35,6 +38,7 @@ import { } from '../state_container'; import './styles.scss'; import { AlertsStatusFilter, AlertsDisclaimer, AlertsSearchBar } from '../../components'; +import { ObservabilityAppServices } from '../../../../application/types'; interface RuleStatsState { total: number; @@ -228,6 +232,10 @@ function AlertsPage() { // If there is any data, set hasData to true otherwise we need to wait till all the data is loaded before setting hasData to true or false; undefined indicates the data is still loading. const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false); + const kibana = useKibana(); + const CasesContext = kibana.services.cases.getCasesContext(); + const userPermissions = useGetUserCasesPermissions(); + if (!hasAnyData && !isAllRequestsComplete) { return ; } @@ -322,13 +330,19 @@ function AlertsPage() { - + + + diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx index d419fbee1d34e..20b86fed197f8 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx @@ -189,14 +189,14 @@ function ObservabilityActions({ timelines.getAddToExistingCaseButton({ event, casePermissions, - appId: observabilityFeatureId, + appId: observabilityAppId, owner: observabilityFeatureId, onClose: afterCaseSelection, }), timelines.getAddToNewCaseButton({ event, casePermissions, - appId: observabilityFeatureId, + appId: observabilityAppId, owner: observabilityFeatureId, onClose: afterCaseSelection, }), diff --git a/x-pack/plugins/reporting/common/constants/index.ts b/x-pack/plugins/reporting/common/constants/index.ts index b8cf8a27d7fb4..f6b7efabe6449 100644 --- a/x-pack/plugins/reporting/common/constants/index.ts +++ b/x-pack/plugins/reporting/common/constants/index.ts @@ -32,28 +32,6 @@ export const ALLOWED_JOB_CONTENT_TYPES = [ 'text/plain', ]; -// See: -// https://github.com/chromium/chromium/blob/3611052c055897e5ebbc5b73ea295092e0c20141/services/network/public/cpp/header_util_unittest.cc#L50 -// For a list of headers that chromium doesn't like -export const KBN_SCREENSHOT_HEADER_BLOCK_LIST = [ - 'accept-encoding', - 'connection', - 'content-length', - 'content-type', - 'host', - 'referer', - // `Transfer-Encoding` is hop-by-hop header that is meaningful - // only for a single transport-level connection, and shouldn't - // be stored by caches or forwarded by proxies. - 'transfer-encoding', - 'trailer', - 'te', - 'upgrade', - 'keep-alive', -]; - -export const KBN_SCREENSHOT_HEADER_BLOCK_LIST_STARTS_WITH_PATTERN = ['proxy-']; - export const UI_SETTINGS_SEARCH_INCLUDE_FROZEN = 'search:includeFrozen'; export const UI_SETTINGS_CUSTOM_PDF_LOGO = 'xpackReporting:customPdfLogo'; export const UI_SETTINGS_CSV_SEPARATOR = 'csv:separator'; diff --git a/x-pack/plugins/reporting/common/errors/errors.test.ts b/x-pack/plugins/reporting/common/errors/errors.test.ts new file mode 100644 index 0000000000000..e0cc86a40d373 --- /dev/null +++ b/x-pack/plugins/reporting/common/errors/errors.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AuthenticationExpiredError } from '.'; + +describe('ReportingError', () => { + it('provides error code when stringified', () => { + expect(new AuthenticationExpiredError() + '').toBe( + `ReportingError(code: authentication_expired)` + ); + }); + it('provides details if there are any and error code when stringified', () => { + expect(new AuthenticationExpiredError('some details') + '').toBe( + `ReportingError(code: authentication_expired) "some details"` + ); + }); +}); diff --git a/x-pack/plugins/reporting/common/errors/index.ts b/x-pack/plugins/reporting/common/errors/index.ts new file mode 100644 index 0000000000000..3c0ade0ead7e3 --- /dev/null +++ b/x-pack/plugins/reporting/common/errors/index.ts @@ -0,0 +1,51 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +export abstract class ReportingError extends Error { + public abstract code: string; + + constructor(public details?: string) { + super(); + } + + public get message(): string { + const prefix = `ReportingError`; + return this.details + ? `${prefix}(code: ${this.code}) "${this.details}"` + : `${prefix}(code: ${this.code})`; + } + + public toString() { + return this.message; + } +} + +/** + * While performing some reporting action, like fetching data from ES, our + * access token expired. + */ +export class AuthenticationExpiredError extends ReportingError { + code = 'authentication_expired'; +} + +export class QueueTimeoutError extends ReportingError { + code = 'queue_timeout_error'; +} + +/** + * An unknown error has occurred. See details. + */ +export class UnknownError extends ReportingError { + code = 'unknown_error'; +} + +// TODO: Add ReportingError for Kibana stopping unexpectedly +// TODO: Add ReportingError for missing Chromium dependencies +// TODO: Add ReportingError for missing Chromium dependencies +// TODO: Add ReportingError for Chromium not starting for an unknown reason diff --git a/x-pack/plugins/reporting/common/index.ts b/x-pack/plugins/reporting/common/index.ts new file mode 100644 index 0000000000000..36c9f99628778 --- /dev/null +++ b/x-pack/plugins/reporting/common/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Common types that are documented in the Public API + */ +export type { + BaseParams, + BaseParamsV2, + BasePayload, + BasePayloadV2, + JobAppParamsPDF, + JobAppParamsPDFV2, + LocatorParams, +} from './types'; diff --git a/x-pack/plugins/reporting/common/types/base.ts b/x-pack/plugins/reporting/common/types/base.ts index 234467a16921e..6d5e6c1479da0 100644 --- a/x-pack/plugins/reporting/common/types/base.ts +++ b/x-pack/plugins/reporting/common/types/base.ts @@ -11,6 +11,9 @@ import { LocatorParams } from './url'; export type JobId = string; +/** + * @deprecated + */ export type BaseParams = Ensure< { layout?: LayoutParams; @@ -22,17 +25,26 @@ export type BaseParams = Ensure< SerializableRecord >; +/** + * Report job parameters that an application must return from its + * getSharingData function. + */ export type BaseParamsV2 = BaseParams & { locatorParams: LocatorParams[]; }; -// base params decorated with encrypted headers that come into runJob functions +/** + * @deprecated + */ export interface BasePayload extends BaseParams { headers: string; spaceId?: string; isDeprecated?: boolean; } +/** + * Report job parameters, after they are processed in the request handler. + */ export interface BasePayloadV2 extends BaseParamsV2 { headers: string; spaceId?: string; diff --git a/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts index d5b8ec21cc13b..e22b45f021b9e 100644 --- a/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts +++ b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts @@ -15,14 +15,20 @@ interface BaseParamsPDF { } // Job params: structure of incoming user request data, after being parsed from RISON + /** * @deprecated */ export type JobParamsPDFDeprecated = BaseParamsPDF & BaseParams; +/** + * @deprecated + */ export type JobAppParamsPDF = Omit; -// Job payload: structure of stored job data provided by create_job +/** + * Structure of stored job data provided by create_job + */ export interface TaskPayloadPDF extends BasePayload { layout: LayoutParams; forceNow?: string; diff --git a/x-pack/plugins/reporting/common/types/index.ts b/x-pack/plugins/reporting/common/types/index.ts index 37c33dd0ecc7c..f8fefd790aa3b 100644 --- a/x-pack/plugins/reporting/common/types/index.ts +++ b/x-pack/plugins/reporting/common/types/index.ts @@ -71,6 +71,7 @@ export interface ReportSource { */ jobtype: string; // refers to `ExportTypeDefinition.jobType` created_by: string | false; // username or `false` if security is disabled. Used for ensuring users can only access the reports they've created. + error_code?: string; payload: BasePayload; meta: { // for telemetry diff --git a/x-pack/plugins/reporting/common/types/url.ts b/x-pack/plugins/reporting/common/types/url.ts index 28e935713c45e..2445e210b89b7 100644 --- a/x-pack/plugins/reporting/common/types/url.ts +++ b/x-pack/plugins/reporting/common/types/url.ts @@ -16,7 +16,15 @@ export type ManagementLinkFn = () => ManagementLink; export interface LocatorParams

{ id: string; + + /** + * Kibana version used to create the params + */ version: string; + + /** + * Data to recreate the user's state in the application + */ params: P; } diff --git a/x-pack/plugins/reporting/public/index.ts b/x-pack/plugins/reporting/public/index.ts index fdd5769ab3e00..1026a718b95ea 100644 --- a/x-pack/plugins/reporting/public/index.ts +++ b/x-pack/plugins/reporting/public/index.ts @@ -5,22 +5,41 @@ * 2.0. */ -import { PluginInitializerContext } from 'src/core/public'; -import { ReportingAPIClient } from './lib/reporting_api_client'; +import type { PluginInitializerContext } from 'src/core/public'; import { ReportingPublicPlugin } from './plugin'; -import { getSharedComponents } from './shared'; +import type { ReportingPublicComponents } from './shared/get_shared_components'; +/** + * Setup contract for the Reporting plugin. + */ export interface ReportingSetup { + /** + * Used to inform plugins if Reporting config is compatible with UI Capabilities / Application Sub-Feature Controls + * + * @returns boolean + */ usesUiCapabilities: () => boolean; - components: ReturnType; + + /** + * A set of React components for displaying a Reporting share menu in an application + */ + components: ReportingPublicComponents; } +/** + * Start contract for the Reporting plugin. + */ export type ReportingStart = ReportingSetup; -export { ReportingAPIClient, ReportingPublicPlugin as Plugin }; +/** + * Public interface needed for shared components + */ +export type { ApplicationProps } from './shared'; +export type { ReportingPublicComponents }; /** * @internal + * * @param {PluginInitializerContext} initializerContext * @returns {ReportingPublicPlugin} */ diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/index.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/index.ts index ed39eaff144bd..742c5af80f705 100644 --- a/x-pack/plugins/reporting/public/lib/reporting_api_client/index.ts +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/index.ts @@ -7,5 +7,5 @@ export { InternalApiClientProvider, useInternalApiClient } from './context'; export { useCheckIlmPolicyStatus } from './hooks'; -export type { DiagnoseResponse } from './reporting_api_client'; export { ReportingAPIClient } from './reporting_api_client'; +export type { DiagnoseResponse } from './reporting_api_client'; diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts index dab62ea97b7e8..d76f06e00586f 100644 --- a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts @@ -71,7 +71,6 @@ interface IReportingAPI { /** * Client class for interacting with Reporting APIs * @implements IReportingAPI - * @internal */ export class ReportingAPIClient implements IReportingAPI { private http: HttpSetup; diff --git a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx index e1fa1198cf1f8..1925bf377a370 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx @@ -33,6 +33,10 @@ import { ReportingAPIClient } from '../../lib/reporting_api_client'; import { ErrorUnsavedWorkPanel, ErrorUrlTooLongPanel } from './components'; import { getMaxUrlLength } from './constants'; +/** + * Properties for displaying a share menu with Reporting features, including + * internally-derived fields. + */ export interface ReportingPanelProps { apiClient: ReportingAPIClient; toasts: ToastsSetup; @@ -42,7 +46,9 @@ export interface ReportingPanelProps { requiresSavedState: boolean; // Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. layoutId?: string; objectId?: string; + getJobParams: (forShareUrl?: boolean) => Omit; + options?: ReactElement | null; isDirty?: boolean; onClose?: () => void; diff --git a/x-pack/plugins/reporting/public/shared/get_shared_components.tsx b/x-pack/plugins/reporting/public/shared/get_shared_components.tsx index 0906bf85c9538..3a0374a957832 100644 --- a/x-pack/plugins/reporting/public/shared/get_shared_components.tsx +++ b/x-pack/plugins/reporting/public/shared/get_shared_components.tsx @@ -7,29 +7,70 @@ import { CoreSetup } from 'kibana/public'; import React from 'react'; -import { ReportingAPIClient } from '../'; import { PDF_REPORT_TYPE, PDF_REPORT_TYPE_V2, PNG_REPORT_TYPE_V2 } from '../../common/constants'; -import type { Props as PanelPropsScreenCapture } from '../share_context_menu/screen_capture_panel_content'; +import { ReportingAPIClient } from '../lib/reporting_api_client'; +import { ReportingPanelProps } from '../share_context_menu/reporting_panel_content'; import { ScreenCapturePanelContent } from '../share_context_menu/screen_capture_panel_content_lazy'; -interface IncludeOnCloseFn { +/** + * Properties for displaying a share menu with Reporting features. + */ +export interface ApplicationProps { + /** + * A function that Reporting calls to get the sharing data from the application. + */ + getJobParams: ReportingPanelProps['getJobParams']; + + /** + * Option to control how the screenshot(s) is/are placed in the PDF + */ + layoutOption?: 'canvas' | 'print'; + + /** + * Saved object ID + */ + objectId?: string; + + /** + * A function to callback when the Reporting panel should be closed + */ onClose: () => void; } -type Props = Pick & - IncludeOnCloseFn; +/** + * React components used to display share menus with Reporting features in an application. + */ +export interface ReportingPublicComponents { + /** + * An element to display a form to export the page as PDF + * @deprecated + */ + ReportingPanelPDF(props: ApplicationProps): JSX.Element; -/* + /** + * An element to display a form to export the page as PDF + */ + ReportingPanelPDFV2(props: ApplicationProps): JSX.Element; + + /** + * An element to display a form to export the page as PNG + */ + ReportingPanelPNGV2(props: ApplicationProps): JSX.Element; +} + +/** * As of 7.14, the only shared component is a PDF report that is suited for Canvas integration. * This is not planned to expand, as work is to be done on moving the export-type implementations out of Reporting * Related Discuss issue: https://github.com/elastic/kibana/issues/101422 */ -export function getSharedComponents(core: CoreSetup, apiClient: ReportingAPIClient) { +export function getSharedComponents( + core: CoreSetup, + apiClient: ReportingAPIClient +): ReportingPublicComponents { return { - ReportingPanelPDF(props: Props) { + ReportingPanelPDF(props: ApplicationProps) { return ( ); }, - ReportingPanelPDFV2(props: Props) { + ReportingPanelPDFV2(props: ApplicationProps) { return ( ); }, - ReportingPanelPNGV2(props: Props) { + ReportingPanelPNGV2(props: ApplicationProps) { return ( { - const reportingConfig = { kibanaServer: { hostname: 'custom-hostname' } }; - const mockSchema = createMockConfigSchema(reportingConfig); - mockConfig = createMockConfig(mockSchema); -}); - -describe('conditions', () => { - test(`uses hostname from reporting config if set`, async () => { - const permittedHeaders = { - foo: 'bar', - baz: 'quix', - }; - - const conditionalHeaders = getConditionalHeaders(mockConfig, permittedHeaders); - - expect(conditionalHeaders.conditions.hostname).toEqual( - mockConfig.get('kibanaServer', 'hostname') - ); - expect(conditionalHeaders.conditions.port).toEqual(mockConfig.get('kibanaServer', 'port')); - expect(conditionalHeaders.conditions.protocol).toEqual( - mockConfig.get('kibanaServer', 'protocol') - ); - expect(conditionalHeaders.conditions.basePath).toEqual( - mockConfig.kbnConfig.get('server', 'basePath') - ); - }); -}); - -describe('config formatting', () => { - test(`lowercases kibanaServer.hostname`, async () => { - const reportingConfig = { kibanaServer: { hostname: 'GREAT-HOSTNAME' } }; - const mockSchema = createMockConfigSchema(reportingConfig); - mockConfig = createMockConfig(mockSchema); - - const conditionalHeaders = getConditionalHeaders(mockConfig, {}); - expect(conditionalHeaders.conditions.hostname).toEqual('great-hostname'); - }); -}); diff --git a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts deleted file mode 100644 index 262d9733cb16a..0000000000000 --- a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ReportingConfig } from '../../'; -import { ConditionalHeaders } from './'; - -export const getConditionalHeaders = ( - config: ReportingConfig, - filteredHeaders: Record -) => { - const { kbnConfig } = config; - const [hostname, port, basePath, protocol] = [ - config.get('kibanaServer', 'hostname'), - config.get('kibanaServer', 'port'), - kbnConfig.get('server', 'basePath'), - config.get('kibanaServer', 'protocol'), - ] as [string, number, string, string]; - - const conditionalHeaders: ConditionalHeaders = { - headers: filteredHeaders, - conditions: { - hostname: hostname ? hostname.toLowerCase() : hostname, - port, - basePath, - protocol, - }, - }; - - return conditionalHeaders; -}; diff --git a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts index e21b7404f5ed5..f5675b50cfddd 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts @@ -7,12 +7,10 @@ import { ReportingCore } from '../..'; import { - createMockConfig, createMockConfigSchema, createMockLevelLogger, createMockReportingCore, } from '../../test_helpers'; -import { getConditionalHeaders } from '.'; import { getCustomLogo } from './get_custom_logo'; let mockReportingPlugin: ReportingCore; @@ -24,7 +22,7 @@ beforeEach(async () => { }); test(`gets logo from uiSettings`, async () => { - const permittedHeaders = { + const headers = { foo: 'bar', baz: 'quix', }; @@ -40,17 +38,7 @@ test(`gets logo from uiSettings`, async () => { get: mockGet, }); - const conditionalHeaders = getConditionalHeaders( - createMockConfig(createMockConfigSchema()), - permittedHeaders - ); - - const { logo } = await getCustomLogo( - mockReportingPlugin, - conditionalHeaders, - 'spaceyMcSpaceIdFace', - logger - ); + const { logo } = await getCustomLogo(mockReportingPlugin, headers, 'spaceyMcSpaceIdFace', logger); expect(mockGet).toBeCalledWith('xpackReporting:customPdfLogo'); expect(logo).toBe('purple pony'); diff --git a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts index 983f6f41af8d9..fcabd34a642c8 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts @@ -5,25 +5,21 @@ * 2.0. */ +import type { Headers } from 'src/core/server'; import { ReportingCore } from '../../'; import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../common/constants'; import { LevelLogger } from '../../lib'; -import { ConditionalHeaders } from '../common'; export const getCustomLogo = async ( reporting: ReportingCore, - conditionalHeaders: ConditionalHeaders, + headers: Headers, spaceId: string | undefined, logger: LevelLogger ) => { - const fakeRequest = reporting.getFakeRequest( - { headers: conditionalHeaders.headers }, - spaceId, - logger - ); + const fakeRequest = reporting.getFakeRequest({ headers }, spaceId, logger); const uiSettingsClient = await reporting.getUiSettingsClient(fakeRequest, logger); const logo: string = await uiSettingsClient.get(UI_SETTINGS_CUSTOM_PDF_LOGO); // continue the pipeline - return { conditionalHeaders, logo }; + return { headers, logo }; }; diff --git a/x-pack/plugins/reporting/server/export_types/common/index.ts b/x-pack/plugins/reporting/server/export_types/common/index.ts index 501de48e0450a..c9589d5262059 100644 --- a/x-pack/plugins/reporting/server/export_types/common/index.ts +++ b/x-pack/plugins/reporting/server/export_types/common/index.ts @@ -6,9 +6,7 @@ */ export { decryptJobHeaders } from './decrypt_job_headers'; -export { getConditionalHeaders } from './get_conditional_headers'; export { getFullUrls } from './get_full_urls'; -export { omitBlockedHeaders } from './omit_blocked_headers'; export { validateUrls } from './validate_urls'; export { generatePngObservable } from './generate_png'; export { getCustomLogo } from './get_custom_logo'; @@ -17,15 +15,3 @@ export interface TimeRangeParams { min?: Date | string | number | null; max?: Date | string | number | null; } - -export interface ConditionalHeadersConditions { - protocol: string; - hostname: string; - port: number; - basePath: string; -} - -export interface ConditionalHeaders { - headers: Record; - conditions: ConditionalHeadersConditions; -} diff --git a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.test.ts deleted file mode 100644 index e7f4f984c2054..0000000000000 --- a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.test.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omitBlockedHeaders } from './index'; - -test(`omits blocked headers`, async () => { - const permittedHeaders = { - foo: 'bar', - baz: 'quix', - }; - - const blockedHeaders = { - 'accept-encoding': '', - connection: 'upgrade', - 'content-length': '', - 'content-type': '', - host: '', - 'transfer-encoding': '', - 'proxy-connection': 'bananas', - 'proxy-authorization': 'some-base64-encoded-thing', - trailer: 's are for trucks', - }; - - const filteredHeaders = omitBlockedHeaders({ - ...permittedHeaders, - ...blockedHeaders, - }); - - expect(filteredHeaders).toEqual(permittedHeaders); -}); diff --git a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.ts b/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.ts deleted file mode 100644 index c2ad8fdb507f2..0000000000000 --- a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.ts +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omitBy } from 'lodash'; -import { - KBN_SCREENSHOT_HEADER_BLOCK_LIST, - KBN_SCREENSHOT_HEADER_BLOCK_LIST_STARTS_WITH_PATTERN, -} from '../../../common/constants'; - -export const omitBlockedHeaders = (decryptedHeaders: Record) => { - const filteredHeaders: Record = omitBy( - decryptedHeaders, - (_value, header: string) => - header && - (KBN_SCREENSHOT_HEADER_BLOCK_LIST.includes(header) || - KBN_SCREENSHOT_HEADER_BLOCK_LIST_STARTS_WITH_PATTERN.some((pattern) => - header?.startsWith(pattern) - )) - ); - return filteredHeaders; -}; diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/README.md b/x-pack/plugins/reporting/server/export_types/common/pdf/README.md new file mode 100644 index 0000000000000..d2536605f5b38 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/README.md @@ -0,0 +1,54 @@ +# Use of worker threads + +See following performance characteristics of generating a PDF buffer +using a worker thread, for a given, small PDF report. TL;DR running this code for +a small report in a release build is _far_ more performant and takes about 20% +of the time of dev builds. + +### Dev build: new worker for each run + transpile from TS (seconds) + +``` +3.885 +3.063 +2.64 +2.821 +``` + +### Release build: new worker for each run + no transpile from TS (seconds) + +``` +0.674 +0.77 +0.712 +0.77 +``` + +Transpiling TS code is expensive (very small reports can take up to 5x longer). +However, release builds ship all JS which is far more performant for generating +PDF buffers. + +### Use of long-lived workers + +One alternative that was investigated is use of long-lived workers. This would +mean re-using a single worker over time for making a PDF buffer. The following +performance was observed for dev and release builds on non-initial runs that did +not instantiate a new worker: + +``` +0.328 +0.332 +0.368 +0.328 +0.341 +0.358 +0.316 +0.257 +0.378 +0.326 +``` + +Clearly there is overhead for just instantiating a worker thread. We decided to +avoid long-lived workers for our initial implementation since, even though it is +about ~50% extra time the overhead for small reports this number (~0.3s) will +be proportionally far smaller for larger, more common PDFs. That take longer +to compile. diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/constants.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/constants.ts new file mode 100644 index 0000000000000..7e20d1d8b45d5 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/constants.ts @@ -0,0 +1,23 @@ +/* + * 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 path from 'path'; + +export const assetPath = path.resolve(__dirname, '..', '..', 'common', 'assets'); +export const tableBorderWidth = 1; +export const pageMarginTop = 40; +export const pageMarginBottom = 80; +export const pageMarginWidth = 40; +export const headingFontSize = 14; +export const headingMarginTop = 10; +export const headingMarginBottom = 5; +export const headingHeight = headingFontSize * 1.5 + headingMarginTop + headingMarginBottom; +export const subheadingFontSize = 12; +export const subheadingMarginTop = 0; +export const subheadingMarginBottom = 5; +export const subheadingHeight = + subheadingFontSize * 1.5 + subheadingMarginTop + subheadingMarginBottom; diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts index 0c7fedc8f7b7e..80c5238411379 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts @@ -12,30 +12,29 @@ import { DynamicContent, StyleDictionary, TDocumentDefinitions, + PredefinedPageSize, } from 'pdfmake/interfaces'; -import type { Layout } from '../../../../../screenshotting/server'; import { REPORTING_TABLE_LAYOUT } from './get_doc_options'; import { getFont } from './get_font'; +import { TemplateLayout } from './types'; +import { + headingFontSize, + headingMarginBottom, + headingMarginTop, + pageMarginBottom, + pageMarginTop, + pageMarginWidth, + subheadingFontSize, + subheadingMarginBottom, +} from './constants'; export function getTemplate( - layout: Layout, + layout: TemplateLayout, logo: string | undefined, title: string, tableBorderWidth: number, assetPath: string ): Partial { - const pageMarginTop = 40; - const pageMarginBottom = 80; - const pageMarginWidth = 40; - const headingFontSize = 14; - const headingMarginTop = 10; - const headingMarginBottom = 5; - const headingHeight = headingFontSize * 1.5 + headingMarginTop + headingMarginBottom; - const subheadingFontSize = 12; - const subheadingMarginTop = 0; - const subheadingMarginBottom = 5; - const subheadingHeight = subheadingFontSize * 1.5 + subheadingMarginTop + subheadingMarginBottom; - const getStyle = (): StyleDictionary => ({ heading: { alignment: 'left', @@ -111,15 +110,8 @@ export function getTemplate( return { // define page size - pageOrientation: layout.getPdfPageOrientation(), - pageSize: layout.getPdfPageSize({ - pageMarginTop, - pageMarginBottom, - pageMarginWidth, - tableBorderWidth, - headingHeight, - subheadingHeight, - }), + pageOrientation: layout.orientation, + pageSize: layout.pageSize as PredefinedPageSize, pageMargins: layout.useReportingBranding ? [pageMarginWidth, pageMarginTop, pageMarginWidth, pageMarginBottom] : [0, 0, 0, 0], diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts index e4a1680a958dd..1f7f2e21a45a2 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts @@ -6,3 +6,4 @@ */ export { PdfMaker } from './pdfmaker'; +export { PdfWorkerOutOfMemoryError } from './pdfmaker_errors'; diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/buggy_worker.js b/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/buggy_worker.js new file mode 100644 index 0000000000000..47397f2d34a1f --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/buggy_worker.js @@ -0,0 +1,10 @@ +/* + * 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. + */ + +(async function execute() { + throw new Error('This is a bug'); +})(); diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/memory_leak_worker.js b/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/memory_leak_worker.js new file mode 100644 index 0000000000000..1064daadf6878 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/memory_leak_worker.js @@ -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. + */ +const { isMainThread, resourceLimits } = require('worker_threads'); + +// Give Node.js a chance to move the memory to the old generation region +const WAIT = 40; + +const allocateMemory = async () => { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + new Array(resourceLimits.maxYoungGenerationSizeMb * 1024 * 1024) + .fill('') + .map((_, idx) => idx) // more unique values prevent aggressive memory compression and hits mem limits faster + ); + }, WAIT); + }); +}; + +if (!isMainThread) { + (async function run() { + const memoryLeak = []; + for (;;) /* a computer crying */ { + memoryLeak.push(await allocateMemory()); + } + })(); +} diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/pdfmaker.test.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/pdfmaker.test.ts index 4258349726ccf..4b35e0221685b 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/pdfmaker.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/pdfmaker.test.ts @@ -5,15 +5,20 @@ * 2.0. */ +/* eslint-disable max-classes-per-file */ + +import path from 'path'; +import { isUint8Array } from 'util/types'; import { createMockLayout } from '../../../../../../screenshotting/server/layouts/mock'; import { PdfMaker } from '../'; +import { PdfWorkerOutOfMemoryError } from '../pdfmaker_errors'; const imageBase64 = Buffer.from( `iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAGFBMVEXy8vJpaWn7+/vY2Nj39/cAAACcnJzx8fFvt0oZAAAAi0lEQVR4nO3SSQoDIBBFwR7U3P/GQXKEIIJULXr9H3TMrHhX5Yysvj3jjM8+XRnVa9wec8QuHKv3h74Z+PNyGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xu3Bxy026rXu4ljdUVW395xUFfGzLo946DK+QW+bgCTFcecSAAAAABJRU5ErkJggg==`, 'base64' ); -describe('PdfMaker', () => { +describe.skip('PdfMaker', () => { let layout: ReturnType; let pdf: PdfMaker; @@ -22,14 +27,44 @@ describe('PdfMaker', () => { pdf = new PdfMaker(layout, undefined); }); - describe('getBuffer', () => { - it('should generate PDF buffer', async () => { + describe('generate', () => { + it('should generate PDF array buffer', async () => { pdf.setTitle('the best PDF in the world'); pdf.addImage(imageBase64, { title: 'first viz', description: '☃️' }); pdf.addImage(imageBase64, { title: 'second viz', description: '❄️' }); - pdf.generate(); - await expect(pdf.getBuffer()).resolves.toBeInstanceOf(Buffer); + expect(isUint8Array(await pdf.generate())).toBe(true); + }); + }); + + describe('worker', () => { + /** + * Leave this test skipped! It is a proof-of-concept for demonstrating that + * we correctly handle a worker OOM error. Due to the variability of when + * Node will terminate the worker thread for exceeding resource + * limits we cannot guarantee this test will always execute in a reasonable + * amount of time. + */ + it.skip('should report when the PDF worker runs out of memory instead of crashing the main thread', async () => { + const leakyMaker = new (class MemoryLeakPdfMaker extends PdfMaker { + // From local testing: + // OOMs after 456.486 seconds with high young generation size + // OOMs after 53.538 seconds low young generation size + protected workerMaxOldHeapSizeMb = 2; + protected workerMaxYoungHeapSizeMb = 2; + protected workerModulePath = path.resolve(__dirname, './memory_leak_worker.js'); + })(layout, undefined); + await expect(leakyMaker.generate()).rejects.toBeInstanceOf(PdfWorkerOutOfMemoryError); + }); + + it.skip('restarts the PDF worker if it crashes', async () => { + const buggyMaker = new (class BuggyPdfMaker extends PdfMaker { + protected workerModulePath = path.resolve(__dirname, './buggy_worker.js'); + })(layout, undefined); + + await expect(buggyMaker.generate()).rejects.toEqual(new Error('This is a bug')); + await expect(buggyMaker.generate()).rejects.toEqual(new Error('This is a bug')); + await expect(buggyMaker.generate()).rejects.toEqual(new Error('This is a bug')); }); }); @@ -38,11 +73,11 @@ describe('PdfMaker', () => { expect(pdf.getPageCount()).toBe(0); }); - it('should return a number of generated pages', () => { + it('should return a number of generated pages', async () => { for (let i = 0; i < 100; i++) { pdf.addImage(imageBase64, { title: `${i} viz`, description: '☃️' }); } - pdf.generate(); + await pdf.generate(); expect(pdf.getPageCount()).toBe(100); }); @@ -51,8 +86,7 @@ describe('PdfMaker', () => { for (let i = 0; i < 100; i++) { pdf.addImage(imageBase64, { title: `${i} viz`, description: '☃️' }); } - pdf.generate(); - await pdf.getBuffer(); + await pdf.generate(); expect(pdf.getPageCount()).toBe(100); }); diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker.ts index 4d462a429607a..18e5346f71c40 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker.ts @@ -5,52 +5,69 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; -// @ts-ignore: no module definition -import concat from 'concat-stream'; -import _ from 'lodash'; +import { SerializableRecord } from '@kbn/utility-types'; import path from 'path'; -import Printer from 'pdfmake'; import { Content, ContentImage, ContentText } from 'pdfmake/interfaces'; +import { MessageChannel, MessagePort, Worker } from 'worker_threads'; import type { Layout } from '../../../../../screenshotting/server'; -import { getDocOptions, REPORTING_TABLE_LAYOUT } from './get_doc_options'; +import { + headingHeight, + pageMarginBottom, + pageMarginTop, + pageMarginWidth, + subheadingHeight, + tableBorderWidth, +} from './constants'; +import { REPORTING_TABLE_LAYOUT } from './get_doc_options'; import { getFont } from './get_font'; -import { getTemplate } from './get_template'; +import { PdfWorkerOutOfMemoryError } from './pdfmaker_errors'; +import type { GeneratePdfRequest, GeneratePdfResponse, WorkerData } from './worker'; -const assetPath = path.resolve(__dirname, '..', '..', 'common', 'assets'); -const tableBorderWidth = 1; +// Ensure that all dependencies are included in the release bundle. +import './worker_dependencies'; export class PdfMaker { - private _layout: Layout; + _layout: Layout; private _logo: string | undefined; private _title: string; private _content: Content[]; - private _printer: Printer; - private _pdfDoc: PDFKit.PDFDocument | undefined; - constructor(layout: Layout, logo: string | undefined) { - const fontPath = (filename: string) => path.resolve(assetPath, 'fonts', filename); - const fonts = { - Roboto: { - normal: fontPath('roboto/Roboto-Regular.ttf'), - bold: fontPath('roboto/Roboto-Medium.ttf'), - italics: fontPath('roboto/Roboto-Italic.ttf'), - bolditalics: fontPath('roboto/Roboto-Italic.ttf'), - }, - 'noto-cjk': { - // Roboto does not support CJK characters, so we'll fall back on this font if we detect them. - normal: fontPath('noto/NotoSansCJKtc-Regular.ttf'), - bold: fontPath('noto/NotoSansCJKtc-Medium.ttf'), - italics: fontPath('noto/NotoSansCJKtc-Regular.ttf'), - bolditalics: fontPath('noto/NotoSansCJKtc-Medium.ttf'), - }, - }; + private worker?: Worker; + private pageCount: number = 0; + + protected workerModulePath = path.resolve(__dirname, './worker.js'); + + /** + * The maximum heap size for old memory region of the worker thread. + * + * @note We need to provide a sane number given that we need to load a + * node environment for TS compilation (dev-builds only), some library code + * and buffers that result from generating a PDF. + * + * Local testing indicates that to trigger an OOM event for the worker we need + * to exhaust not only heap but also any compression optimization and fallback + * swap space. + * + * With this value we are able to generate PDFs in excess of 5000x5000 pixels + * at which point issues other than memory start to show like glitches in the + * image. + */ + protected workerMaxOldHeapSizeMb = 128; + + /** + * The maximum heap size for young memory region of the worker thread. + * + * @note we leave this 'undefined' to use the Node.js default value. + * @note we set this to a low value to trigger an OOM event sooner for the worker + * in test scenarios. + */ + protected workerMaxYoungHeapSizeMb: number | undefined = undefined; + constructor(layout: Layout, logo: string | undefined) { this._layout = layout; this._logo = logo; this._title = ''; this._content = []; - this._printer = new Printer(fonts); } _addContents(contents: Content[]) { @@ -124,47 +141,91 @@ export class PdfMaker { this._title = title; } - generate() { - const docTemplate = _.assign( - getTemplate(this._layout, this._logo, this._title, tableBorderWidth, assetPath), - { - content: this._content, - } - ); - this._pdfDoc = this._printer.createPdfKitDocument(docTemplate, getDocOptions(tableBorderWidth)); - return this; + private getGeneratePdfRequestData(): GeneratePdfRequest['data'] { + return { + layout: { + hasFooter: this._layout.hasFooter, + hasHeader: this._layout.hasHeader, + orientation: this._layout.getPdfPageOrientation(), + useReportingBranding: this._layout.useReportingBranding, + pageSize: this._layout.getPdfPageSize({ + pageMarginTop, + pageMarginBottom, + pageMarginWidth, + tableBorderWidth, + headingHeight, + subheadingHeight, + }), + }, + title: this._title, + logo: this._logo, + content: this._content as unknown as SerializableRecord[], + }; } - getBuffer(): Promise { - return new Promise((resolve, reject) => { - if (!this._pdfDoc) { - throw new Error( - i18n.translate( - 'xpack.reporting.exportTypes.printablePdf.documentStreamIsNotgeneratedErrorMessage', - { - defaultMessage: 'Document stream has not been generated', - } - ) - ); - } - - const concatStream = concat(function (pdfBuffer: Buffer) { - resolve(pdfBuffer); - }); - - this._pdfDoc.on('error', reject); - this._pdfDoc.pipe(concatStream); - this._pdfDoc.end(); + private createWorker(port: MessagePort): Worker { + const workerData: WorkerData = { + port, + }; + return new Worker(this.workerModulePath, { + workerData, + resourceLimits: { + maxYoungGenerationSizeMb: this.workerMaxYoungHeapSizeMb, + maxOldGenerationSizeMb: this.workerMaxOldHeapSizeMb, + }, + transferList: [port], }); } - getPageCount(): number { - const pageRange = this._pdfDoc?.bufferedPageRange(); - if (!pageRange) { - return 0; + private async cleanupWorker(): Promise { + if (this.worker) { + await this.worker.terminate().catch(); // best effort + this.worker = undefined; } - const { count, start } = pageRange; + } - return start + count; + public async generate(): Promise { + if (this.worker) throw new Error('PDF generation already in progress!'); + + try { + return await new Promise((resolve, reject) => { + const { port1: myPort, port2: theirPort } = new MessageChannel(); + this.worker = this.createWorker(theirPort); + this.worker.on('error', (workerError: NodeJS.ErrnoException) => { + if (workerError.code === 'ERR_WORKER_OUT_OF_MEMORY') { + reject(new PdfWorkerOutOfMemoryError(workerError.message)); + } else { + reject(workerError); + } + }); + this.worker.on('exit', () => {}); // do nothing on errors + + // Send the initial request + const generatePdfRequest: GeneratePdfRequest = { + data: this.getGeneratePdfRequestData(), + }; + myPort.postMessage(generatePdfRequest); + + // We expect one message from the worker generating the PDF buffer. + myPort.on('message', ({ error, data }: GeneratePdfResponse) => { + if (error) { + reject(new Error(`PDF worker returned the following error: ${error}`)); + return; + } + if (!data) { + reject(new Error(`Worker did not generate a PDF!`)); + return; + } + this.pageCount = data.metrics.pages; + resolve(data.buffer); + }); + }); + } finally { + await this.cleanupWorker(); + } + } + + getPageCount(): number { + return this.pageCount; } } diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker_errors.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker_errors.ts new file mode 100644 index 0000000000000..d55921d791ade --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker_errors.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export class PdfWorkerOutOfMemoryError extends Error { + constructor(message: string) { + super(message); + this.name = 'PdfWorkerOutOfMemoryError'; + } +} diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/types.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/types.ts new file mode 100644 index 0000000000000..e622cd49ad13e --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/types.ts @@ -0,0 +1,24 @@ +/* + * 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 { Ensure, SerializableRecord } from '@kbn/utility-types'; + +export type TemplateLayout = Ensure< + { + orientation: 'landscape' | 'portrait' | undefined; + useReportingBranding: boolean; + hasHeader: boolean; + hasFooter: boolean; + pageSize: + | string + | { + width: number; + height: number | 'auto'; + }; + }, + SerializableRecord +>; diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/worker.js b/x-pack/plugins/reporting/server/export_types/common/pdf/worker.js new file mode 100644 index 0000000000000..d3dfa3e9accf8 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/worker.js @@ -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. + */ + +require('../../../../../../../src/setup_node_env'); +require('./worker.ts'); diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/worker.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/worker.ts new file mode 100644 index 0000000000000..983cebca7d6ae --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/worker.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Ensure, SerializableRecord } from '@kbn/utility-types'; + +import { isMainThread, MessagePort, workerData } from 'worker_threads'; +import path from 'path'; + +import { getTemplate } from './get_template'; +import type { TemplateLayout } from './types'; +import { assetPath } from './constants'; +import { _, Printer } from './worker_dependencies'; + +export interface WorkerData { + port: MessagePort; +} + +export type GenerateReportRequestData = Ensure< + { + layout: TemplateLayout; + title: string; + content: SerializableRecord[]; + + logo?: string; + }, + SerializableRecord +>; + +export interface GeneratePdfRequest { + data: GenerateReportRequestData; +} + +export type GeneratePdfResponse = SuccessResponse | ErrorResponse; + +export interface SuccessResponse { + error?: undefined; + data: { + buffer: Uint8Array; + metrics: { + pages: number; + }; + }; +} + +export interface ErrorResponse { + error: string; + data: null; +} + +if (!isMainThread) { + const { port } = workerData as WorkerData; + port.on('message', execute); +} + +const getPageCount = (pdfDoc: PDFKit.PDFDocument): number => { + const pageRange = pdfDoc.bufferedPageRange(); + if (!pageRange) { + return 0; + } + const { count, start } = pageRange; + + return start + count; +}; + +async function execute({ data: { layout, logo, title, content } }: GeneratePdfRequest) { + const { port } = workerData as WorkerData; + try { + const tableBorderWidth = 1; + + const fontPath = (filename: string) => path.resolve(assetPath, 'fonts', filename); + + const fonts = { + Roboto: { + normal: fontPath('roboto/Roboto-Regular.ttf'), + bold: fontPath('roboto/Roboto-Medium.ttf'), + italics: fontPath('roboto/Roboto-Italic.ttf'), + bolditalics: fontPath('roboto/Roboto-Italic.ttf'), + }, + 'noto-cjk': { + // Roboto does not support CJK characters, so we'll fall back on this font if we detect them. + normal: fontPath('noto/NotoSansCJKtc-Regular.ttf'), + bold: fontPath('noto/NotoSansCJKtc-Medium.ttf'), + italics: fontPath('noto/NotoSansCJKtc-Regular.ttf'), + bolditalics: fontPath('noto/NotoSansCJKtc-Medium.ttf'), + }, + }; + + const printer = new Printer(fonts); + + const docDefinition = _.assign(getTemplate(layout, logo, title, tableBorderWidth, assetPath), { + content, + }); + + const pdfDoc = printer.createPdfKitDocument(docDefinition, { + tableLayouts: { + noBorder: { + // format is function (i, node) { ... }; + hLineWidth: () => 0, + vLineWidth: () => 0, + paddingLeft: () => 0, + paddingRight: () => 0, + paddingTop: () => 0, + paddingBottom: () => 0, + }, + }, + }); + + if (!pdfDoc) { + throw new Error('Document stream has not been generated'); + } + + const buffer = await new Promise((resolve, reject) => { + const buffers: Buffer[] = []; + pdfDoc.on('error', reject); + pdfDoc.on('data', (data: Buffer) => { + buffers.push(data); + }); + pdfDoc.on('end', () => { + resolve(Buffer.concat(buffers)); + }); + pdfDoc.end(); + }); + + const successResponse: SuccessResponse = { + data: { + buffer, + metrics: { + pages: getPageCount(pdfDoc), + }, + }, + }; + port.postMessage(successResponse, [buffer.buffer /* Transfer buffer instead of copying */]); + } catch (error) { + const errorResponse: ErrorResponse = { error: error.message, data: null }; + port.postMessage(errorResponse); + } finally { + process.nextTick(() => { + process.exit(0); + }); + } +} diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/worker_dependencies.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/worker_dependencies.ts new file mode 100644 index 0000000000000..58e2248945a48 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/worker_dependencies.ts @@ -0,0 +1,11 @@ +/* + * 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 Printer from 'pdfmake'; + +export { _, Printer }; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index 61b0a862d245b..eb3cbd6118eb9 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -7,6 +7,7 @@ import { Writable } from 'stream'; import * as Rx from 'rxjs'; +import { errors as esErrors } from '@elastic/elasticsearch'; import { identity, range } from 'lodash'; import { IScopedClusterClient, IUiSettingsClient, SearchResponse } from 'src/core/server'; import { @@ -26,6 +27,7 @@ import { UI_SETTINGS_CSV_SEPARATOR, UI_SETTINGS_DATEFORMAT_TZ, } from '../../../../common/constants'; +import { AuthenticationExpiredError } from '../../../../common/errors'; import { createMockConfig, createMockConfigSchema, @@ -805,3 +807,26 @@ it('can override ignoring frozen indices', async () => { { strategy: 'es' } ); }); + +it('throws an AuthenticationExpiredError when ES does not accept credentials', async () => { + mockDataClient.search = jest.fn().mockImplementation(() => { + throw new esErrors.ResponseError({ statusCode: 403, meta: {} as any, warnings: [] }); + }); + const generateCsv = new CsvGenerator( + createMockJob({ columns: ['date', 'ip', 'message'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger, + stream + ); + await expect(generateCsv.generateData()).rejects.toEqual(new AuthenticationExpiredError()); +}); diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index 0feaab90975d8..5be17f5e6d252 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -6,6 +6,7 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { errors as esErrors } from '@elastic/elasticsearch'; import { i18n } from '@kbn/i18n'; import type { IScopedClusterClient, IUiSettingsClient } from 'src/core/server'; import type { IScopedSearchClient } from 'src/plugins/data/server'; @@ -30,6 +31,7 @@ import type { import { KbnServerError } from '../../../../../../../src/plugins/kibana_utils/server'; import type { CancellationToken } from '../../../../common/cancellation_token'; import { CONTENT_TYPE_CSV } from '../../../../common/constants'; +import { AuthenticationExpiredError } from '../../../../common/errors'; import { byteSizeValueToNumber } from '../../../../common/schema_utils'; import type { LevelLogger } from '../../../lib'; import type { TaskRunResult } from '../../../lib/tasks'; @@ -369,6 +371,9 @@ export class CsvGenerator { if (err instanceof KbnServerError && err.errBody) { throw JSON.stringify(err.errBody.error); } + if (err instanceof esErrors.ResponseError && [401, 403].includes(err.statusCode ?? 0)) { + throw new AuthenticationExpiredError(); + } } finally { // clear scrollID if (scrollId) { diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts index bbdab4d75b7bf..9069ec63a8825 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts @@ -87,10 +87,7 @@ test(`passes browserTimezone to generatePng`, async () => { expect.objectContaining({ urls: ['localhost:80undefined/app/kibana#/something'], browserTimezone: 'UTC', - conditionalHeaders: expect.objectContaining({ - conditions: expect.any(Object), - headers: {}, - }), + headers: {}, }) ); }); diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts index 20a2ea98e06d4..67d013740bedd 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts @@ -11,13 +11,7 @@ import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { PNG_JOB_TYPE, REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; import { TaskRunResult } from '../../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../../types'; -import { - decryptJobHeaders, - getConditionalHeaders, - getFullUrls, - omitBlockedHeaders, - generatePngObservable, -} from '../../common'; +import { decryptJobHeaders, getFullUrls, generatePngObservable } from '../../common'; import { TaskPayloadPNG } from '../types'; export const runTaskFnFactory: RunTaskFnFactory> = @@ -33,16 +27,14 @@ export const runTaskFnFactory: RunTaskFnFactory> = const jobLogger = parentLogger.clone([PNG_JOB_TYPE, 'execute', jobId]); const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => { + mergeMap((headers) => { const [url] = getFullUrls(config, job); apmGetAssets?.end(); apmGeneratePng = apmTrans?.startSpan('generate-png-pipeline', 'execute'); return generatePngObservable(reporting, jobLogger, { - conditionalHeaders, + headers, urls: [url], browserTimezone: job.browserTimezone, layout: job.layout, diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts index f7c6b6138419c..1b1ad6878d78f 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts @@ -94,10 +94,7 @@ test(`passes browserTimezone to generatePng`, async () => { ], ], browserTimezone: 'UTC', - conditionalHeaders: expect.objectContaining({ - conditions: expect.any(Object), - headers: {}, - }), + headers: {}, }) ); }); diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts index 1acce6e475630..51044aa324a1a 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts @@ -11,12 +11,7 @@ import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { PNG_JOB_TYPE_V2, REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; import { TaskRunResult } from '../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../types'; -import { - decryptJobHeaders, - getConditionalHeaders, - omitBlockedHeaders, - generatePngObservable, -} from '../common'; +import { decryptJobHeaders, generatePngObservable } from '../common'; import { getFullRedirectAppUrl } from '../common/v2/get_full_redirect_app_url'; import { TaskPayloadPNGV2 } from './types'; @@ -33,9 +28,7 @@ export const runTaskFnFactory: RunTaskFnFactory> = const jobLogger = parentLogger.clone([PNG_JOB_TYPE_V2, 'execute', jobId]); const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => { + mergeMap((headers) => { const url = getFullRedirectAppUrl(config, job.spaceId, job.forceNow); const [locatorParams] = job.locatorParams; @@ -43,7 +36,7 @@ export const runTaskFnFactory: RunTaskFnFactory> = apmGeneratePng = apmTrans?.startSpan('generate-png-pipeline', 'execute'); return generatePngObservable(reporting, jobLogger, { - conditionalHeaders, + headers, browserTimezone: job.browserTimezone, layout: job.layout, urls: [[url, locatorParams]], diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts index 02ba917ce329d..ab3793935e1d8 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts @@ -11,13 +11,7 @@ import { catchError, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { PDF_JOB_TYPE, REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; import { TaskRunResult } from '../../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../../types'; -import { - decryptJobHeaders, - getConditionalHeaders, - getFullUrls, - omitBlockedHeaders, - getCustomLogo, -} from '../../common'; +import { decryptJobHeaders, getFullUrls, getCustomLogo } from '../../common'; import { generatePdfObservable } from '../lib/generate_pdf'; import { TaskPayloadPDF } from '../types'; @@ -34,12 +28,8 @@ export const runTaskFnFactory: RunTaskFnFactory> = const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => - getCustomLogo(reporting, conditionalHeaders, job.spaceId, jobLogger) - ), - mergeMap(({ logo, conditionalHeaders }) => { + mergeMap((headers) => getCustomLogo(reporting, headers, job.spaceId, jobLogger)), + mergeMap(({ headers, logo }) => { const urls = getFullUrls(config, job); const { browserTimezone, layout, title } = job; @@ -53,7 +43,7 @@ export const runTaskFnFactory: RunTaskFnFactory> = { urls, browserTimezone, - conditionalHeaders, + headers, layout, }, logo diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts index 149f4fc3aee52..459887ebb8118 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts @@ -13,7 +13,7 @@ import type { PdfMetrics } from '../../../../common/types'; import { ReportingCore } from '../../../'; import { LevelLogger } from '../../../lib'; import { ScreenshotOptions } from '../../../types'; -import { PdfMaker } from '../../common/pdf'; +import { PdfMaker, PdfWorkerOutOfMemoryError } from '../../common/pdf'; import { getTracker } from './tracker'; const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { @@ -27,7 +27,7 @@ const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { }; interface PdfResult { - buffer: Buffer | null; + buffer: Uint8Array | null; metrics?: PdfMetrics; warnings: string[]; } @@ -72,44 +72,49 @@ export function generatePdfObservable( }); }); - let buffer: Buffer | null = null; + const warnings = results.reduce((found, current) => { + if (current.error) { + found.push(current.error.message); + } + if (current.renderErrors) { + found.push(...current.renderErrors); + } + return found; + }, []); + + let buffer: Uint8Array | null = null; try { tracker.startCompile(); logger.info(`Compiling PDF using "${layout.id}" layout...`); - pdfOutput.generate(); + buffer = await pdfOutput.generate(); tracker.endCompile(); - tracker.startGetBuffer(); logger.debug(`Generating PDF Buffer...`); - buffer = await pdfOutput.getBuffer(); const byteLength = buffer?.byteLength ?? 0; logger.debug(`PDF buffer byte length: ${byteLength}`); tracker.setByteLength(byteLength); - - tracker.endGetBuffer(); } catch (err) { logger.error(`Could not generate the PDF buffer!`); logger.error(err); + if (err instanceof PdfWorkerOutOfMemoryError) { + warnings.push( + 'Failed to generate PDF due to low memory. Please consider generating a smaller PDF.' + ); + } else { + warnings.push(`Failed to generate PDF due to the following error: ${err.message}`); + } } tracker.end(); return { buffer, + warnings, metrics: { ...metrics, pages: pdfOutput.getPageCount(), }, - warnings: results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, [] as string[]), }; }) ); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts index d1cf2b96817d2..62094be45ef35 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts @@ -20,8 +20,6 @@ interface PdfTracker { endAddImage: () => void; startCompile: () => void; endCompile: () => void; - startGetBuffer: () => void; - endGetBuffer: () => void; end: () => void; } @@ -39,7 +37,6 @@ export function getTracker(): PdfTracker { let apmSetup: ApmSpan | null = null; let apmAddImage: ApmSpan | null = null; let apmCompilePdf: ApmSpan | null = null; - let apmGetBuffer: ApmSpan | null = null; return { startScreenshots() { @@ -66,12 +63,6 @@ export function getTracker(): PdfTracker { endCompile() { if (apmCompilePdf) apmCompilePdf.end(); }, - startGetBuffer() { - apmGetBuffer = apmTrans?.startSpan('get-buffer', SPANTYPE_OUTPUT) || null; - }, - endGetBuffer() { - if (apmGetBuffer) apmGetBuffer.end(); - }, setByteLength(byteLength: number) { apmTrans?.setLabel('byte-length', byteLength, false); }, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts index de6f2ae70a756..85684bca66b86 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts @@ -11,12 +11,7 @@ import { catchError, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { PDF_JOB_TYPE_V2, REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; import { TaskRunResult } from '../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../types'; -import { - decryptJobHeaders, - getConditionalHeaders, - omitBlockedHeaders, - getCustomLogo, -} from '../common'; +import { decryptJobHeaders, getCustomLogo } from '../common'; import { generatePdfObservable } from './lib/generate_pdf'; import { TaskPayloadPDFV2 } from './types'; @@ -33,12 +28,8 @@ export const runTaskFnFactory: RunTaskFnFactory> = const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => - getCustomLogo(reporting, conditionalHeaders, job.spaceId, jobLogger) - ), - mergeMap(({ logo, conditionalHeaders }) => { + mergeMap((headers) => getCustomLogo(reporting, headers, job.spaceId, jobLogger)), + mergeMap(({ logo, headers }) => { const { browserTimezone, layout, title, locatorParams } = job; apmGetAssets?.end(); @@ -51,7 +42,7 @@ export const runTaskFnFactory: RunTaskFnFactory> = locatorParams, { browserTimezone, - conditionalHeaders, + headers, layout, }, logo diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts index 08e73371f74b7..b0ac1a59010a6 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts @@ -9,11 +9,12 @@ import { groupBy } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap, tap } from 'rxjs/operators'; import { ReportingCore } from '../../../'; +import { ScreenshotResult } from '../../../../../screenshotting/server'; import { LocatorParams, PdfMetrics, UrlOrUrlLocatorTuple } from '../../../../common/types'; import { LevelLogger } from '../../../lib'; -import { ScreenshotResult } from '../../../../../screenshotting/server'; import { ScreenshotOptions } from '../../../types'; import { PdfMaker } from '../../common/pdf'; +import { PdfWorkerOutOfMemoryError } from '../../common/pdf'; import { getFullRedirectAppUrl } from '../../common/v2/get_full_redirect_app_url'; import type { TaskPayloadPDFV2 } from '../types'; import { getTracker } from './tracker'; @@ -29,7 +30,7 @@ const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { }; interface PdfResult { - buffer: Buffer | null; + buffer: Uint8Array | null; metrics?: PdfMetrics; warnings: string[]; } @@ -84,44 +85,47 @@ export function generatePdfObservable( }); }); - let buffer: Buffer | null = null; + const warnings = results.reduce((found, current) => { + if (current.error) { + found.push(current.error.message); + } + if (current.renderErrors) { + found.push(...current.renderErrors); + } + return found; + }, []); + + let buffer: Uint8Array | null = null; try { tracker.startCompile(); logger.info(`Compiling PDF using "${layout.id}" layout...`); - pdfOutput.generate(); + buffer = await pdfOutput.generate(); tracker.endCompile(); - tracker.startGetBuffer(); - logger.debug(`Generating PDF Buffer...`); - buffer = await pdfOutput.getBuffer(); - const byteLength = buffer?.byteLength ?? 0; logger.debug(`PDF buffer byte length: ${byteLength}`); tracker.setByteLength(byteLength); - tracker.endGetBuffer(); + tracker.end(); } catch (err) { logger.error(`Could not generate the PDF buffer!`); logger.error(err); + if (err instanceof PdfWorkerOutOfMemoryError) { + warnings.push( + 'Failed to generate PDF due to low memory. Please consider generating a smaller PDF.' + ); + } else { + warnings.push(`Failed to generate PDF due to the following error: ${err.message}`); + } } - tracker.end(); - return { buffer, + warnings, metrics: { ...metrics, pages: pdfOutput.getPageCount(), }, - warnings: results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, [] as string[]), }; }) ); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts index d1cf2b96817d2..62094be45ef35 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts @@ -20,8 +20,6 @@ interface PdfTracker { endAddImage: () => void; startCompile: () => void; endCompile: () => void; - startGetBuffer: () => void; - endGetBuffer: () => void; end: () => void; } @@ -39,7 +37,6 @@ export function getTracker(): PdfTracker { let apmSetup: ApmSpan | null = null; let apmAddImage: ApmSpan | null = null; let apmCompilePdf: ApmSpan | null = null; - let apmGetBuffer: ApmSpan | null = null; return { startScreenshots() { @@ -66,12 +63,6 @@ export function getTracker(): PdfTracker { endCompile() { if (apmCompilePdf) apmCompilePdf.end(); }, - startGetBuffer() { - apmGetBuffer = apmTrans?.startSpan('get-buffer', SPANTYPE_OUTPUT) || null; - }, - endGetBuffer() { - if (apmGetBuffer) apmGetBuffer.end(); - }, setByteLength(byteLength: number) { apmTrans?.setLabel('byte-length', byteLength, false); }, diff --git a/x-pack/plugins/reporting/server/index.ts b/x-pack/plugins/reporting/server/index.ts index f1ead1fc038fb..fd967b08e383c 100644 --- a/x-pack/plugins/reporting/server/index.ts +++ b/x-pack/plugins/reporting/server/index.ts @@ -9,16 +9,18 @@ import { PluginInitializerContext } from 'kibana/server'; import { ReportingConfigType } from './config'; import { ReportingPlugin } from './plugin'; +export { config } from './config'; + +/** + * Common types that are documented in the Public API + */ +export type { ReportingSetup, ReportingStart } from './types'; + +// @internal export const plugin = (initContext: PluginInitializerContext) => new ReportingPlugin(initContext); -export { config } from './config'; +// @internal export type { ReportingConfig } from './config/config'; -// internal imports +// @internal export { ReportingCore } from './core'; -export type { - ReportingSetup, - ReportingSetupDeps as PluginSetup, - ReportingStartDeps as PluginStart, -} from './types'; -export { ReportingPlugin as Plugin }; diff --git a/x-pack/plugins/reporting/server/lib/store/mapping.ts b/x-pack/plugins/reporting/server/lib/store/mapping.ts index f860493dfc3fa..db088aa99f1e1 100644 --- a/x-pack/plugins/reporting/server/lib/store/mapping.ts +++ b/x-pack/plugins/reporting/server/lib/store/mapping.ts @@ -44,6 +44,7 @@ export const mapping = { created_at: { type: 'date' }, started_at: { type: 'date' }, completed_at: { type: 'date' }, + error_code: { type: 'keyword' }, attempts: { type: 'short' }, max_attempts: { type: 'short' }, kibana_name: { type: 'keyword' }, diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index 41fdd9580c996..29f61d8b2a364 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -34,6 +34,7 @@ export type ReportProcessingFields = Required<{ export type ReportFailedFields = Required<{ completed_at: Report['completed_at']; output: ReportOutput | null; + error_code: undefined | string; }>; export type ReportCompletedFields = Required<{ diff --git a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts index 8cc4139da3f1f..47f4e8617a9f3 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts @@ -19,6 +19,7 @@ import { TaskRunCreatorFunction, } from '../../../../task_manager/server'; import { CancellationToken } from '../../../common/cancellation_token'; +import { ReportingError, UnknownError, QueueTimeoutError } from '../../../common/errors'; import { durationToNumber, numberToDuration } from '../../../common/schema_utils'; import type { ReportOutput } from '../../../common/types'; import type { ReportingConfigType } from '../../config'; @@ -148,7 +149,9 @@ export class ExecuteReportTask implements ReportingTask { // check if job has exceeded the configured maxAttempts const maxAttempts = this.config.capture.maxAttempts; if (report.attempts >= maxAttempts) { - const err = new Error(`Max attempts reached (${maxAttempts}). Queue timeout reached.`); + const err = new QueueTimeoutError( + `Max attempts reached (${maxAttempts}). Queue timeout reached.` + ); await this._failJob(report, err); throw err; } @@ -189,7 +192,7 @@ export class ExecuteReportTask implements ReportingTask { private async _failJob( report: SavedReport, - error?: Error + error?: ReportingError ): Promise> { const message = `Failing ${report.jobtype} job ${report._id}`; @@ -208,12 +211,13 @@ export class ExecuteReportTask implements ReportingTask { const doc: ReportFailedFields = { completed_at: completedTime, output: docOutput ?? null, + error_code: error?.code, }; return await store.setReportFailed(report, doc); } - private _formatOutput(output: CompletedReportOutput | Error): ReportOutput { + private _formatOutput(output: CompletedReportOutput | ReportingError): ReportOutput { const docOutput = {} as ReportOutput; const unknownMime = null; @@ -228,7 +232,7 @@ export class ExecuteReportTask implements ReportingTask { const defaultOutput = null; docOutput.content = output.toString() || defaultOutput; docOutput.content_type = unknownMime; - docOutput.warnings = [output.toString()]; + docOutput.warnings = [output.details ?? output.toString()]; } return docOutput; @@ -409,11 +413,16 @@ export class ExecuteReportTask implements ReportingTask { } else { // 0 attempts remain - fail the job try { - const maxAttemptsMsg = `Max attempts (${attempts}) reached for job ${jobId}. Failed with: ${failedToExecuteErr}`; if (report == null) { throw new Error(`Report ${jobId} is null!`); } - const resp = await this._failJob(report, new Error(maxAttemptsMsg)); + const maxAttemptsMsg = `Max attempts (${attempts}) reached for job ${jobId}. Failed with: ${failedToExecuteErr.message}`; + const error = + failedToExecuteErr instanceof ReportingError + ? failedToExecuteErr + : new UnknownError(); + error.details = maxAttemptsMsg; + const resp = await this._failJob(report, error); report._seq_no = resp._seq_no; report._primary_term = resp._primary_term; } catch (failedToFailError) { diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts index 2d5a254045104..90b4c9d9a30c6 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { ReportingCore } from '../..'; import { APP_WRAPPER_CLASS } from '../../../../../../src/core/server'; import { API_DIAGNOSE_URL } from '../../../common/constants'; -import { omitBlockedHeaders, generatePngObservable } from '../../export_types/common'; +import { generatePngObservable } from '../../export_types/common'; import { getAbsoluteUrlFactory } from '../../export_types/common/get_absolute_url'; import { LevelLogger as Logger } from '../../lib'; import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; @@ -24,9 +24,8 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log path: `${API_DIAGNOSE_URL}/screenshot`, validate: {}, }, - authorizedUserPreRouting(reporting, async (_user, _context, req, res) => { + authorizedUserPreRouting(reporting, async (_user, _context, request, res) => { const config = reporting.getConfig(); - const decryptedHeaders = req.headers as Record; const [basePath, protocol, hostname, port] = [ config.kbnConfig.get('server', 'basePath'), config.get('kibanaServer', 'protocol'), @@ -51,19 +50,9 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log }, }; - const conditionalHeaders = { - headers: omitBlockedHeaders(decryptedHeaders), - conditions: { - hostname, - port: +port, - basePath, - protocol, - }, - }; - return generatePngObservable(reporting, logger, { - conditionalHeaders, layout, + request, browserTimezone: 'America/Los_Angeles', urls: [hashUrl], }) diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index cd28972f5941a..fa69509d16be8 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -32,26 +32,28 @@ import type { ReportingCore } from './core'; import type { LevelLogger } from './lib'; import type { ReportTaskParams } from './lib/tasks'; -/* - * Plugin Contract +/** + * Plugin Setup Contract */ - export interface ReportingSetup { + /** + * Used to inform plugins if Reporting config is compatible with UI Capabilities / Application Sub-Feature Controls + */ usesUiCapabilities: () => boolean; } -export type ReportingStart = ReportingSetup; - -/* - * Internal Types +/** + * Plugin Start Contract */ - +export type ReportingStart = ReportingSetup; export type ReportingUser = { username: AuthenticatedUser['username'] } | false; export type CaptureConfig = ReportingConfigType['capture']; export type ScrollConfig = ReportingConfigType['csv']['scroll']; -export type { BaseParams, BasePayload }; +/** + * Internal Types + */ // default fn type for CreateJobFnFactory export type CreateJobFn = ( @@ -91,9 +93,6 @@ export interface ExportTypeDefinition< validLicenses: string[]; } -/* - * @internal - */ export interface ReportingSetupDeps { features: FeaturesPluginSetup; screenshotMode: ScreenshotModePluginSetup; @@ -103,9 +102,6 @@ export interface ReportingSetupDeps { usageCollection?: UsageCollectionSetup; } -/* - * @internal - */ export interface ReportingStartDeps { data: DataPluginStart; fieldFormats: FieldFormatsStart; @@ -115,22 +111,15 @@ export interface ReportingStartDeps { taskManager: TaskManagerStartContract; } -/** - * @internal - */ export interface ReportingRequestHandlerContext { reporting: ReportingStart | null; core: RequestHandlerContext['core']; } -/** - * @internal - */ export type ReportingPluginRouter = IRouter; -/** - * @internal - */ export interface ScreenshotOptions extends Omit { urls: UrlOrUrlLocatorTuple[]; } + +export type { BaseParams, BasePayload }; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index baa60664dea57..3593030913ba7 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -72,6 +72,7 @@ function createRule(shouldWriteAlerts: boolean = true) { scheduleActions, } as any; }, + done: () => ({ getRecoveredAlerts: () => [] }), }; return { diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts index 4e71194486d01..f9bf31ad35f6f 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts @@ -5,29 +5,18 @@ * 2.0. */ -import { map, truncate } from 'lodash'; +import { truncate } from 'lodash'; import open from 'opn'; import puppeteer, { ElementHandle, EvaluateFn, Page, SerializableOrJSHandle } from 'puppeteer'; import { parse as parseUrl } from 'url'; -import { Logger } from 'src/core/server'; +import { Headers, Logger } from 'src/core/server'; import { KBN_SCREENSHOT_MODE_HEADER, ScreenshotModePluginSetup, } from '../../../../../../src/plugins/screenshot_mode/server'; import { ConfigType } from '../../config'; import { allowRequest } from '../network_policy'; - -export interface ConditionalHeadersConditions { - protocol: string; - hostname: string; - port: number; - basePath: string; -} - -export interface ConditionalHeaders { - headers: Record; - conditions: ConditionalHeadersConditions; -} +import { stripUnsafeHeaders } from './strip_unsafe_headers'; export type Context = Record; @@ -52,8 +41,8 @@ export interface Viewport { } interface OpenOptions { - conditionalHeaders: ConditionalHeaders; context?: Context; + headers: Headers; waitForSelector: string; timeout: number; } @@ -104,6 +93,7 @@ export class HeadlessChromiumDriver { constructor( private screenshotMode: ScreenshotModePluginSetup, private config: ConfigType, + private basePath: string, private readonly page: Page ) {} @@ -123,7 +113,7 @@ export class HeadlessChromiumDriver { */ async open( url: string, - { conditionalHeaders, context, waitForSelector: pageLoadSelector, timeout }: OpenOptions, + { headers, context, waitForSelector: pageLoadSelector, timeout }: OpenOptions, logger: Logger ): Promise { logger.info(`opening url ${url}`); @@ -142,7 +132,7 @@ export class HeadlessChromiumDriver { } await this.page.setRequestInterception(true); - this.registerListeners(conditionalHeaders, logger); + this.registerListeners(url, headers, logger); await this.page.goto(url, { waitUntil: 'domcontentloaded' }); if (this.config.browser.chromium.inspect) { @@ -243,14 +233,13 @@ export class HeadlessChromiumDriver { }); } - private registerListeners(conditionalHeaders: ConditionalHeaders, logger: Logger) { + private registerListeners(url: string, customHeaders: Headers, logger: Logger) { if (this.listenersAttached) { return; } - // @ts-ignore // FIXME: retrieve the client in open() and pass in the client - const client = this.page._client; + const client = this.page.client(); // We have to reach into the Chrome Devtools Protocol to apply headers as using // puppeteer's API will cause map tile requests to hang indefinitely: @@ -277,19 +266,17 @@ export class HeadlessChromiumDriver { return; } - if (this._shouldUseCustomHeaders(conditionalHeaders.conditions, interceptedUrl)) { + if (this._shouldUseCustomHeaders(url, interceptedUrl)) { logger.trace(`Using custom headers for ${interceptedUrl}`); - const headers = map( - { - ...interceptedRequest.request.headers, - ...conditionalHeaders.headers, - [KBN_SCREENSHOT_MODE_HEADER]: 'true', - }, - (value, name) => ({ - name, - value, - }) - ); + const headers = Object.entries({ + ...interceptedRequest.request.headers, + ...stripUnsafeHeaders(customHeaders), + [KBN_SCREENSHOT_MODE_HEADER]: 'true', + }).flatMap(([name, rawValue]) => { + const values = Array.isArray(rawValue) ? rawValue : [rawValue ?? '']; + + return values.map((value) => ({ name, value })); + }); try { await client.send('Fetch.continueRequest', { @@ -353,13 +340,27 @@ export class HeadlessChromiumDriver { ); } - private _shouldUseCustomHeaders(conditions: ConditionalHeadersConditions, url: string) { - const { hostname, protocol, port, pathname } = parseUrl(url); + private _shouldUseCustomHeaders(sourceUrl: string, targetUrl: string) { + const { + hostname: sourceHostname, + protocol: sourceProtocol, + port: sourcePort, + } = parseUrl(sourceUrl); + const { + hostname: targetHostname, + protocol: targetProtocol, + port: targetPort, + pathname: targetPathname, + } = parseUrl(targetUrl); + + if (targetPathname === null) { + throw new Error(`URL missing pathname: ${targetUrl}`); + } // `port` is null in URLs that don't explicitly state it, // however we can derive the port from the protocol (http/https) // IE: https://feeds-staging.elastic.co/kibana/v8.0.0.json - const derivedPort = (() => { + const derivedPort = (protocol: string | null, port: string | null, url: string) => { if (port) { return port; } @@ -372,36 +373,15 @@ export class HeadlessChromiumDriver { return '443'; } - return null; - })(); - - if (derivedPort === null) throw new Error(`URL missing port: ${url}`); - if (pathname === null) throw new Error(`URL missing pathname: ${url}`); + throw new Error(`URL missing port: ${url}`); + }; return ( - hostname === conditions.hostname && - protocol === `${conditions.protocol}:` && - this._shouldUseCustomHeadersForPort(conditions, derivedPort) && - pathname.startsWith(`${conditions.basePath}/`) + sourceHostname === targetHostname && + sourceProtocol === targetProtocol && + derivedPort(sourceProtocol, sourcePort, sourceUrl) === + derivedPort(targetProtocol, targetPort, targetUrl) && + targetPathname.startsWith(`${this.basePath}/`) ); } - - private _shouldUseCustomHeadersForPort( - conditions: ConditionalHeadersConditions, - port: string | undefined - ) { - if (conditions.protocol === 'http' && conditions.port === 80) { - return ( - port === undefined || port === null || port === '' || port === conditions.port.toString() - ); - } - - if (conditions.protocol === 'https' && conditions.port === 443) { - return ( - port === undefined || port === null || port === '' || port === conditions.port.toString() - ); - } - - return port === conditions.port.toString(); - } } diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts index bf8a1786735eb..5c9136b272831 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts @@ -61,7 +61,7 @@ describe('HeadlessChromiumDriverFactory', () => { (puppeteer as jest.Mocked).launch.mockResolvedValue(mockBrowser); - factory = new HeadlessChromiumDriverFactory(screenshotMode, config, logger, path); + factory = new HeadlessChromiumDriverFactory(screenshotMode, config, logger, path, ''); jest.spyOn(factory, 'getBrowserLogger').mockReturnValue(Rx.EMPTY); jest.spyOn(factory, 'getProcessLogger').mockReturnValue(Rx.EMPTY); jest.spyOn(factory, 'getPageExit').mockReturnValue(Rx.EMPTY); diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts index 2f4c59707430e..4d1e0566f9943 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts @@ -108,7 +108,8 @@ export class HeadlessChromiumDriverFactory { private screenshotMode: ScreenshotModePluginSetup, private config: ConfigType, private logger: Logger, - private binaryPath: string + private binaryPath: string, + private basePath: string ) { if (this.config.browser.chromium.disableSandbox) { logger.warn(`Enabling the Chromium sandbox provides an additional layer of protection.`); @@ -243,7 +244,12 @@ export class HeadlessChromiumDriverFactory { this.getProcessLogger(browser, logger).subscribe(); // HeadlessChromiumDriver: object to "drive" a browser page - const driver = new HeadlessChromiumDriver(this.screenshotMode, this.config, page); + const driver = new HeadlessChromiumDriver( + this.screenshotMode, + this.config, + this.basePath, + page + ); // Rx.Observable: stream to interrupt page capture const unexpectedExit$ = this.getPageExit(browser, page); diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts index 88572081c5491..bc32e719e0f79 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts @@ -9,7 +9,7 @@ export const getChromiumDisconnectedError = () => new Error('Browser was closed unexpectedly! Check the server logs for more info.'); export { HeadlessChromiumDriver } from './driver'; -export type { ConditionalHeaders, Context } from './driver'; +export type { Context } from './driver'; export { DEFAULT_VIEWPORT, HeadlessChromiumDriverFactory } from './driver_factory'; export type { PerformanceMetrics } from './driver_factory'; export { ChromiumArchivePaths } from './paths'; diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/strip_unsafe_headers.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/strip_unsafe_headers.test.ts new file mode 100644 index 0000000000000..38feae0b2c026 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/strip_unsafe_headers.test.ts @@ -0,0 +1,37 @@ +/* + * 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 { stripUnsafeHeaders } from './strip_unsafe_headers'; + +describe('stripUnsafeHeaders', () => { + it.each` + header | value + ${'accept-encoding'} | ${''} + ${'connection'} | ${'upgrade'} + ${'content-length'} | ${''} + ${'content-type'} | ${''} + ${'host'} | ${''} + ${'transfer-encoding'} | ${''} + ${'proxy-connection'} | ${'bananas'} + ${'proxy-authorization'} | ${'some-base64-encoded-thing'} + ${'trailer'} | ${'s are for trucks'} + `('should strip unsafe header "$header"', ({ header, value }) => { + const headers = { [header]: value }; + + expect(stripUnsafeHeaders(headers)).toEqual({}); + }); + + it.each` + header | value + ${'foo'} | ${'bar'} + ${'baz'} | ${'quix'} + `('should keep safe header "$header"', ({ header, value }) => { + const headers = { [header]: value }; + + expect(stripUnsafeHeaders(headers)).toEqual(headers); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/strip_unsafe_headers.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/strip_unsafe_headers.ts new file mode 100644 index 0000000000000..7e3f46775ea4e --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/strip_unsafe_headers.ts @@ -0,0 +1,40 @@ +/* + * 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 { omitBy } from 'lodash'; +import type { Headers } from 'src/core/server'; + +// @see https://github.com/chromium/chromium/blob/3611052c055897e5ebbc5b73ea295092e0c20141/services/network/public/cpp/header_util_unittest.cc#L50 +// For a list of headers that chromium doesn't like +const UNSAFE_HEADERS = [ + 'accept-encoding', + 'connection', + 'content-length', + 'content-type', + 'host', + 'referer', + // `Transfer-Encoding` is hop-by-hop header that is meaningful + // only for a single transport-level connection, and shouldn't + // be stored by caches or forwarded by proxies. + 'transfer-encoding', + 'trailer', + 'te', + 'upgrade', + 'keep-alive', +]; + +const UNSAFE_HEADERS_PATTERNS = [/^proxy-/i]; + +export function stripUnsafeHeaders(headers: Headers): Headers { + return omitBy( + headers, + (_value, header: string) => + header && + (UNSAFE_HEADERS.includes(header) || + UNSAFE_HEADERS_PATTERNS.some((pattern) => pattern.test(header))) + ); +} diff --git a/x-pack/plugins/screenshotting/server/browsers/index.ts b/x-pack/plugins/screenshotting/server/browsers/index.ts index 51687d74ff31d..15c8b2b2db06b 100644 --- a/x-pack/plugins/screenshotting/server/browsers/index.ts +++ b/x-pack/plugins/screenshotting/server/browsers/index.ts @@ -7,7 +7,7 @@ export { download } from './download'; export { install } from './install'; -export type { ConditionalHeaders, Context, PerformanceMetrics } from './chromium'; +export type { Context, PerformanceMetrics } from './chromium'; export { getChromiumDisconnectedError, ChromiumArchivePaths, diff --git a/x-pack/plugins/screenshotting/server/plugin.ts b/x-pack/plugins/screenshotting/server/plugin.ts index 138193815debe..e0c5d45f67d75 100755 --- a/x-pack/plugins/screenshotting/server/plugin.ts +++ b/x-pack/plugins/screenshotting/server/plugin.ts @@ -54,7 +54,7 @@ export class ScreenshottingPlugin implements Plugin { const paths = new ChromiumArchivePaths(); @@ -63,8 +63,15 @@ export class ScreenshottingPlugin implements Plugin { this.logger.error('Error in screenshotting setup, it may not function properly.'); diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts index ff5c910e9cc3e..b129d875f018d 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -37,7 +37,7 @@ describe('Screenshot Observable Pipeline', () => { } as unknown as jest.Mocked; options = { browserTimezone: 'UTC', - conditionalHeaders: {}, + headers: {}, layout: {}, timeouts: { loadDelay: 2000, diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.ts b/x-pack/plugins/screenshotting/server/screenshots/index.ts index e8a90145f77e6..b056e9a5450ed 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.ts @@ -18,7 +18,7 @@ import { tap, toArray, } from 'rxjs/operators'; -import type { Logger } from 'src/core/server'; +import type { KibanaRequest, Logger } from 'src/core/server'; import { LayoutParams } from '../../common'; import type { ConfigType } from '../config'; import type { HeadlessChromiumDriverFactory, PerformanceMetrics } from '../browsers'; @@ -30,6 +30,11 @@ import { Semaphore } from './semaphore'; export interface ScreenshotOptions extends ScreenshotObservableOptions { layout: LayoutParams; + + /** + * Source Kibana request object from where the headers will be extracted. + */ + request?: KibanaRequest; } export interface ScreenshotResult { @@ -77,6 +82,7 @@ export class Screenshots { browserTimezone, timeouts: { openUrl: openUrlTimeout }, } = options; + const headers = { ...(options.request?.headers ?? {}), ...(options.headers ?? {}) }; return this.browserDriverFactory .createPage( @@ -93,7 +99,10 @@ export class Screenshots { apmCreatePage?.end(); unexpectedExit$.subscribe({ error: () => apmTrans?.end() }); - const screen = new ScreenshotObservableHandler(driver, this.logger, layout, options); + const screen = new ScreenshotObservableHandler(driver, this.logger, layout, { + ...options, + headers, + }); return from(options.urls).pipe( concatMap((url, index) => diff --git a/x-pack/plugins/screenshotting/server/screenshots/observable.test.ts b/x-pack/plugins/screenshotting/server/screenshots/observable.test.ts index 5d5fbbde4e048..83800a168ddbe 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/observable.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/observable.test.ts @@ -8,7 +8,6 @@ import { interval, throwError, of } from 'rxjs'; import { map } from 'rxjs/operators'; import type { Logger } from 'src/core/server'; -import type { ConditionalHeaders } from '../browsers'; import { createMockBrowserDriver } from '../browsers/mock'; import { createMockLayout } from '../layouts/mock'; import { ScreenshotObservableHandler, ScreenshotObservableOptions } from './observable'; @@ -24,10 +23,7 @@ describe('ScreenshotObservableHandler', () => { layout = createMockLayout(); logger = { error: jest.fn() } as unknown as jest.Mocked; options = { - conditionalHeaders: { - headers: { testHeader: 'testHeadValue' }, - conditions: {} as unknown as ConditionalHeaders['conditions'], - }, + headers: { testHeader: 'testHeadValue' }, timeouts: { loadDelay: 5000, openUrl: 30000, diff --git a/x-pack/plugins/screenshotting/server/screenshots/observable.ts b/x-pack/plugins/screenshotting/server/screenshots/observable.ts index 5cfda2c841cb8..a743c206ef98d 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/observable.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/observable.ts @@ -8,8 +8,8 @@ import type { Transaction } from 'elastic-apm-node'; import { defer, forkJoin, throwError, Observable } from 'rxjs'; import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators'; -import type { Logger } from 'src/core/server'; -import type { ConditionalHeaders, Context, HeadlessChromiumDriver } from '../browsers'; +import type { Headers, Logger } from 'src/core/server'; +import type { Context, HeadlessChromiumDriver } from '../browsers'; import { getChromiumDisconnectedError, DEFAULT_VIEWPORT } from '../browsers'; import type { Layout } from '../layouts'; import type { ElementsPositionAndAttribute } from './get_element_position_data'; @@ -60,7 +60,7 @@ export interface ScreenshotObservableOptions { /** * Custom headers to be sent with each request. */ - conditionalHeaders: ConditionalHeaders; + headers?: Headers; /** * Timeouts for each phase of the screenshot. @@ -177,7 +177,7 @@ export class ScreenshotObservableHandler { index, url, { ...(context ?? {}), layout: this.layout.id }, - this.options.conditionalHeaders + this.options.headers ?? {} ); }).pipe(this.waitUntil(this.options.timeouts.openUrl, 'open URL')); } diff --git a/x-pack/plugins/screenshotting/server/screenshots/open_url.ts b/x-pack/plugins/screenshotting/server/screenshots/open_url.ts index a4c4bd6217205..8b26f35961db4 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/open_url.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/open_url.ts @@ -6,9 +6,9 @@ */ import apm from 'elastic-apm-node'; -import type { Logger } from 'src/core/server'; +import type { Headers, Logger } from 'src/core/server'; import type { HeadlessChromiumDriver } from '../browsers'; -import type { ConditionalHeaders, Context } from '../browsers'; +import type { Context } from '../browsers'; import { DEFAULT_PAGELOAD_SELECTOR } from './constants'; export const openUrl = async ( @@ -18,7 +18,7 @@ export const openUrl = async ( index: number, url: string, context: Context, - conditionalHeaders: ConditionalHeaders + headers: Headers ): Promise => { // If we're moving to another page in the app, we'll want to wait for the app to tell us // it's loaded the next page. @@ -27,7 +27,7 @@ export const openUrl = async ( const span = apm.startSpan('open_url', 'wait'); try { - await browser.open(url, { conditionalHeaders, context, waitForSelector, timeout }, logger); + await browser.open(url, { context, headers, waitForSelector, timeout }, logger); } catch (err) { logger.error(err); throw new Error(`An error occurred when trying to open the Kibana URL: ${err.message}`); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/perform_bulk_action_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/perform_bulk_action_schema.ts deleted file mode 100644 index 2bc3ea8448ee9..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/perform_bulk_action_schema.ts +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; - -const rule = t.type({ - id: t.string, - name: t.union([t.string, t.undefined]), -}); - -const error = t.type({ - status_code: PositiveIntegerGreaterThanZero, - message: t.string, - rules: t.array(rule), -}); - -export const bulkActionPartialErrorResponseSchema = t.exact( - t.type({ - status_code: PositiveIntegerGreaterThanZero, - message: t.string, - attributes: t.type({ - errors: t.array(error), - rules: t.type({ - failed: PositiveInteger, - succeeded: PositiveInteger, - total: PositiveInteger, - }), - }), - }) -); - -export type BulkActionPartialErrorResponse = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 25bc0c48e4ac0..0ae0dda273958 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -22,7 +22,6 @@ export const allowedExperimentalValues = Object.freeze({ riskyHostsEnabled: false, securityRulesCancelEnabled: false, pendingActionResponsesWithAck: true, - rulesBulkEditEnabled: true, policyListEnabled: false, /** diff --git a/x-pack/plugins/security_solution/cypress/integration/data_sources/create_runtime_field.spec.ts b/x-pack/plugins/security_solution/cypress/integration/data_sources/create_runtime_field.spec.ts index 79a2314af9397..1b9c63dd2dbcb 100644 --- a/x-pack/plugins/security_solution/cypress/integration/data_sources/create_runtime_field.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/data_sources/create_runtime_field.spec.ts @@ -26,7 +26,7 @@ describe('Create DataView runtime field', () => { cleanKibana(); }); - it('adds field to alert table', () => { + it.skip('adds field to alert table', () => { const fieldName = 'field.name.alert.page'; loginAndWaitForPage(ALERTS_URL); createCustomRuleActivated(getNewRule()); diff --git a/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts index 62ba50a494df5..05b9cb567fafd 100644 --- a/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/data_sources/sourcerer.spec.ts @@ -139,7 +139,7 @@ describe('Timeline scope', () => { loginAndWaitForPage(TIMELINES_URL); }); - it('correctly loads SIEM data view before and after signals index exists', () => { + it.skip('correctly loads SIEM data view before and after signals index exists', () => { openTimelineUsingToggle(); openSourcerer('timeline'); isDataViewSelection(siemDataViewTitle); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/acknowledged.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/acknowledged.spec.ts index 0887c4774f9a8..32ce0bebda225 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/acknowledged.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/acknowledged.spec.ts @@ -26,7 +26,7 @@ import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; -describe('Marking alerts as acknowledged', () => { +describe.skip('Marking alerts as acknowledged', () => { beforeEach(() => { cleanKibana(); loginAndWaitForPage(ALERTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 16beb418d0d13..2d5a676646688 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -26,7 +26,7 @@ import { getUnmappedRule } from '../../objects/rule'; import { ALERTS_URL } from '../../urls/navigation'; -describe('Alert details with unmapped fields', () => { +describe.skip('Alert details with unmapped fields', () => { beforeEach(() => { cleanKibana(); esArchiverLoad('unmapped_fields'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts index ce232f7b84157..436ef0975ef02 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts @@ -23,7 +23,7 @@ const loadDetectionsPage = (role: ROLES) => { waitForAlertsToPopulate(); }; -describe('Alerts timeline', () => { +describe.skip('Alerts timeline', () => { before(() => { // First we login as a privileged user to create alerts. cleanKibana(); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts index bdd83d93fa25d..288d16dc22fb6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts @@ -18,7 +18,7 @@ import { ALERTS_URL, DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigatio const EXPECTED_NUMBER_OF_ALERTS = 16; -describe('Alerts generated by building block rules', () => { +describe.skip('Alerts generated by building block rules', () => { beforeEach(() => { cleanKibana(); loginAndWaitForPageWithoutDateRange(ALERTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts index efa7f31455b1f..af2772b98a790 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts @@ -31,7 +31,7 @@ import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; -describe('Closing alerts', () => { +describe.skip('Closing alerts', () => { beforeEach(() => { cleanKibana(); loginAndWaitForPage(ALERTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts index 0e4dbc9a95f9c..c5e015b6382c2 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts @@ -33,7 +33,7 @@ import { openJsonView, openThreatIndicatorDetails } from '../../tasks/alerts_det import { ALERTS_URL } from '../../urls/navigation'; import { addsFieldsToTimeline } from '../../tasks/rule_details'; -describe('CTI Enrichment', () => { +describe.skip('CTI Enrichment', () => { before(() => { cleanKibana(); esArchiverLoad('threat_indicator'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts index 033a559ffff98..e8873de412f4c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts @@ -17,7 +17,7 @@ import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; -describe('Alerts timeline', () => { +describe.skip('Alerts timeline', () => { beforeEach(() => { cleanKibana(); loginAndWaitForPage(ALERTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts index 20a2f6ebed3e2..ece7dbe559672 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts @@ -29,7 +29,7 @@ import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; -describe('Opening alerts', () => { +describe.skip('Opening alerts', () => { beforeEach(() => { cleanKibana(); loginAndWaitForPage(ALERTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts index 530ec4934b447..b98f626c6356c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts @@ -105,7 +105,7 @@ import { activatesRule, getDetails } from '../../tasks/rule_details'; import { RULE_CREATION, DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; -describe('Custom detection rules creation', () => { +describe.skip('Custom detection rules creation', () => { const expectedUrls = getNewRule().referenceUrls.join(''); const expectedFalsePositives = getNewRule().falsePositivesExamples.join(''); const expectedTags = getNewRule().tags.join(''); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts index c3797cd4b178c..8384c879d8110 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts @@ -63,7 +63,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { RULE_CREATION } from '../../urls/navigation'; -describe('Detection rules, EQL', () => { +describe.skip('Detection rules, EQL', () => { const expectedUrls = getEqlRule().referenceUrls.join(''); const expectedFalsePositives = getEqlRule().falsePositivesExamples.join(''); const expectedTags = getEqlRule().tags.join(''); @@ -159,7 +159,7 @@ describe('Detection rules, EQL', () => { }); }); -describe('Detection rules, sequence EQL', () => { +describe.skip('Detection rules, sequence EQL', () => { const expectedNumberOfRules = 1; const expectedNumberOfSequenceAlerts = '1 alert'; diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index da8b089ee186d..d34d9bd4fc171 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -114,7 +114,7 @@ import { goBackToAllRulesTable, getDetails } from '../../tasks/rule_details'; import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation'; const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"'; -describe('indicator match', () => { +describe.skip('indicator match', () => { describe('Detection rules, Indicator Match', () => { const expectedUrls = getNewThreatIndicatorRule().referenceUrls.join(''); const expectedFalsePositives = getNewThreatIndicatorRule().falsePositivesExamples.join(''); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts index e0596d52809e0..bf8d753a8161c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts @@ -57,7 +57,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { RULE_CREATION } from '../../urls/navigation'; -describe('Detection rules, machine learning', () => { +describe.skip('Detection rules, machine learning', () => { const expectedUrls = getMachineLearningRule().referenceUrls.join(''); const expectedFalsePositives = getMachineLearningRule().falsePositivesExamples.join(''); const expectedTags = getMachineLearningRule().tags.join(''); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts index 6d078b3da24c0..694036d8a1678 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts @@ -73,7 +73,7 @@ import { getDetails } from '../../tasks/rule_details'; import { RULE_CREATION } from '../../urls/navigation'; -describe('Detection rules, override', () => { +describe.skip('Detection rules, override', () => { const expectedUrls = getNewOverrideRule().referenceUrls.join(''); const expectedFalsePositives = getNewOverrideRule().falsePositivesExamples.join(''); const expectedTags = getNewOverrideRule().tags.join(''); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index ec84359e63712..921128ce3303d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -77,7 +77,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { RULE_CREATION } from '../../urls/navigation'; -describe('Detection rules, threshold', () => { +describe.skip('Detection rules, threshold', () => { let rule = getNewThresholdRule(); const expectedUrls = getNewThresholdRule().referenceUrls.join(''); const expectedFalsePositives = getNewThresholdRule().falsePositivesExamples.join(''); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts index 2e5994e73ac52..9887eb1e8612b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts @@ -29,7 +29,7 @@ import { import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; import { cleanKibana, reload } from '../../tasks/common'; -describe('From alert', () => { +describe.skip('From alert', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert'; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts index 1262bea01708d..d9661324aee6d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts @@ -30,7 +30,7 @@ import { refreshPage } from '../../tasks/security_header'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; import { cleanKibana, reload } from '../../tasks/common'; -describe('From rule', () => { +describe.skip('From rule', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1'; beforeEach(() => { cleanKibana(); diff --git a/x-pack/plugins/security_solution/cypress/integration/users/user_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/users/user_details.spec.ts index d718b499f199d..a30b651bfba39 100644 --- a/x-pack/plugins/security_solution/cypress/integration/users/user_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/users/user_details.spec.ts @@ -20,7 +20,7 @@ import { } from '../../tasks/alerts'; import { USER_COLUMN } from '../../screens/alerts'; -describe('user details flyout', () => { +describe.skip('user details flyout', () => { beforeEach(() => { cleanKibana(); loginAndWaitForPageWithoutDateRange(ALERTS_URL); diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index 1f02ff88b19bd..ba592f0ccc61b 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -102,14 +102,7 @@ const CaseContainerComponent: React.FC = () => { basePath: CASES_PATH, owner: [APP_ID], features: { - metrics: [ - 'alerts.count', - 'alerts.users', - 'alerts.hosts', - 'actions.isolateHost', - 'connectors', - 'lifespan', - ], + metrics: ['alerts.count', 'alerts.users', 'alerts.hosts', 'connectors', 'lifespan'], }, refreshRef, onComponentInitialized, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx index 24b907e6bd938..4bb4c4809764a 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx @@ -258,6 +258,105 @@ describe('AlertSummaryView', () => { }); }); + test('Ransomware event code shows correct fields', () => { + const enhancedData = [ + ...mockAlertDetailsData.map((item) => { + if (item.category === 'event' && item.field === 'event.code') { + return { + ...item, + values: ['ransomware'], + originalValue: ['ransomware'], + }; + } + return item; + }), + { category: 'Ransomware', field: 'Ransomware.feature', values: ['mbr'] }, + { + category: 'process', + field: 'process.hash.sha256', + values: ['3287rhf3847gb38fb3o984g9384g7b3b847gb'], + }, + ] as TimelineEventsDetailsItem[]; + const renderProps = { + ...props, + data: enhancedData, + }; + const { getByText } = render( + + + + ); + ['process.hash.sha256', 'Ransomware.feature'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); + }); + + test('Machine learning events show correct fields', () => { + const enhancedData = [ + ...mockAlertDetailsData.map((item) => { + if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { + return { + ...item, + values: ['machine_learning'], + originalValue: ['machine_learning'], + }; + } + return item; + }), + { + category: 'kibana', + field: 'kibana.alert.rule.parameters.machine_learning_job_id', + values: ['i_am_the_ml_job_id'], + }, + { category: 'kibana', field: 'kibana.alert.rule.parameters.anomaly_threshold', values: [2] }, + ] as TimelineEventsDetailsItem[]; + const renderProps = { + ...props, + data: enhancedData, + }; + const { getByText } = render( + + + + ); + ['i_am_the_ml_job_id', 'kibana.alert.rule.parameters.anomaly_threshold'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); + }); + + test('Threat match events show correct fields', () => { + const enhancedData = [ + ...mockAlertDetailsData.map((item) => { + if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { + return { + ...item, + values: ['threat_match'], + originalValue: ['threat_match'], + }; + } + return item; + }), + { + category: 'kibana', + field: 'kibana.alert.rule.threat_index', + values: ['threat_index*'], + }, + { category: 'kibana', field: 'kibana.alert.rule.threat_query', values: ['*query*'] }, + ] as TimelineEventsDetailsItem[]; + const renderProps = { + ...props, + data: enhancedData, + }; + const { getByText } = render( + + + + ); + ['threat_index*', '*query*'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); + }); + test('Ransomware event code resolves fields from the source event', () => { const renderProps = { ...props, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx index af93393e5b8a4..9f0dfb53a5c4b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx @@ -6,7 +6,7 @@ */ import { find, isEmpty, uniqBy } from 'lodash/fp'; -import { ALERT_RULE_NAMESPACE, ALERT_RULE_TYPE } from '@kbn/rule-data-utils'; +import { ALERT_RULE_NAMESPACE, ALERT_RULE_PARAMETERS, ALERT_RULE_TYPE } from '@kbn/rule-data-utils'; import * as i18n from './translations'; import { BrowserFields } from '../../../../common/search_strategy/index_fields'; @@ -14,7 +14,6 @@ import { ALERTS_HEADERS_THRESHOLD_CARDINALITY, ALERTS_HEADERS_THRESHOLD_COUNT, ALERTS_HEADERS_THRESHOLD_TERMS, - ALERTS_HEADERS_TARGET_IMPORT_HASH, ALERTS_HEADERS_RULE_DESCRIPTION, } from '../../../detections/components/alerts_table/translations'; import { ALERT_THRESHOLD_RESULT } from '../../../../common/field_maps/field_names'; @@ -111,16 +110,17 @@ function getFieldsByEventCode( case EventCode.SHELLCODE_THREAD: return [ { id: 'Target.process.executable' }, - { - id: 'Target.process.thread.Ext.start_address_detaiuls.memory_pe.imphash', - label: ALERTS_HEADERS_TARGET_IMPORT_HASH, - }, { id: 'Memory_protection.unique_key_v1', }, ]; - case EventCode.MEMORY_SIGNATURE: case EventCode.RANSOMWARE: + return [ + { id: 'Ransomware.feature' }, + { id: 'process.hash.sha256' }, + ...getFieldsByCategory({ ...eventCategories, primaryEventCategory: undefined }), + ]; + case EventCode.MEMORY_SIGNATURE: // Resolve more fields based on the source event return getFieldsByCategory({ ...eventCategories, primaryEventCategory: undefined }); default: @@ -145,10 +145,10 @@ function getFieldsByRuleType(ruleType?: string): EventSummaryField[] { case 'machine_learning': return [ { - id: `${ALERT_RULE_NAMESPACE}.machine_learning_job_id`, + id: `${ALERT_RULE_PARAMETERS}.machine_learning_job_id`, }, { - id: `${ALERT_RULE_NAMESPACE}.anomaly_threshold`, + id: `${ALERT_RULE_PARAMETERS}.anomaly_threshold`, }, ]; case 'threat_match': @@ -157,7 +157,7 @@ function getFieldsByRuleType(ruleType?: string): EventSummaryField[] { id: `${ALERT_RULE_NAMESPACE}.threat_index`, }, { - id: `${ALERT_RULE_NAMESPACE}.index`, + id: `${ALERT_RULE_NAMESPACE}.threat_query`, }, ]; default: diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts index 411dd5542038b..ebf361914ca3b 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts @@ -157,8 +157,8 @@ export const useGetUserCasesPermissions = () => { useEffect(() => { setCasesPermissions({ - crud: !!uiCapabilities[CASES_FEATURE_ID].crud_cases, - read: !!uiCapabilities[CASES_FEATURE_ID].read_cases, + crud: !!uiCapabilities[CASES_FEATURE_ID]?.crud_cases, + read: !!uiCapabilities[CASES_FEATURE_ID]?.read_cases, }); }, [uiCapabilities]); diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_cases_context.tsx b/x-pack/plugins/security_solution/public/common/mock/mock_cases_context.tsx new file mode 100644 index 0000000000000..66c3fc2c932eb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/mock/mock_cases_context.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; + +export const mockCasesContext: React.FC = (props) => { + return <>{props?.children ?? null}; +}; +mockCasesContext.displayName = 'CasesContextMock'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 256a063c44158..1499e803fdf37 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -10,6 +10,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; import { Dispatch } from 'redux'; import type { Filter } from '@kbn/es-query'; +import { APP_ID } from '../../../../common/constants'; import { getEsQueryConfig } from '../../../../../../../src/plugins/data/common'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { RowRendererId, TimelineIdLiteral } from '../../../../common/types/timeline'; @@ -24,7 +25,7 @@ import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; import { defaultCellActions } from '../../../common/lib/cell_actions/default_cell_actions'; -import { useKibana } from '../../../common/lib/kibana'; +import { useGetUserCasesPermissions, useKibana } from '../../../common/lib/kibana'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import * as i18nCommon from '../../../common/translations'; @@ -356,29 +357,34 @@ export const AlertsTableComponent: React.FC = ({ const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); + const casesPermissions = useGetUserCasesPermissions(); + const CasesContext = kibana.services.cases.getCasesContext(); + if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { return null; } return ( - + + + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 1897ad45fe7ff..590b5759ecae4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -136,13 +136,6 @@ export const ALERTS_HEADERS_THRESHOLD_CARDINALITY = i18n.translate( } ); -export const ALERTS_HEADERS_TARGET_IMPORT_HASH = i18n.translate( - 'xpack.securitySolution.eventsViewer.alerts.overviewTable.targetImportHash', - { - defaultMessage: 'Import Hash', - } -); - export const ACTION_OPEN_ALERT = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.actions.openAlertTitle', { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts index c765db6eaecd8..427cf28ef8f2f 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts @@ -45,7 +45,7 @@ import { BulkRuleResponse, PatchRuleProps, BulkActionProps, - BulkActionResponse, + BulkActionResponseMap, PreviewRulesProps, } from './types'; import { KibanaServices } from '../../../../common/lib/kibana'; @@ -266,16 +266,19 @@ export const performBulkAction = async ({ query, edit, ids, -}: BulkActionProps): Promise> => - KibanaServices.get().http.fetch>(DETECTION_ENGINE_RULES_BULK_ACTION, { - method: 'POST', - body: JSON.stringify({ - action, - ...(edit ? { edit } : {}), - ...(ids ? { ids } : {}), - ...(query !== undefined ? { query } : {}), - }), - }); +}: BulkActionProps): Promise> => + KibanaServices.get().http.fetch>( + DETECTION_ENGINE_RULES_BULK_ACTION, + { + method: 'POST', + body: JSON.stringify({ + action, + ...(edit ? { edit } : {}), + ...(ids ? { ids } : {}), + ...(query !== undefined ? { query } : {}), + }), + } + ); /** * Create Prepackaged Rules diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index 7943bca613488..af5e4fd568a64 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -236,18 +236,41 @@ export interface BulkActionProps { edit?: BulkActionEditPayload[]; } +export interface BulkActionSummary { + failed: number; + succeeded: number; + total: number; +} + export interface BulkActionResult { - success: boolean; - rules_count: number; + updated: Rule[]; + created: Rule[]; + deleted: Rule[]; +} + +export interface BulkActionAggregatedError { + message: string; + status_code: number; + rules: Array<{ id: string; name?: string }>; +} + +export interface BulkActionResponse { + success?: boolean; + rules_count?: number; + attributes: { + summary: BulkActionSummary; + results: BulkActionResult; + errors?: BulkActionAggregatedError[]; + }; } -export type BulkActionResponse = { - [BulkAction.delete]: BulkActionResult; - [BulkAction.disable]: BulkActionResult; - [BulkAction.enable]: BulkActionResult; - [BulkAction.duplicate]: BulkActionResult; +export type BulkActionResponseMap = { + [BulkAction.delete]: BulkActionResponse; + [BulkAction.disable]: BulkActionResponse; + [BulkAction.enable]: BulkActionResponse; + [BulkAction.duplicate]: BulkActionResponse; [BulkAction.export]: Blob; - [BulkAction.edit]: BulkActionResult; + [BulkAction.edit]: BulkActionResponse; }[Action]; export interface BasicFetchProps { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index c5d053c57fc97..4b6cbb6f7e16d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -24,6 +24,7 @@ import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../common/mock/router'; import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; import { mockBrowserFields } from '../../../common/containers/source/mock'; +import { mockCasesContext } from '../../../common/mock/mock_cases_context'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -72,6 +73,9 @@ jest.mock('../../../common/lib/kibana', () => { siem: { crud_alerts: true, read_alerts: true }, }, }, + cases: { + getCasesContext: mockCasesContext, + }, uiSettings: { get: jest.fn(), }, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.ts index 5b2fdbc5e290e..29b374f3fb26e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.ts @@ -209,7 +209,7 @@ const executeRulesBulkAction = async ({ } else { const response = await performBulkAction({ ...search, action, edit: payload?.edit }); - onSuccess?.({ rulesCount: response.rules_count }); + onSuccess?.({ rulesCount: response.attributes.summary.succeeded }); } } catch (e) { if (onError) { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx index 4d3767e73a59e..7058b95bf5fc9 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx @@ -42,11 +42,12 @@ import { useHasActionsPrivileges } from '../use_has_actions_privileges'; import { useHasMlPermissions } from '../use_has_ml_permissions'; import { getCustomRulesCountFromCache } from './use_custom_rules_count'; import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; -import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features'; import { convertRulesFilterToKQL } from '../../../../../containers/detection_engine/rules/utils'; -import type { FilterOptions } from '../../../../../containers/detection_engine/rules/types'; -import type { BulkActionPartialErrorResponse } from '../../../../../../../common/detection_engine/schemas/response/perform_bulk_action_schema'; +import type { + BulkActionResponse, + FilterOptions, +} from '../../../../../containers/detection_engine/rules/types'; import type { HTTPError } from '../../../../../../../common/detection_engine/types'; import { useInvalidateRules } from '../../../../../containers/detection_engine/rules/use_find_rules_query'; @@ -74,7 +75,6 @@ export const useBulkActions = ({ const [, dispatchToaster] = useStateToaster(); const hasActionsPrivileges = useHasActionsPrivileges(); const toasts = useAppToasts(); - const isRulesBulkEditEnabled = useIsExperimentalFeatureEnabled('rulesBulkEditEnabled'); const getIsMounted = useIsMounted(); const filterQuery = convertRulesFilterToKQL(filterOptions); @@ -300,8 +300,8 @@ export const useBulkActions = ({ hideWarningToast(); // if response doesn't have number of failed rules, it means the whole bulk action failed // and general error toast will be shown. Otherwise - error toast for partial failure - const failedRulesCount = (error?.body as BulkActionPartialErrorResponse)?.attributes - ?.rules?.failed; + const failedRulesCount = (error?.body as BulkActionResponse)?.attributes?.summary + ?.failed; if (isNaN(failedRulesCount)) { toasts.addError(error, { title: i18n.BULK_ACTION_FAILED }); @@ -340,7 +340,7 @@ export const useBulkActions = ({ return [ { id: 0, - title: isRulesBulkEditEnabled ? i18n.BULK_ACTION_MENU_TITLE : undefined, + title: i18n.BULK_ACTION_MENU_TITLE, items: [ { key: i18n.BULK_ACTION_ENABLE, @@ -351,7 +351,7 @@ export const useBulkActions = ({ onClick: handleActivateAction, toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, toolTipPosition: 'right', - icon: isRulesBulkEditEnabled ? undefined : 'checkInCircleFilled', + icon: undefined, }, { key: i18n.BULK_ACTION_DUPLICATE, @@ -361,26 +361,22 @@ export const useBulkActions = ({ onClick: handleDuplicateAction, toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, toolTipPosition: 'right', - icon: isRulesBulkEditEnabled ? undefined : 'crossInACircleFilled', + icon: undefined, + }, + { + key: i18n.BULK_ACTION_INDEX_PATTERNS, + name: i18n.BULK_ACTION_INDEX_PATTERNS, + 'data-test-subj': 'indexPatternsBulkEditRule', + disabled: isEditDisabled, + panel: 2, + }, + { + key: i18n.BULK_ACTION_TAGS, + name: i18n.BULK_ACTION_TAGS, + 'data-test-subj': 'tagsBulkEditRule', + disabled: isEditDisabled, + panel: 1, }, - ...(isRulesBulkEditEnabled - ? [ - { - key: i18n.BULK_ACTION_INDEX_PATTERNS, - name: i18n.BULK_ACTION_INDEX_PATTERNS, - 'data-test-subj': 'indexPatternsBulkEditRule', - disabled: isEditDisabled, - panel: 2, - }, - { - key: i18n.BULK_ACTION_TAGS, - name: i18n.BULK_ACTION_TAGS, - 'data-test-subj': 'tagsBulkEditRule', - disabled: isEditDisabled, - panel: 1, - }, - ] - : []), { key: i18n.BULK_ACTION_EXPORT, name: i18n.BULK_ACTION_EXPORT, @@ -390,7 +386,7 @@ export const useBulkActions = ({ containsLoading || selectedRuleIds.length === 0, onClick: handleExportAction, - icon: isRulesBulkEditEnabled ? undefined : 'exportAction', + icon: undefined, }, { key: i18n.BULK_ACTION_DISABLE, @@ -401,7 +397,7 @@ export const useBulkActions = ({ onClick: handleDeactivateActions, toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, toolTipPosition: 'right', - icon: isRulesBulkEditEnabled ? undefined : 'copy', + icon: undefined, }, { key: i18n.BULK_ACTION_DELETE, @@ -419,7 +415,7 @@ export const useBulkActions = ({ ? i18n.BATCH_ACTION_DELETE_SELECTED_IMMUTABLE : undefined, toolTipPosition: 'right', - icon: isRulesBulkEditEnabled ? undefined : 'trash', + icon: undefined, }, ], }, @@ -477,7 +473,6 @@ export const useBulkActions = ({ rules, selectedRuleIds, hasActionsPrivileges, - isRulesBulkEditEnabled, isAllSelected, loadingRuleIds, hasMlPermissions, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index db927e67ccc67..66a140987475c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -24,6 +24,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; import { ColumnHeaderOptions, TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../../common/lib/kibana/hooks'); jest.mock('../../../../common/hooks/use_app_toasts'); @@ -40,6 +41,9 @@ jest.mock('../../../../common/lib/kibana', () => { siem: { crud_alerts: true, read_alerts: true }, }, }, + cases: { + getCasesContext: () => mockCasesContext, + }, data: { search: jest.fn(), query: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 7257d4246f6fe..a9d0028f6d9db 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -11,6 +11,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { connect, ConnectedProps, useDispatch } from 'react-redux'; import deepEqual from 'fast-deep-equal'; +import { APP_ID } from '../../../../../common/constants'; +import { useGetUserCasesPermissions, useKibana } from '../../../../common/lib/kibana'; import { FIRST_ARIA_INDEX, ARIA_COLINDEX_ATTRIBUTE, @@ -225,60 +227,65 @@ export const BodyComponent = React.memo( }, [columnHeaders.length, containerRef, data.length] ); + const kibana = useKibana(); + const casesPermissions = useGetUserCasesPermissions(); + const CasesContext = kibana.services.cases.getCasesContext(); return ( <> - - + + + - - + + + diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx index b4bd3aa1f0ae2..43622b7e45365 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx @@ -23,6 +23,7 @@ import { useTimelineEventsDetails } from '../../../containers/details/index'; import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../containers/index', () => ({ useTimelineEvents: jest.fn(), @@ -56,6 +57,9 @@ jest.mock('../../../../common/lib/kibana', () => { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), }, + cases: { + getCasesContext: () => mockCasesContext, + }, docLinks: { links: { query: { eql: 'url-eql_doc' } } }, uiSettings: { get: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx index 8707bb33da08c..ffe50f935b9fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx @@ -24,6 +24,7 @@ import { mockSourcererScope } from '../../../../common/containers/sourcerer/mock import { PinnedTabContentComponent, Props as PinnedTabContentComponentProps } from '.'; import { Direction } from '../../../../../common/search_strategy'; import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../containers/index', () => ({ useTimelineEvents: jest.fn(), @@ -51,6 +52,9 @@ jest.mock('../../../../common/lib/kibana', () => { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), }, + cases: { + getCasesContext: () => mockCasesContext, + }, uiSettings: { get: jest.fn(), }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index 580f5cf9cc2ae..019bedacbffe8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -26,6 +26,7 @@ import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { Direction } from '../../../../../common/search_strategy'; import * as helpers from '../helpers'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../containers/index', () => ({ useTimelineEvents: jest.fn(), @@ -59,6 +60,9 @@ jest.mock('../../../../common/lib/kibana', () => { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), }, + cases: { + getCasesContext: () => mockCasesContext, + }, uiSettings: { get: jest.fn(), }, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts index 74a51a6e16199..7181b97b4ff68 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts @@ -17,13 +17,13 @@ import { ANCESTRY_LIMIT, EndpointDocGenerator } from '../../common/endpoint/gene main(); -const handleErr = (err: unknown) => { +function handleErr(err: unknown) { if (err instanceof errors.ResponseError && err.statusCode !== 404) { console.log(JSON.stringify(err, null, 2)); // eslint-disable-next-line no-process-exit process.exit(1); } -}; +} async function deleteIndices(indices: string[], client: Client) { for (const index of indices) { @@ -84,7 +84,7 @@ async function deleteUser(esClient: Client, username: string): Promise<{ found: }); } -const updateURL = ({ +function updateURL({ url, user, protocol, @@ -92,7 +92,7 @@ const updateURL = ({ url: string; user?: { username: string; password: string }; protocol?: string; -}): string => { +}): string { const urlObject = new URL(url); if (user) { urlObject.username = user.username; @@ -102,7 +102,7 @@ const updateURL = ({ urlObject.protocol = protocol; } return urlObject.href; -}; +} async function main() { const argv = yargs.help().options({ @@ -247,7 +247,7 @@ async function main() { default: false, }, withNewUser: { - alias: 'nu', + alias: 'wnu', describe: 'If the --fleet flag is enabled, using `--withNewUser=username:password` would add a new user with \ the given username, password and `superuser`, `kibana_system` roles. Adding a new user would also write \ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/test_adapters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/test_adapters.ts index f5d652ef34236..bfbd396851cd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/test_adapters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/test_adapters.ts @@ -35,6 +35,11 @@ const buildResponses = (method: Method, calls: MockCall[]): ResponseCall[] => { status: call.statusCode, body: JSON.parse(call.body), })); + case 'customError': + return calls.map(([call]) => ({ + status: call.statusCode, + body: call.body, + })); default: throw new Error(`Encountered unexpected call to response.${method}`); } 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 b826ed83d34ed..b1737b1efe6a9 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 @@ -3,7 +3,7 @@ exports[`get_signals_template backwards compatibility mappings for version 45 should match snapshot 1`] = ` Object { "_meta": Object { - "aliases_version": 2, + "aliases_version": 3, "version": 45, }, "properties": Object { @@ -533,9 +533,522 @@ Object { exports[`get_signals_template backwards compatibility mappings for version 57 should match snapshot 1`] = ` Object { "_meta": Object { - "aliases_version": 2, + "aliases_version": 3, "version": 57, }, + "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.building_block_type": Object { + "path": "signal.rule.building_block_type", + "type": "alias", + }, + "kibana.alert.depth": Object { + "path": "signal.depth", + "type": "alias", + }, + "kibana.alert.group.id": Object { + "path": "signal.group.id", + "type": "alias", + }, + "kibana.alert.group.index": Object { + "path": "signal.group.index", + "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.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.interval": Object { + "path": "signal.rule.interval", + "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.references": Object { + "path": "signal.rule.references", + "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.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.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.timestamp_override": Object { + "path": "signal.rule.timestamp_override", + "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", + }, + "timestamp_override": Object { + "type": "keyword", + }, + }, + "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", + }, + }, } `; @@ -552,7 +1065,7 @@ Object { }, "mappings": Object { "_meta": Object { - "aliases_version": 2, + "aliases_version": 3, "version": 67, }, "dynamic": false, 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 9bb848b2f7d34..5ff76179eeb03 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 @@ -145,6 +145,10 @@ const addFieldAliasesToIndices = async ({ const indicesByVersion: Record = {}; const versions: Set = new Set(); for (const [indexName, mapping] of Object.entries(indexMappings)) { + // The `version` tells us which set of backwards compatibility mappings to apply: `version` never changes + // and represents what was actually shipped. `aliases_version` tells us if the most up to date backwards + // compatibility mappings have already been applied to the index. `aliases_version` DOES get updated when we apply + // new compatibility mappings like runtime fields and aliases. const version: number = get(mapping.mappings?._meta, 'version') ?? 0; const aliasesVersion: number = get(mapping.mappings?._meta, ALIAS_VERSION_FIELD) ?? 0; // Only attempt to add backwards compatibility mappings to indices whose names start with the alias 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 1f02fcde67184..642236f950308 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 @@ -47,7 +47,7 @@ export const SIGNALS_TEMPLATE_VERSION = 67; 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 = 2; +export const SIGNALS_FIELD_ALIASES_VERSION = 3; /** @constant @@ -154,7 +154,6 @@ export const backwardsCompatibilityMappings = [ }, }, }, - properties, }, }, ]; @@ -171,7 +170,7 @@ export const createBackwardsCompatibilityMapping = (version: number) => { }, }; - return merge({}, ...mappings, meta); + return merge({ properties }, ...mappings, meta); }; export const getRbacRequiredFields = (spaceId: string) => { 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 a5a982a6d78c6..74f777b29ca01 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 @@ -17,7 +17,7 @@ import { } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { readRules } from '../../rules/read_rules'; import { getDuplicates } from './utils'; import { transformValidateBulkError } from './validate'; @@ -92,7 +92,7 @@ export const createRulesBulkRoute = ( }); } - throwHttpError(await mlAuthz.validateRuleType(internalRule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); const finalIndex = internalRule.params.outputIndex; const indexExists = await getIndexExists(esClient.asCurrentUser, finalIndex); if (!isRuleRegistryEnabled && !indexExists) { 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 ef5c7368841db..fe39c5cb9680e 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 @@ -14,7 +14,7 @@ import { import { SetupPlugins } from '../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { readRules } from '../../rules/read_rules'; import { buildSiemResponse } from '../utils'; @@ -79,7 +79,7 @@ export const createRulesRoute = ( request, savedObjectsClient, }); - throwHttpError(await mlAuthz.validateRuleType(internalRule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); const indexExists = await getIndexExists( esClient.asCurrentUser, 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 55c05dd73e33b..58d364cb34b5c 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 @@ -17,7 +17,7 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { transformBulkError, buildSiemResponse } from '../utils'; import { getIdBulkError } from './utils'; import { transformValidateBulkError } from './validate'; @@ -117,7 +117,7 @@ export const patchRulesBulkRoute = ( try { if (type) { // reject an unauthorized "promotion" to ML - throwHttpError(await mlAuthz.validateRuleType(type)); + throwAuthzError(await mlAuthz.validateRuleType(type)); } const existingRule = await readRules({ @@ -128,7 +128,7 @@ export const patchRulesBulkRoute = ( }); if (existingRule?.params.type) { // reject an unauthorized modification of an ML rule - throwHttpError(await mlAuthz.validateRuleType(existingRule?.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(existingRule?.params.type)); } const migratedRule = await legacyMigrate({ 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 0767e86c4e22c..a5bc76cc5ef2e 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 @@ -17,7 +17,7 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { patchRules } from '../../rules/patch_rules'; import { buildSiemResponse } from '../utils'; @@ -118,7 +118,7 @@ export const patchRulesRoute = ( }); if (type) { // reject an unauthorized "promotion" to ML - throwHttpError(await mlAuthz.validateRuleType(type)); + throwAuthzError(await mlAuthz.validateRuleType(type)); } const existingRule = await readRules({ @@ -129,7 +129,7 @@ export const patchRulesRoute = ( }); if (existingRule?.params.type) { // reject an unauthorized modification of an ML rule - throwHttpError(await mlAuthz.validateRuleType(existingRule?.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(existingRule?.params.type)); } const migratedRule = await legacyMigrate({ 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 386b87fefc40c..ecf925bda97e5 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 @@ -51,14 +51,36 @@ describe.each([ it('returns 200 when performing bulk action with all dependencies present', async () => { const response = await server.inject(getBulkActionRequest(), context); expect(response.status).toEqual(200); - expect(response.body).toEqual({ success: true, rules_count: 1 }); + expect(response.body).toEqual({ + success: true, + rules_count: 1, + attributes: { + results: someBulkActionResults(), + summary: { + failed: 0, + succeeded: 1, + total: 1, + }, + }, + }); }); it("returns 200 when provided filter query doesn't match any rules", async () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); const response = await server.inject(getBulkActionRequest(), context); expect(response.status).toEqual(200); - expect(response.body).toEqual({ success: true, rules_count: 0 }); + expect(response.body).toEqual({ + success: true, + rules_count: 0, + attributes: { + results: someBulkActionResults(), + summary: { + failed: 0, + succeeded: 0, + total: 0, + }, + }, + }); }); it('returns 400 when provided filter query matches too many rules', async () => { @@ -93,7 +115,7 @@ describe.each([ errors: [ { message: 'Elastic rule can`t be edited', - status_code: 403, + status_code: 400, rules: [ { id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', @@ -102,7 +124,8 @@ describe.each([ ], }, ], - rules: { + results: someBulkActionResults(), + summary: { failed: 1, succeeded: 0, total: 1, @@ -133,7 +156,8 @@ describe.each([ ], }, ], - rules: { + results: someBulkActionResults(), + summary: { failed: 1, succeeded: 0, total: 1, @@ -165,7 +189,8 @@ describe.each([ ], }, ], - rules: { + results: someBulkActionResults(), + summary: { failed: 1, succeeded: 0, total: 1, @@ -202,7 +227,7 @@ describe.each([ expect(response.status).toEqual(500); expect(response.body).toEqual({ attributes: { - rules: { + summary: { failed: 1, succeeded: 0, total: 1, @@ -220,6 +245,7 @@ describe.each([ ], }, ], + results: someBulkActionResults(), }, message: 'Bulk edit failed', status_code: 500, @@ -252,7 +278,7 @@ describe.each([ expect(response.status).toEqual(500); expect(response.body).toEqual({ attributes: { - rules: { + summary: { failed: 1, succeeded: 0, total: 1, @@ -269,6 +295,7 @@ describe.each([ ], }, ], + results: someBulkActionResults(), }, message: 'Bulk edit failed', status_code: 500, @@ -303,7 +330,7 @@ describe.each([ expect(response.status).toEqual(500); expect(response.body).toEqual({ attributes: { - rules: { + summary: { failed: 3, succeeded: 2, total: 5, @@ -334,6 +361,7 @@ describe.each([ ], }, ], + results: someBulkActionResults(), }, message: 'Bulk edit partially failed', status_code: 500, @@ -369,14 +397,14 @@ describe.each([ expect(response.status).toEqual(500); expect(response.body).toEqual({ attributes: { - rules: { + summary: { failed: 1, succeeded: 1, total: 2, }, errors: [ { - message: 'Can`t fetch a rule', + message: 'Rule not found', status_code: 500, rules: [ { @@ -385,6 +413,7 @@ describe.each([ ], }, ], + results: someBulkActionResults(), }, message: 'Bulk edit partially failed', status_code: 500, @@ -498,6 +527,23 @@ describe.each([ const response = await server.inject(getBulkActionEditRequest(), context); expect(response.status).toEqual(200); - expect(response.body).toEqual({ success: true, rules_count: rulesNumber }); + expect(response.body).toEqual( + expect.objectContaining({ + success: true, + rules_count: rulesNumber, + attributes: { + summary: { failed: 0, succeeded: rulesNumber, total: rulesNumber }, + results: someBulkActionResults(), + }, + }) + ); }); }); + +function someBulkActionResults() { + return { + created: expect.any(Array), + deleted: expect.any(Array), + updated: expect.any(Array), + }; +} 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 89f92448847a2..1e1c894ad097c 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 @@ -5,11 +5,12 @@ * 2.0. */ +import { truncate } from 'lodash'; import moment from 'moment'; -import { transformError } from '@kbn/securitysolution-es-utils'; -import { Logger } from 'src/core/server'; +import { BadRequestError, transformError } from '@kbn/securitysolution-es-utils'; +import { KibanaResponseFactory, Logger } from 'src/core/server'; -import { RuleAlertType as Rule } from '../../rules/types'; +import { RuleAlertType } from '../../rules/types'; import type { RulesClient } from '../../../../../../alerting/server'; @@ -24,9 +25,13 @@ import { SetupPlugins } from '../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { routeLimitedConcurrencyTag } from '../../../../utils/route_limited_concurrency_tag'; -import { initPromisePool } from '../../../../utils/promise_pool'; +import { + initPromisePool, + PromisePoolError, + PromisePoolOutcome, +} from '../../../../utils/promise_pool'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { deleteRules } from '../../rules/delete_rules'; import { duplicateRule } from '../../rules/duplicate_rule'; import { findRules } from '../../rules/find_rules'; @@ -35,28 +40,13 @@ import { patchRules } from '../../rules/patch_rules'; import { applyBulkActionEditToRule } from '../../rules/bulk_action_edit'; import { getExportByObjectIds } from '../../rules/get_export_by_object_ids'; import { buildSiemResponse } from '../utils'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; +import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; const MAX_RULES_TO_PROCESS_TOTAL = 10000; const MAX_ERROR_MESSAGE_LENGTH = 1000; const MAX_ROUTE_CONCURRENCY = 5; -type RuleActionFn = (rule: Rule) => Promise; - -type RuleActionSuccess = undefined; - -type RuleActionResult = RuleActionSuccess | RuleActionError; - -interface RuleActionError { - error: { - message: string; - statusCode: number; - }; - rule: { - id: string; - name: string; - }; -} - interface NormalizedRuleError { message: string; status_code: number; @@ -66,18 +56,25 @@ interface NormalizedRuleError { }>; } -const normalizeErrorResponse = (errors: RuleActionError[]): NormalizedRuleError[] => { +const normalizeErrorResponse = ( + errors: Array | PromisePoolError> +): NormalizedRuleError[] => { const errorsMap = new Map(); - errors.forEach((ruleError) => { - const { message } = ruleError.error; + errors.forEach(({ error, item }) => { + const { message, statusCode } = + error instanceof Error ? transformError(error) : { message: String(error), statusCode: 500 }; + // The promise pool item is either a rule ID string or a rule object. We have + // string IDs when we fail to fetch rules. Rule objects come from other + // situations when we found a rule but failed somewhere else. + const rule = typeof item === 'string' ? { id: item } : { id: item.id, name: item.name }; + if (errorsMap.has(message)) { - errorsMap.get(message).rules.push(ruleError.rule); + errorsMap.get(message).rules.push(rule); } else { - const { error, rule } = ruleError; errorsMap.set(message, { - message: error.message, - status_code: error.statusCode, + message: truncate(message, { length: MAX_ERROR_MESSAGE_LENGTH }), + status_code: statusCode, rules: [rule], }); } @@ -86,93 +83,58 @@ const normalizeErrorResponse = (errors: RuleActionError[]): NormalizedRuleError[ return Array.from(errorsMap, ([_, normalizedError]) => normalizedError); }; -const getErrorResponseBody = (errors: RuleActionError[], rulesCount: number) => { - const errorsCount = errors.length; - return { - message: errorsCount === rulesCount ? 'Bulk edit failed' : 'Bulk edit partially failed', - status_code: 500, - attributes: { - errors: normalizeErrorResponse(errors).map(({ message, ...error }) => ({ - ...error, - message: - message.length > MAX_ERROR_MESSAGE_LENGTH - ? `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH - 3)}...` - : message, - })), - rules: { - total: rulesCount, - failed: errorsCount, - succeeded: rulesCount - errorsCount, - }, - }, +const buildBulkResponse = ( + response: KibanaResponseFactory, + fetchRulesOutcome: PromisePoolOutcome, + bulkActionOutcome: PromisePoolOutcome +) => { + const errors = [...fetchRulesOutcome.errors, ...bulkActionOutcome.errors]; + const summary = { + failed: errors.length, + succeeded: bulkActionOutcome.results.length, + total: bulkActionOutcome.results.length + errors.length, }; -}; -const executeActionAndHandleErrors = async ( - rule: Rule, - action: RuleActionFn -): Promise => { - try { - await action(rule); - } catch (err) { - const { message, statusCode } = transformError(err); - return { - error: { message, statusCode }, - rule: { id: rule.id, name: rule.name }, - }; - } -}; - -const executeBulkAction = async (rules: Rule[], action: RuleActionFn, abortSignal: AbortSignal) => - initPromisePool({ - concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, - items: rules, - executor: async (rule) => executeActionAndHandleErrors(rule, action), - abortSignal, - }); - -const getRulesByIds = async ({ - ids, - rulesClient, - isRuleRegistryEnabled, - abortSignal, -}: { - ids: string[]; - rulesClient: RulesClient; - isRuleRegistryEnabled: boolean; - abortSignal: AbortSignal; -}) => { - const readRulesExecutor = async (id: string) => { - try { - const rule = await readRules({ id, rulesClient, isRuleRegistryEnabled, ruleId: undefined }); - if (rule == null) { - throw Error('Can`t fetch a rule'); - } - return { rule }; - } catch (err) { - const { message, statusCode } = transformError(err); - return { - error: { message, statusCode }, - rule: { id }, - }; - } + const results = { + updated: bulkActionOutcome.results + .filter(({ item, result }) => item.id === result?.id) + .map(({ result }) => result && internalRuleToAPIResponse(result)), + created: bulkActionOutcome.results + .filter(({ item, result }) => result != null && result.id !== item.id) + .map(({ result }) => result && internalRuleToAPIResponse(result)), + deleted: bulkActionOutcome.results + .filter(({ result }) => result == null) + .map(({ item }) => internalRuleToAPIResponse(item)), }; - const { results } = await initPromisePool({ - concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, - items: ids, - executor: readRulesExecutor, - abortSignal, - }); + if (errors.length > 0) { + return response.custom({ + headers: { 'content-type': 'application/json' }, + body: Buffer.from( + JSON.stringify({ + message: summary.succeeded > 0 ? 'Bulk edit partially failed' : 'Bulk edit failed', + status_code: 500, + attributes: { + errors: normalizeErrorResponse(errors), + results, + summary, + }, + }) + ), + statusCode: 500, + }); + } - return { - total: ids.length, - rules: results.filter((rule) => rule.error === undefined).map(({ rule }) => rule) as Rule[], - fetchErrors: results.filter((rule): rule is RuleActionError => rule.error !== undefined), - }; + return response.ok({ + body: { + success: true, + rules_count: summary.total, + attributes: { results, summary }, + }, + }); }; -const fetchRules = async ({ +const fetchRulesByQueryOrIds = async ({ query, ids, rulesClient, @@ -184,12 +146,18 @@ const fetchRules = async ({ rulesClient: RulesClient; isRuleRegistryEnabled: boolean; abortSignal: AbortSignal; -}) => { +}): Promise> => { if (ids) { - return getRulesByIds({ - ids, - rulesClient, - isRuleRegistryEnabled, + return initPromisePool({ + concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, + items: ids, + executor: async (id: string) => { + const rule = await readRules({ id, rulesClient, isRuleRegistryEnabled, ruleId: undefined }); + if (rule == null) { + throw Error('Rule not found'); + } + return rule; + }, abortSignal, }); } @@ -205,10 +173,15 @@ const fetchRules = async ({ fields: undefined, }); + if (total > MAX_RULES_TO_PROCESS_TOTAL) { + throw new BadRequestError( + `More than ${MAX_RULES_TO_PROCESS_TOTAL} rules matched the filter query. Try to narrow it down.` + ); + } + return { - rules: data, - total, - fetchErrors: [] as RuleActionError[], + results: data.map((rule) => ({ item: rule.id, result: rule })), + errors: [], }; }; @@ -268,7 +241,7 @@ export const performBulkActionRoute = ( savedObjectsClient, }); - const { rules, total, fetchErrors } = await fetchRules({ + const fetchRulesOutcome = await fetchRulesByQueryOrIds({ isRuleRegistryEnabled, rulesClient, query: body.query, @@ -276,68 +249,77 @@ export const performBulkActionRoute = ( abortSignal: abortController.signal, }); - if (total > MAX_RULES_TO_PROCESS_TOTAL) { - return siemResponse.error({ - body: `More than ${MAX_RULES_TO_PROCESS_TOTAL} rules matched the filter query. Try to narrow it down.`, - statusCode: 400, - }); - } + const rules = fetchRulesOutcome.results.map(({ result }) => result); + let bulkActionOutcome: PromisePoolOutcome; - let processingResponse: { - results: RuleActionResult[]; - } = { - results: [], - }; switch (body.action) { case BulkAction.enable: - processingResponse = await executeBulkAction( - rules, - async (rule) => { + bulkActionOutcome = await initPromisePool({ + concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, + items: rules, + executor: async (rule) => { if (!rule.enabled) { - throwHttpError(await mlAuthz.validateRuleType(rule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(rule.params.type)); await rulesClient.enable({ id: rule.id }); } + + return { + ...rule, + enabled: true, + }; }, - abortController.signal - ); + abortSignal: abortController.signal, + }); break; case BulkAction.disable: - processingResponse = await executeBulkAction( - rules, - async (rule) => { + bulkActionOutcome = await initPromisePool({ + concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, + items: rules, + executor: async (rule) => { if (rule.enabled) { - throwHttpError(await mlAuthz.validateRuleType(rule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(rule.params.type)); await rulesClient.disable({ id: rule.id }); } + + return { + ...rule, + enabled: false, + }; }, - abortController.signal - ); + abortSignal: abortController.signal, + }); break; case BulkAction.delete: - processingResponse = await executeBulkAction( - rules, - async (rule) => { + bulkActionOutcome = await initPromisePool({ + concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, + items: rules, + executor: async (rule) => { await deleteRules({ ruleId: rule.id, rulesClient, ruleExecutionLog, }); + + return null; }, - abortController.signal - ); + abortSignal: abortController.signal, + }); break; case BulkAction.duplicate: - processingResponse = await executeBulkAction( - rules, - async (rule) => { - throwHttpError(await mlAuthz.validateRuleType(rule.params.type)); + bulkActionOutcome = await initPromisePool({ + concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, + items: rules, + executor: async (rule) => { + throwAuthzError(await mlAuthz.validateRuleType(rule.params.type)); - await rulesClient.create({ + const createdRule = await rulesClient.create({ data: duplicateRule(rule, isRuleRegistryEnabled), }); + + return createdRule; }, - abortController.signal - ); + abortSignal: abortController.signal, + }); break; case BulkAction.export: const exported = await getExportByObjectIds( @@ -359,15 +341,15 @@ export const performBulkActionRoute = ( body: responseBody, }); case BulkAction.edit: - processingResponse = await executeBulkAction( - rules, - async (rule) => { - throwHttpError({ - valid: !rule.params.immutable, - message: 'Elastic rule can`t be edited', - }); + bulkActionOutcome = await initPromisePool({ + concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, + items: rules, + executor: async (rule) => { + if (rule.params.immutable) { + throw new BadRequestError('Elastic rule can`t be edited'); + } - throwHttpError(await mlAuthz.validateRuleType(rule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(rule.params.type)); const editedRule = body[BulkAction.edit].reduce( (acc, action) => applyBulkActionEditToRule(acc, action), @@ -385,40 +367,19 @@ export const performBulkActionRoute = ( timelineTitle, timelineId, }); + + return editedRule; }, - abortController.signal - ); + abortSignal: abortController.signal, + }); + break; } if (abortController.signal.aborted === true) { - throw Error('Bulk action was aborted'); + throw new AbortError('Bulk action was aborted'); } - const errors = [ - ...fetchErrors, - ...processingResponse.results.filter( - (resp): resp is RuleActionError => resp?.error !== undefined - ), - ]; - - if (errors.length > 0) { - const responseBody = getErrorResponseBody(errors, total); - - return response.custom({ - headers: { - 'content-type': 'application/json', - }, - body: Buffer.from(JSON.stringify(responseBody)), - statusCode: 500, - }); - } - - return response.ok({ - body: { - success: true, - rules_count: total, - }, - }); + return buildBulkResponse(response, fetchRulesOutcome, bulkActionOutcome); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts index e4626aaa93216..11396864d802d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts @@ -14,7 +14,7 @@ import { RuleParams } from '../../schemas/rule_schemas'; import { createPreviewRuleExecutionLogger } from '../../signals/preview/preview_rule_execution_logger'; import { parseInterval } from '../../signals/utils'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { SetupPlugins } from '../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../types'; @@ -106,7 +106,7 @@ export const previewRulesRoute = async ( request, savedObjectsClient, }); - throwHttpError(await mlAuthz.validateRuleType(internalRule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); await context.lists?.getExceptionListClient().createEndpointList(); const spaceId = siemClient.getSpaceId(); @@ -145,8 +145,15 @@ export const previewRulesRoute = async ( id: string ) => Pick< Alert, - 'getState' | 'replaceState' | 'scheduleActions' | 'scheduleActionsWithSubGroup' + | 'getState' + | 'replaceState' + | 'scheduleActions' + | 'scheduleActionsWithSubGroup' + | 'setContext' + | 'getContext' + | 'hasContext' >; + done: () => { getRecoveredAlerts: () => [] }; } ) => { let statePreview = runState as TState; @@ -228,7 +235,7 @@ export const previewRulesRoute = async ( queryAlertType.name, previewRuleParams, () => true, - { create: alertInstanceFactoryStub } + { create: alertInstanceFactoryStub, done: () => ({ getRecoveredAlerts: () => [] }) } ); break; case 'threshold': @@ -241,7 +248,7 @@ export const previewRulesRoute = async ( thresholdAlertType.name, previewRuleParams, () => true, - { create: alertInstanceFactoryStub } + { create: alertInstanceFactoryStub, done: () => ({ getRecoveredAlerts: () => [] }) } ); break; case 'threat_match': @@ -254,7 +261,7 @@ export const previewRulesRoute = async ( threatMatchAlertType.name, previewRuleParams, () => true, - { create: alertInstanceFactoryStub } + { create: alertInstanceFactoryStub, done: () => ({ getRecoveredAlerts: () => [] }) } ); break; case 'eql': @@ -265,7 +272,7 @@ export const previewRulesRoute = async ( eqlAlertType.name, previewRuleParams, () => true, - { create: alertInstanceFactoryStub } + { create: alertInstanceFactoryStub, done: () => ({ getRecoveredAlerts: () => [] }) } ); break; case 'machine_learning': @@ -276,7 +283,7 @@ export const previewRulesRoute = async ( mlAlertType.name, previewRuleParams, () => true, - { create: alertInstanceFactoryStub } + { create: alertInstanceFactoryStub, done: () => ({ getRecoveredAlerts: () => [] }) } ); break; } 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 f560230cd531b..d1df5713914df 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 @@ -14,7 +14,7 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { getIdBulkError } from './utils'; import { transformValidateBulkError } from './validate'; import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '../utils'; @@ -65,7 +65,7 @@ export const updateRulesBulkRoute = ( }); } - throwHttpError(await mlAuthz.validateRuleType(payloadRule.type)); + throwAuthzError(await mlAuthz.validateRuleType(payloadRule.type)); const existingRule = await readRules({ isRuleRegistryEnabled, 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 4f63e49cd1690..8ac90748d9217 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 @@ -12,7 +12,7 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwHttpError } from '../../../machine_learning/validation'; +import { throwAuthzError } from '../../../machine_learning/validation'; import { buildSiemResponse } from '../utils'; import { getIdError } from './utils'; @@ -54,7 +54,7 @@ export const updateRulesRoute = ( request, savedObjectsClient, }); - throwHttpError(await mlAuthz.validateRuleType(request.body.type)); + throwAuthzError(await mlAuthz.validateRuleType(request.body.type)); const existingRule = await readRules({ isRuleRegistryEnabled, 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 db04d8eded869..0ca665bb10584 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 @@ -10,7 +10,6 @@ import { createPromiseFromStreams } from '@kbn/utils'; import { Action, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { - transformAlertToRule, getIdError, transformFindAlerts, transform, @@ -39,6 +38,7 @@ import { getQueryRuleParams, getThreatRuleParams, } from '../../schemas/rule_schemas.mock'; +import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; import { requestContextMock } from '../__mocks__'; // eslint-disable-next-line no-restricted-imports @@ -70,17 +70,17 @@ describe.each([ ])('utils - %s', (_, isRuleRegistryEnabled) => { const { clients } = requestContextMock.createTools(); - describe('transformAlertToRule', () => { + describe('internalRuleToAPIResponse', () => { test('should work with a full data set', () => { const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); - const rule = transformAlertToRule(fullRule); + const rule = internalRuleToAPIResponse(fullRule); expect(rule).toEqual(getOutputRuleAlertForRest()); }); test('should omit note if note is undefined', () => { const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.params.note = undefined; - const rule = transformAlertToRule(fullRule); + const rule = internalRuleToAPIResponse(fullRule); const { note, ...expectedWithoutNote } = getOutputRuleAlertForRest(); expect(rule).toEqual(expectedWithoutNote); }); @@ -88,7 +88,7 @@ describe.each([ test('should return enabled is equal to false', () => { const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.enabled = false; - const ruleWithEnabledFalse = transformAlertToRule(fullRule); + const ruleWithEnabledFalse = internalRuleToAPIResponse(fullRule); const expected = getOutputRuleAlertForRest(); expected.enabled = false; expect(ruleWithEnabledFalse).toEqual(expected); @@ -97,7 +97,7 @@ describe.each([ test('should return immutable is equal to false', () => { const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.params.immutable = false; - const ruleWithEnabledFalse = transformAlertToRule(fullRule); + const ruleWithEnabledFalse = internalRuleToAPIResponse(fullRule); const expected = getOutputRuleAlertForRest(); expect(ruleWithEnabledFalse).toEqual(expected); }); @@ -105,7 +105,7 @@ describe.each([ test('should work with tags but filter out any internal tags', () => { const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); fullRule.tags = ['tag 1', 'tag 2', `${INTERNAL_IDENTIFIER}_some_other_value`]; - const rule = transformAlertToRule(fullRule); + const rule = internalRuleToAPIResponse(fullRule); const expected = getOutputRuleAlertForRest(); expected.tags = ['tag 1', 'tag 2']; expect(rule).toEqual(expected); @@ -117,7 +117,7 @@ describe.each([ mlRule.params.machineLearningJobId = ['some_job_id']; mlRule.params.type = 'machine_learning'; - const rule = transformAlertToRule(mlRule); + const rule = internalRuleToAPIResponse(mlRule); expect(rule).toEqual( expect.objectContaining({ anomaly_threshold: 55, @@ -165,7 +165,7 @@ describe.each([ threatRule.params.threatMapping = threatMapping; threatRule.params.threatQuery = '*:*'; - const rule = transformAlertToRule(threatRule); + const rule = internalRuleToAPIResponse(threatRule); expect(rule).toEqual( expect.objectContaining({ threat_index: ['index-123'], @@ -183,7 +183,7 @@ describe.each([ lists: [], ...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), }; - const rule = transformAlertToRule(result); + const rule = internalRuleToAPIResponse(result); expect(rule).toEqual( expect.not.objectContaining({ lists: [], @@ -198,7 +198,7 @@ describe.each([ exceptions_list: [], ...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), }; - const rule = transformAlertToRule(result); + const rule = internalRuleToAPIResponse(result); expect(rule).toEqual( expect.not.objectContaining({ exceptions_list: [], 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 7e9f03cf828a8..ff2eab1f799b9 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 @@ -22,7 +22,6 @@ import { RuleAlertType, isAlertType } from '../../rules/types'; import { createBulkErrorObject, BulkError, OutputError } from '../utils'; import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; import { RuleParams } from '../../schemas/rule_schemas'; -import { SanitizedAlert } from '../../../../../../alerting/common'; // eslint-disable-next-line no-restricted-imports import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; import { RuleExecutionSummariesByRuleId } from '../../rule_execution_log'; @@ -93,21 +92,11 @@ export const transformTags = (tags: string[]): string[] => { return tags.filter((tag) => !tag.startsWith(INTERNAL_IDENTIFIER)); }; -// Transforms the data but will remove any null or undefined it encounters and not include -// those on the export -export const transformAlertToRule = ( - rule: SanitizedAlert, - ruleExecutionSummary?: RuleExecutionSummary | null, - legacyRuleActions?: LegacyRulesActionsSavedObject | null -): Partial => { - return internalRuleToAPIResponse(rule, ruleExecutionSummary, legacyRuleActions); -}; - export const transformAlertsToRules = ( rules: RuleAlertType[], legacyRuleActions: Record ): Array> => { - return rules.map((rule) => transformAlertToRule(rule, null, legacyRuleActions[rule.id])); + return rules.map((rule) => internalRuleToAPIResponse(rule, null, legacyRuleActions[rule.id])); }; export const transformFindAlerts = ( @@ -138,7 +127,7 @@ export const transform = ( legacyRuleActions?: LegacyRulesActionsSavedObject | null ): Partial | null => { if (isAlertType(isRuleRegistryEnabled ?? false, rule)) { - return transformAlertToRule(rule, ruleExecutionSummary, legacyRuleActions); + return internalRuleToAPIResponse(rule, ruleExecutionSummary, legacyRuleActions); } return null; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts index 121e54c768856..fd4c751d7fb84 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts @@ -21,7 +21,7 @@ import { readRules } from '../../../rules/read_rules'; import { patchRules } from '../../../rules/patch_rules'; import { ImportRulesSchemaDecoded } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { MlAuthz } from '../../../../machine_learning/authz'; -import { throwHttpError } from '../../../../machine_learning/validation'; +import { throwAuthzError } from '../../../../machine_learning/validation'; import { RulesClient } from '../../../../../../../../plugins/alerting/server'; import { ExceptionListClient } from '../../../../../../../../plugins/lists/server'; import { checkRuleExceptionReferences } from './check_rule_exception_references'; @@ -165,7 +165,7 @@ export const importRules = async ({ const language = !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined; // TODO: Fix these either with an is conversion or by better typing them within io-ts const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; - throwHttpError(await mlAuthz.validateRuleType(type)); + throwAuthzError(await mlAuthz.validateRuleType(type)); const rule = await readRules({ isRuleRegistryEnabled, rulesClient, 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 54a1b3521f2b1..e743e26e8da3f 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 @@ -19,10 +19,11 @@ import { import { PartialAlert } from '../../../../../../alerting/server'; import { isAlertType } from '../../rules/types'; import { createBulkErrorObject, BulkError } from '../utils'; -import { transform, transformAlertToRule } from './utils'; +import { transform } from './utils'; import { RuleParams } from '../../schemas/rule_schemas'; // eslint-disable-next-line no-restricted-imports import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; +import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; export const transformValidate = ( rule: PartialAlert, @@ -69,7 +70,7 @@ export const transformValidateBulkError = ( isRuleRegistryEnabled?: boolean ): RulesSchema | BulkError => { if (isAlertType(isRuleRegistryEnabled ?? false, rule)) { - const transformed = transformAlertToRule(rule, ruleExecutionSummary); + const transformed = internalRuleToAPIResponse(rule, ruleExecutionSummary); const [validated, errors] = validateNonExact(transformed, rulesSchema); if (errors != null || validated == null) { return createBulkErrorObject({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts index 72cfada909cdc..feaaa7f3e6c08 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts @@ -62,7 +62,7 @@ export const legacyGetBulkRuleActionsSavedObject = async ({ throw new AggregateError(errors, 'Error fetching rule actions'); } - const savedObjects = results.flatMap((result) => result.saved_objects); + const savedObjects = results.flatMap(({ result }) => result.saved_objects); return savedObjects.reduce( (acc: { [key: string]: LegacyRulesActionsSavedObject }, savedObject) => { const ruleAlertId = savedObject.references.find((reference) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/client_for_routes/client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/client_for_routes/client.ts index 05647089bbd45..a4c528d941ba4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/client_for_routes/client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/client_for_routes/client.ts @@ -30,7 +30,7 @@ export const createClientForRoutes = ( return { /** * Get the current rule execution summary for each of the given rule IDs. - * This method splits work into chunks so not to owerwhelm Elasticsearch + * This method splits work into chunks so not to overwhelm Elasticsearch * when fetching statuses for a big number of rules. * * @param ruleIds A list of rule IDs (`rule.id`) to fetch summaries for @@ -71,7 +71,7 @@ export const createClientForRoutes = ( } // Merge all rule statuses into a single dict - return Object.assign({}, ...results); + return Object.assign({}, ...results.map(({ result }) => result)); } catch (e) { const ruleIdsString = `[${truncateList(ruleIds).join(', ')}]`; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts index 3d96e3bb77907..3d390cac6b91f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts @@ -76,7 +76,10 @@ export const createRuleTypeMocks = ( search: elasticsearchServiceMock.createScopedClusterClient(), savedObjectsClient: mockSavedObjectsClient, scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(), - alertFactory: { create: jest.fn(() => ({ scheduleActions })) }, + alertFactory: { + create: jest.fn(() => ({ scheduleActions })), + done: jest.fn().mockResolvedValue({}), + }, findAlerts: jest.fn(), // TODO: does this stay? alertWithPersistence: jest.fn(), logger: loggerMock, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts index 4dd2903994085..180494f9209dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts @@ -71,8 +71,9 @@ export const buildAlertGroupFromSequence = ( // we can build the signal that links the building blocks together // and also insert the group id (which is also the "shell" signal _id) in each building block const doc = buildAlertRoot(wrappedBuildingBlocks, completeRule, spaceId, buildReasonMessage); + const sequenceAlertId = generateAlertId(doc); const sequenceAlert = { - _id: generateAlertId(doc), + _id: sequenceAlertId, _index: '', _source: doc, }; @@ -82,6 +83,8 @@ export const buildAlertGroupFromSequence = ( block._source[ALERT_GROUP_INDEX] = i; }); + sequenceAlert._source[ALERT_UUID] = sequenceAlertId; + return [...wrappedBuildingBlocks, sequenceAlert]; }; 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 7381e2c3a1f02..1181995e0ae4a 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 @@ -16,13 +16,13 @@ import { RulesClient, AlertServices } from '../../../../../alerting/server'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from '../rules/types'; -import { transformAlertToRule } from '../routes/rules/utils'; import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { findRules } from './find_rules'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; // eslint-disable-next-line no-restricted-imports import { legacyGetBulkRuleActionsSavedObject } from '../rule_actions/legacy_get_bulk_rule_actions_saved_object'; +import { internalRuleToAPIResponse } from '../schemas/rule_converters'; interface ExportSuccessRule { statusCode: 200; @@ -127,7 +127,7 @@ export const getRulesFromObjects = async ( ) { return { statusCode: 200, - rule: transformAlertToRule(matchingRule, null, legacyActions[matchingRule.id]), + rule: internalRuleToAPIResponse(matchingRule, null, legacyActions[matchingRule.id]), }; } else { return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json index 7debf76f371c5..41b9c053ca41a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json @@ -4,7 +4,7 @@ ], "description": "Identifies the creation of an AWS log trail that specifies the settings for delivery of log data.", "false_positives": [ - "Trail creations may be made by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Trail creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Trail creations may be made by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Trail creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json index a24c533e2c272..cfa1ed57a34e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json @@ -4,7 +4,7 @@ ], "description": "Identifies the creation of a subscription in Google Cloud Platform (GCP). In GCP, the publisher-subscriber relationship (Pub/Sub) is an asynchronous messaging service that decouples event-producing and event-processing services. A subscription is a named resource representing the stream of messages to be delivered to the subscribing application.", "false_positives": [ - "Subscription creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Subscription creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Subscription creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Subscription creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json index af20bdf46e42a..43f69c10740a5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json @@ -4,7 +4,7 @@ ], "description": "Identifies the creation of a topic in Google Cloud Platform (GCP). In GCP, the publisher-subscriber relationship (Pub/Sub) is an asynchronous messaging service that decouples event-producing and event-processing services. A topic is used to forward messages from publishers to subscribers.", "false_positives": [ - "Topic creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Topic creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Topic creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Topic creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json index 1a9ccc9c70696..30c63674ef284 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json @@ -4,9 +4,9 @@ "Gary Blackwell", "Austin Songer" ], - "description": "Identifies when a new Inbox rule is created in Microsoft 365. Inbox rules process messages in the Inbox based on conditions and take actions, such as moving a message to a specified folder or deleting a message. Adequate permissions are required on the mailbox to create an Inbox rule.", + "description": "Identifies when a new Inbox forwarding rule is created in Microsoft 365. Inbox rules process messages in the Inbox based on conditions and take actions. In this case, the rules will forward the emails to a defined address. Attackers can abuse Inbox Rules to intercept and exfiltrate email data while not requiring organization-wide configuration changes nor privileges to set those.", "false_positives": [ - "An inbox rule may be created by a system or network administrator. Verify that the configuration change was expected. Exceptions can be added to this rule to filter expected behavior." + "Users and Administrators can create inbox rules for legitimate purposes. Verify if it complies with the company policy and done with the user's consent. Exceptions can be added to this rule to filter expected behavior." ], "from": "now-30m", "index": [ @@ -15,17 +15,18 @@ ], "language": "kuery", "license": "Elastic License v2", - "name": "Microsoft 365 New Inbox Rule Created", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-InboxRule\" and event.outcome:success\n", + "name": "Microsoft 365 Inbox Forwarding Rule Created", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:Exchange and\nevent.category:web and event.action:\"New-InboxRule\" and\n (\n o365audit.Parameters.ForwardTo:* or\n o365audit.Parameters.ForwardAsAttachmentTo:* or\n o365audit.Parameters.RedirectTo:*\n ) \n and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/responding-to-a-compromised-email-account?view=o365-worldwide", "https://docs.microsoft.com/en-us/powershell/module/exchange/new-inboxrule?view=exchange-ps", - "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/detect-and-remediate-outlook-rules-forms-attack?view=o365-worldwide" + "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/detect-and-remediate-outlook-rules-forms-attack?view=o365-worldwide", + "https://raw.githubusercontent.com/PwC-IR/Business-Email-Compromise-Guide/main/Extractor%20Cheat%20Sheet.pdf" ], - "risk_score": 21, + "risk_score": 47, "rule_id": "ec8efb0c-604d-42fa-ac46-ed1cfbc38f78", - "severity": "low", + "severity": "medium", "tags": [ "Elastic", "Cloud", @@ -60,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json index 741c07504d920..491ba75865885 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Suspicious Script with Audio Capture Capabilities", - "note": "## Triage and analysis.\n\n### Investigating PowerShell Suspicious Script with Audio Capture Capabilities\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can use PowerShell to interact with the Windows API and capture audio from input devices connected to the\ncomputer.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree)\n- Inspect any file or network events from the suspicious powershell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis.\n\n### Investigating PowerShell Suspicious Script with Audio Capture Capabilities\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can use PowerShell to interact with the Windows API and capture audio from input devices connected to the\ncomputer.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n Get-MicrophoneAudio or (waveInGetNumDevs and mciSendStringA)\n )\n", "references": [ "https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Get-MicrophoneAudio.ps1" @@ -67,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json index f128a2cac13cc..8cac619d6c7dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Detects the use of Win32 API Functions that can be used to capture user Keystrokes in PowerShell Scripts. Attackers use this technique to capture user input, looking for credentials and/or other valuable data.", + "description": "Detects the use of Win32 API Functions that can be used to capture user keystrokes in PowerShell scripts. Attackers use this technique to capture user input, looking for credentials and/or other valuable data.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Keylogging Script", - "note": "## Triage and analysis.\n\n### Investigating PowerShell Keylogging Script\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can abuse PowerShell capabilities to capture user Keystrokes with the goal of stealing credentials and other\nvaluable information as Credit Card data and confidential conversations.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree)\n- Inspect any file or network events from the suspicious powershell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis.\n\n### Investigating PowerShell Keylogging Script\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can abuse PowerShell capabilities to capture user keystrokes with the goal of stealing credentials and other\nvaluable information as credit card data and confidential conversations.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n ( \n powershell.file.script_block_text : (GetAsyncKeyState or NtUserGetAsyncKeyState or GetKeyboardState or Get-Keystrokes) or \n powershell.file.script_block_text : ((SetWindowsHookA or SetWindowsHookW or SetWindowsHookEx or SetWindowsHookExA or NtUserSetWindowsHookEx) and (GetForegroundWindow or GetWindowTextA or GetWindowTextW or WM_KEYBOARD_LL))\n )\n", "references": [ "https://github.com/EmpireProject/Empire/blob/master/data/module_source/collection/Get-Keystrokes.ps1", @@ -75,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json index 916939dc652a6..afab1df677e3d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Detects PowerShell Scripts that can take screenshots, which is a common feature in post-exploitation kits and RATs (Remote Access Tools).", + "description": "Detects PowerShell scripts that can take screenshots, which is a common feature in post-exploitation kits and remote access tools (RATs).", "from": "now-9m", "index": [ "winlogbeat-*", @@ -66,5 +66,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json index 50deb969b57a6..665049d1d9b13 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json @@ -5,9 +5,7 @@ "description": "Adversaries may implement command and control communications that use common web services in order to hide their activity. This attack technique is typically targeted to an organization and uses web services common to the victim network which allows the adversary to blend into legitimate traffic. activity. These popular services are typically targeted since they have most likely been used before a compromise and allow adversaries to blend in the network.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -69,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json index d0039ab4f02d4..f86a96c81460c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Command and Control via Internet Explorer", - "query": "sequence by host.id, user.id with maxspan = 5s\n [library where dll.name : \"IEProxy.dll\" and process.name : (\"rundll32.exe\", \"regsvr32.exe\")]\n [process where event.type == \"start\" and process.parent.name : \"iexplore.exe\" and process.parent.args : \"-Embedding\"]\n /* IE started via COM in normal conditions makes few connections, mainly to Microsoft and OCSP related domains, add FPs here */\n [network where network.protocol == \"dns\" and process.name : \"iexplore.exe\" and\n not dns.question.name :\n (\n \"*.microsoft.com\",\n \"*.digicert.com\",\n \"*.msocsp.com\",\n \"*.windowsupdate.com\",\n \"*.bing.com\",\n \"*.identrust.com\",\n \"*.sharepoint.com\",\n \"*.office365.com\",\n \"*.office.com\"\n )\n ]\n", + "query": "sequence by host.id, user.name with maxspan = 5s\n [library where dll.name : \"IEProxy.dll\" and process.name : (\"rundll32.exe\", \"regsvr32.exe\")]\n [process where event.type == \"start\" and process.parent.name : \"iexplore.exe\" and process.parent.args : \"-Embedding\"]\n /* IE started via COM in normal conditions makes few connections, mainly to Microsoft and OCSP related domains, add FPs here */\n [network where network.protocol == \"dns\" and process.name : \"iexplore.exe\" and\n not dns.question.name :\n (\n \"*.microsoft.com\",\n \"*.digicert.com\",\n \"*.msocsp.com\",\n \"*.windowsupdate.com\",\n \"*.bing.com\",\n \"*.identrust.com\",\n \"*.sharepoint.com\",\n \"*.office365.com\",\n \"*.office.com\"\n )\n ] /* with runs=5 */\n", "risk_score": 47, "rule_id": "acd611f3-2b93-47b3-a0a3-7723bcc46f6d", "severity": "medium", @@ -66,5 +66,5 @@ } ], "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json index 65612e6c28f20..91ca5d8d74166 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Port Forwarding Rule Addition", - "query": "registry where registry.path : \"HKLM\\\\SYSTEM\\\\ControlSet*\\\\Services\\\\PortProxy\\\\v4tov4\\\\*\"\n", + "query": "registry where registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\PortProxy\\\\v4tov4\\\\*\"\n", "references": [ "https://www.fireeye.com/blog/threat-research/2019/01/bypassing-network-restrictions-through-rdp-tunneling.html" ], @@ -45,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json new file mode 100644 index 0000000000000..ee85eacf12b75 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json @@ -0,0 +1,62 @@ +{ + "author": [ + "Elastic" + ], + "description": "This rule identifies when a User Account starts the Active Directory Replication Process. Attackers can use the DCSync technique to get credential information of individual accounts or the entire domain, thus compromising the entire domain.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-system.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential Credential Access via DCSync", + "note": "## Triage and analysis.\n\n### Investigating Active Directory Replication From User Account\n\nActive Directory replication is the process by which the changes that originate on one domain controller are\nautomatically transferred to other domain controllers that store the same data. \n\nActive Directory data takes the form of objects that have properties, or attributes. Each object is an instance\nof an object class, and object classes and their respective attributes are defined in the Active Directory schema.\nThe values of the attributes define the object, and a change to a value of an attribute must be transferred from\nthe domain controller on which it occurs to every other domain controller that stores a replica of that object.\n\nAdversaries can use the DCSync technique that uses Windows Domain Controller's API to simulate the replication process\nfrom a remote domain controller, compromising major credential material such as the Kerberos krbtgt keys used\nlegitimately for tickets creation, but also tickets forging by attackers. This attack requires some extended privileges\nto succeed (DS-Replication-Get-Changes and DS-Replication-Get-Changes-All), which are granted by default to members of\nthe Administrators, Domain Admins, Enterprise Admins, and Domain Controllers groups. Privileged accounts can be abused\nto grant controlled objects the right to DCsync/Replicate.\n\nMore details can be found on [Threat Hunter Playbook](https://threathunterplaybook.com/library/windows/active_directory_replication.html?highlight=dcsync#directory-replication-services-auditing).\nand [The Hacker Recipes](https://www.thehacker.recipes/ad/movement/credentials/dumping/dcsync)\n\nThis rule will monitor for Event ID 4662 (Operation was performed on an Active Directory object) and identify events that use the access\nmask 0x100 (Control Access) and properties that contain at least one of the following or their equivalent Schema-Id-GUID\n(DS-Replication-Get-Changes, DS-Replication-Get-Changes-All, DS-Replication-Get-Changes-In-Filtered-Set). It also filters out events that\nuse computer accounts and also Azure AD Connect MSOL accounts (more details [here](https://techcommunity.microsoft.com/t5/microsoft-defender-for-identity/ad-connect-msol-user-suspected-dcsync-attack/m-p/788028)).\n\n#### Possible investigation steps:\n\n- Identify the account that performed the action\n- Confirm whether the account owner is aware of the operation\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Correlate security events 4662 and 4624 (Logon Type 3) by their Logon ID (`winlog.logon.id`) on the Domain Controller (DC) that received\nthe replication request. This will tell you where the AD replication request came from, and if it came from another DC or not.\n- Investigate which credentials were compromised (e.g. All accounts were replicated or a specific account).\n\n### False Positive Analysis\n\n- This activity should not happen legitimately. Any potential B-TP (Benign True Positive) should be mapped and monitored by the security\nteam as replication should be done by Domain Controllers only. Any account that performs this activity can put the domain at risk for not\nhaving the same security standards (Long, complex, random passwords that change frequently) as computer accounts, exposing it to credential\ncracking attacks (Kerberoasting, brute force, etc.).\n\n### Response and Remediation\n\n- Initiate the incident response process based on the outcome of the triage\n- In case of specific credentials were compromised:\n - Reset the password for the accounts\n- In case of the entire domain or the `krbtgt` user were compromised:\n - Activate your incident response plan for total Active Directory compromise which should include, but not be limited to, a password\n reset (twice) of the `krbtgt` user.\n\n## Config\n\nThe 'Audit Directory Service Access' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Access (Success,Failure)\n```\n", + "query": "any where event.action == \"Directory Service Access\" and\n event.code == \"4662\" and winlog.event_data.Properties : (\n\n /* Control Access Rights/Permissions Symbol */\n\n \"*DS-Replication-Get-Changes*\",\n \"*DS-Replication-Get-Changes-All*\",\n \"*DS-Replication-Get-Changes-In-Filtered-Set*\",\n\n /* Identifying GUID used in ACE */\n\n \"*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2*\",\n \"*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*\",\n \"*89e95b76-444d-4c62-991a-0facbeda640c*\") \n \n /* The right to perform an operation controlled by an extended access right. */\n\n and winlog.event_data.AccessMask : \"0x100\" and\n not winlog.event_data.SubjectUserName : (\"*$\", \"MSOL_*\")\n", + "references": [ + "https://threathunterplaybook.com/notebooks/windows/06_credential_access/WIN-180815210510.html", + "https://threathunterplaybook.com/library/windows/active_directory_replication.html?highlight=dcsync#directory-replication-services-auditing", + "https://github.com/SigmaHQ/sigma/blob/master/rules/windows/builtin/security/win_ad_replication_non_machine_account.yml", + "https://github.com/atc-project/atomic-threat-coverage/blob/master/Atomic_Threat_Coverage/Logging_Policies/LP_0027_windows_audit_directory_service_access.md", + "https://attack.stealthbits.com/privilege-escalation-using-mimikatz-dcsync", + "https://www.thehacker.recipes/ad/movement/credentials/dumping/dcsync" + ], + "risk_score": 73, + "rule_id": "9f962927-1a4f-45f3-a57b-287f2c7029c1", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access", + "Active Directory" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.006", + "name": "DCSync", + "reference": "https://attack.mitre.org/techniques/T1003/006/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json new file mode 100644 index 0000000000000..781a90638b187 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json @@ -0,0 +1,59 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the modification of account Kerberos preauthentication options. An adversary with GenericWrite/GenericAll rights over the account can maliciously modify these settings to perform offline password cracking attacks such as AS-REP roasting.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*", + "logs-system.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Kerberos Preauthentication Disabled for User", + "note": "## Config\n\nThe 'Audit User Account Management' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nAccount Management > \nAudit User Account Management (Success,Failure)\n```\n", + "query": "event.code:4738 and message:\"'Don't Require Preauth' - Enabled\"\n", + "references": [ + "https://www.harmj0y.net/blog/activedirectory/roasting-as-reps", + "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4738", + "https://github.com/atc-project/atomic-threat-coverage/blob/master/Atomic_Threat_Coverage/Logging_Policies/LP_0026_windows_audit_user_account_management.md" + ], + "risk_score": 47, + "rule_id": "e514d8cd-ed15-4011-84e2-d15147e059f1", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/", + "subtechnique": [ + { + "id": "T1558.004", + "name": "AS-REP Roasting", + "reference": "https://attack.mitre.org/techniques/T1558/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json index 8fc7cd7b379b8..f1d18a80a4652 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Kerberos Traffic from Unusual Process", - "query": "network where event.type == \"start\" and network.direction : (\"outgoing\", \"egress\") and\n destination.port == 88 and source.port >= 49152 and\n process.executable != \"C:\\\\Windows\\\\System32\\\\lsass.exe\" and destination.address !=\"127.0.0.1\" and destination.address !=\"::1\" and\n /* insert False Positives here */\n not process.name in (\"swi_fc.exe\", \"fsIPcam.exe\", \"IPCamera.exe\", \"MicrosoftEdgeCP.exe\", \"MicrosoftEdge.exe\", \"iexplore.exe\", \"chrome.exe\", \"msedge.exe\", \"opera.exe\", \"firefox.exe\")\n", + "query": "network where event.type == \"start\" and network.direction : (\"outgoing\", \"egress\") and\n destination.port == 88 and source.port >= 49152 and\n process.executable != \"C:\\\\Windows\\\\System32\\\\lsass.exe\" and destination.address !=\"127.0.0.1\" and destination.address !=\"::1\" and\n /* insert false positives here */\n not process.name in (\"swi_fc.exe\", \"fsIPcam.exe\", \"IPCamera.exe\", \"MicrosoftEdgeCP.exe\", \"MicrosoftEdge.exe\", \"iexplore.exe\", \"chrome.exe\", \"msedge.exe\", \"opera.exe\", \"firefox.exe\")\n", "risk_score": 47, "rule_id": "897dc6b5-b39f-432a-8d75-d3730d50c782", "severity": "medium", @@ -45,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mfa_push_brute_force.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mfa_push_brute_force.json new file mode 100644 index 0000000000000..9fa6f3a10b31c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mfa_push_brute_force.json @@ -0,0 +1,49 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detect when an attacker abuses the Multi-Factor authentication mechanism by repeatedly issuing login requests until the user eventually accepts the Okta push notification. An adversary may attempt to bypass the Okta MFA policies configured for an organization to obtain unauthorized access.", + "index": [ + "filebeat-*", + "logs-okta*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential Abuse of Repeated MFA Push Notifications", + "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "sequence by user.email with maxspan=10m\n [any where event.module == \"okta\" and event.action == \"user.mfa.okta_verify.deny_push\"]\n [any where event.module == \"okta\" and event.action == \"user.mfa.okta_verify.deny_push\"]\n [any where event.module == \"okta\" and event.action == \"user.authentication.sso\"]\n", + "references": [ + "https://www.mandiant.com/resources/russian-targeting-gov-business" + ], + "risk_score": 73, + "rule_id": "97a8e584-fd3b-421f-9b9d-9c9d9e57e9d7", + "severity": "high", + "tags": [ + "Elastic", + "Identity", + "Okta", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1110", + "name": "Brute Force", + "reference": "https://attack.mitre.org/techniques/T1110/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json index 6bd3606d3b1f9..823d0d29b301f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json @@ -16,8 +16,8 @@ "language": "kuery", "license": "Elastic License v2", "name": "Attempts to Brute Force a Microsoft 365 User Account", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:(AzureActiveDirectory or Exchange) and\n event.category:authentication and event.action:(UserLoginFailed or PasswordLogonInitialAuthUsingPassword) and\n not o365.audit.LogonError:(UserAccountNotFound or EntitlementGrantsNotFound or UserStrongAuthEnrollmentRequired or\n UserStrongAuthClientAuthNRequired or InvalidReplyTo) and event.outcome:failure\n", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:(AzureActiveDirectory or Exchange) and\n event.category:authentication and event.action:(UserLoginFailed or PasswordLogonInitialAuthUsingPassword) and\n not o365.audit.LogonError:(UserAccountNotFound or EntitlementGrantsNotFound or UserStrongAuthEnrollmentRequired or\n UserStrongAuthClientAuthNRequired or InvalidReplyTo) and event.outcome:success\n", "references": [ "https://blueteamblog.com/7-ways-to-monitor-your-office-365-logs-using-siem" ], @@ -56,5 +56,5 @@ "value": 10 }, "type": "threshold", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json index c5a20b643b6d7..ba4500b56c6df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json @@ -14,8 +14,8 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Password Spraying of Microsoft 365 User Accounts", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:(Exchange or AzureActiveDirectory) and event.category:authentication and \nevent.action:(\"UserLoginFailed\" or \"PasswordLogonInitialAuthUsingPassword\") and event.outcome:failure\n", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:(Exchange or AzureActiveDirectory) and event.category:authentication and \nevent.action:(\"UserLoginFailed\" or \"PasswordLogonInitialAuthUsingPassword\") and event.outcome:success\n", "risk_score": 73, "rule_id": "3efee4f0-182a-40a8-a835-102c68a4175d", "severity": "high", @@ -51,5 +51,5 @@ "value": 25 }, "type": "threshold", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json index cd7a6959958fc..d2360c9889dea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Modification of WDigest Security Provider", - "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path:\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\" and\n registry.data.strings:\"1\"\n", + "query": "registry where event.type : (\"creation\", \"change\") and\n registry.path : \n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\"\n and registry.data.strings : (\"1\", \"0x00000001\")\n", "references": [ "https://www.csoonline.com/article/3438824/how-to-detect-and-halt-credential-theft-via-windows-wdigest.html", "https://www.praetorian.com/blog/mitigating-mimikatz-wdigest-cleartext-credential-theft?edition=2019" @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json index 166ddf7c5592d..d83bf00d9860c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json @@ -8,9 +8,7 @@ ], "from": "now-9m", "index": [ - "auditbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -65,5 +63,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json index 1bdba2064b4a4..b9cbc6f0c60cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json @@ -2,9 +2,9 @@ "author": [ "Elastic" ], - "description": "This rule detects PowerShell scripts that have capabilities to dump process memory using WindowsErrorReporting or Dbghelp.dll MiniDumpWriteDump. Attackers can use this tooling to dump LSASS and get access to credentials.", + "description": "This rule detects PowerShell scripts capable of dumping process memory using WindowsErrorReporting or Dbghelp.dll MiniDumpWriteDump. Attackers can use this tooling to dump LSASS and get access to credentials.", "false_positives": [ - "Powershell Scripts that use this capability for troubleshooting." + "PowerShell scripts that use this capability for troubleshooting." ], "from": "now-9m", "index": [ @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell MiniDump Script", - "note": "## Triage and analysis.\n\n### Investigating PowerShell MiniDump Script\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nProcess Memory Dump capabilities can be abused by attackers to extract credentials from LSASS or to obtain other privileged\ninformation stored in the process memory.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree)\n- Inspect any file or network events from the suspicious powershell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis.\n\n### Investigating PowerShell MiniDump Script\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nProcess Memory Dump capabilities can be abused by attackers to extract credentials from LSASS or to obtain other privileged\ninformation stored in the process memory.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and powershell.file.script_block_text:(MiniDumpWriteDump or MiniDumpWithFullMemory or pmuDetirWpmuDiniM)\n", "references": [ "https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Out-Minidump.ps1", @@ -79,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json new file mode 100644 index 0000000000000..ca1ea37561b20 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json @@ -0,0 +1,83 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detects PowerShell scripts that have the capability of requesting kerberos tickets, which is common step in Kerberoasting toolkits to crack service accounts.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "PowerShell Kerberos Ticket Request", + "query": "event.category:process and \n powershell.file.script_block_text : (\n KerberosRequestorSecurityToken\n )\n", + "references": [ + "https://cobalt.io/blog/kerberoast-attack-techniques", + "https://github.com/EmpireProject/Empire/blob/master/data/module_source/credentials/Invoke-Kerberoast.ps1" + ], + "risk_score": 47, + "rule_id": "eb610e70-f9e6-4949-82b9-f1c5bcd37c39", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/" + }, + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/", + "subtechnique": [ + { + "id": "T1558.003", + "name": "Kerberoasting", + "reference": "https://attack.mitre.org/techniques/T1558/003/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.001", + "name": "PowerShell", + "reference": "https://attack.mitre.org/techniques/T1059/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json new file mode 100644 index 0000000000000..20738784c1c5e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the assignment of the SeEnableDelegationPrivilege sensitive \"user right\" to a user. The SeEnableDelegationPrivilege \"user right\" enables computer and user accounts to be trusted for delegation. Attackers can abuse this right to compromise Active Directory accounts and elevate their privileges.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*", + "logs-system.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Sensitive Privilege SeEnableDelegationPrivilege assigned to a User", + "note": "## Config\n\nThe 'Audit Authorization Policy Change' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policy Configuration >\nAudit Policies >\nPolicy Change >\nAudit Authorization Policy Change (Success,Failure)\n```\n", + "query": "event.action: \"Authorization Policy Change\" and event.code:4704 and winlog.event_data.PrivilegeList:\"SeEnableDelegationPrivilege\"\n", + "references": [ + "https://www.harmj0y.net/blog/activedirectory/the-most-dangerous-user-right-you-probably-have-never-heard-of", + "https://github.com/SigmaHQ/sigma/blob/master/rules/windows/builtin/security/win_alert_active_directory_user_control.yml", + "https://github.com/atc-project/atomic-threat-coverage/blob/master/Atomic_Threat_Coverage/Logging_Policies/LP_0105_windows_audit_authorization_policy_change.md" + ], + "risk_score": 73, + "rule_id": "f494c678-3c33-43aa-b169-bb3d5198c41d", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_shadow_credentials.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_shadow_credentials.json new file mode 100644 index 0000000000000..261510427a628 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_shadow_credentials.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identify the modification of the msDS-KeyCredentialLink attribute in an Active Directory Computer or User Object. Attackers can abuse control over the object and create a key pair, append to raw public key in the attribute, and obtain persistent and stealthy access to the target user or computer object.", + "false_positives": [ + "Modifications in the msDS-KeyCredentialLink attribute can be done legitimately by the Azure AD Connect synchronization account or the ADFS service account. These accounts can be added as Exceptions." + ], + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*", + "logs-system.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Potential Shadow Credentials added to AD Object", + "note": "## Config\n\nThe 'Audit Directory Service Changes' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n\nThe above policy does not cover User objects, so we need to set up an AuditRule using https://github.com/OTRF/Set-AuditRule.\nAs this specifies the msDS-KeyCredentialLink Attribute GUID, it is expected to be low noise.\n\n```\nSet-AuditRule -AdObjectPath 'AD:\\CN=Users,DC=Domain,DC=com' -WellKnownSidType WorldSid -Rights WriteProperty -InheritanceFlags Children -AttributeGUID 5b47d60f-6090-40b2-9f37-2a4de88f3063 -AuditFlags Success\n```\n", + "query": "event.action:\"Directory Service Changes\" and event.code:\"5136\" and winlog.event_data.AttributeLDAPDisplayName:\"msDS-KeyCredentialLink\"\n", + "references": [ + "https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab", + "https://www.thehacker.recipes/ad/movement/kerberos/shadow-credentials", + "https://github.com/OTRF/Set-AuditRule" + ], + "risk_score": 73, + "rule_id": "79f97b31-480e-4e63-a7f4-ede42bf2c6de", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access", + "Active Directory" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1556", + "name": "Modify Authentication Process", + "reference": "https://attack.mitre.org/techniques/T1556/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json index d5b3a0acd4314..ce37891caaea4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies suspicious renamed COMSVCS.DLL Image Load, which exports the MiniDump function that can be used to dump a process memory. This may indicate an attempt to dump LSASS memory while bypassing command line based detection in preparation for credential access.", + "description": "Identifies suspicious renamed COMSVCS.DLL Image Load, which exports the MiniDump function that can be used to dump a process memory. This may indicate an attempt to dump LSASS memory while bypassing command-line based detection in preparation for credential access.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json index 14416213c101c..f51f049f03641 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies suspicious access to LSASS handle from a call trace pointing to DBGHelp.dll or DBGCore.dll, which both export the MiniDumpWriteDump method that can be used to dump LSASS memory content in preperation for credential access.", + "description": "Identifies suspicious access to LSASS handle from a call trace pointing to DBGHelp.dll or DBGCore.dll, which both export the MiniDumpWriteDump method that can be used to dump LSASS memory content in preparation for credential access.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Credential Access via LSASS Memory Dump", - "query": "process where event.code == \"10\" and\n winlog.event_data.TargetImage : \"?:\\\\WINDOWS\\\\system32\\\\lsass.exe\" and\n \n /* DLLs exporting MiniDumpWriteDump API to create an lsass mdmp*/\n winlog.event_data.CallTrace : (\"*dbhelp*\", \"*dbgcore*\") and\n \n /* case of lsass crashing */\n not process.executable : (\"?:\\\\Windows\\\\System32\\\\WerFault.exe\", \"?:\\\\Windows\\\\System32\\\\WerFaultSecure.exe\")\n", + "query": "process where event.code == \"10\" and\n winlog.event_data.TargetImage : \"?:\\\\WINDOWS\\\\system32\\\\lsass.exe\" and\n \n /* DLLs exporting MiniDumpWriteDump API to create an lsass mdmp*/\n winlog.event_data.CallTrace : (\"*dbghelp*\", \"*dbgcore*\") and\n \n /* case of lsass crashing */\n not process.executable : (\"?:\\\\Windows\\\\System32\\\\WerFault.exe\", \"?:\\\\Windows\\\\System32\\\\WerFaultSecure.exe\")\n", "references": [ "https://www.ired.team/offensive-security/credential-access-and-credential-dumping/dump-credentials-from-lsass-process-without-mimikatz" ], @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json index f5a444deabeed..95b1ff9fac197 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies suspicious access to an LSASS handle via PssCaptureSnapShot where two successive process access are performed by the same process and targeting two different instances of LSASS. This may indicate an attempt to evade detection and dump LSASS memory for credential access.", + "description": "Identifies suspicious access to an LSASS handle via PssCaptureSnapShot where two successive process accesses are performed by the same process and targeting two different instances of LSASS. This may indicate an attempt to evade detection and dump LSASS memory for credential access.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential LSASS Memory Dump via PssCaptureSnapShot", - "note": "## Config\n\nThis is meant to run only on datasources using agents v7.14+ since versions prior to that will be missing the threshold\nrule cardinality feature.", + "note": "## Config\n\nThis is meant to run only on datasources using Elastic Agent 7.14+ since versions prior to that will be missing the threshold\nrule cardinality feature.", "query": "event.category:process and event.code:10 and\n winlog.event_data.TargetImage:(\"C:\\\\Windows\\\\system32\\\\lsass.exe\" or\n \"c:\\\\Windows\\\\system32\\\\lsass.exe\" or\n \"c:\\\\Windows\\\\System32\\\\lsass.exe\")\n", "references": [ "https://www.matteomalvica.com/blog/2019/12/02/win-defender-atp-cred-bypass/", @@ -65,5 +65,5 @@ }, "timestamp_override": "event.ingested", "type": "threshold", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_createdcredential_access_symbolic_link_to_shadow_copy_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_createdcredential_access_symbolic_link_to_shadow_copy_created.json index 80604018e8d55..bbf07b959285f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_createdcredential_access_symbolic_link_to_shadow_copy_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_createdcredential_access_symbolic_link_to_shadow_copy_created.json @@ -2,9 +2,9 @@ "author": [ "Austin Songer" ], - "description": "Identifies the creation of symbolic links to a shadow copy. Symbolic Links can be used to access files in the shadow copy, including sensitive files that may contain credential information.", + "description": "Identifies the creation of symbolic links to a shadow copy. Symbolic links can be used to access files in the shadow copy, including sensitive files that may contain credential information.", "false_positives": [ - "Legitimate administrative activity related to shadow copies" + "Legitimate administrative activity related to shadow copies." ], "from": "now-9m", "index": [ @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json index 14f44ed3c3824..408a12cf85ce2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json @@ -15,7 +15,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "O365 Excessive Single Sign-On Logon Errors", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:AzureActiveDirectory and event.category:authentication and o365.audit.LogonError:\"SsoArtifactInvalidOrExpired\"\n", "risk_score": 73, "rule_id": "2de10e77-c144-4e69-afb7-344e7127abd0", @@ -52,5 +52,5 @@ "value": 5 }, "type": "threshold", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json index bf33f1d70f7ea..b5dfb8c37a47e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json @@ -4,7 +4,7 @@ ], "description": "Detects events which have a mismatch on the expected event agent ID. The status \"agent_id_mismatch\" occurs when the expected agent ID associated with the API key does not match the actual agent ID in an event. This could indicate attempts to spoof events in order to masquerade actual activity to evade detection.", "false_positives": [ - "This is meant to run only on datasources using agents v7.14+ since versions prior to that will be missing the necessary field, resulting in false positives." + "This is meant to run only on datasources using Elastic Agent 7.14+ since versions prior to that will be missing the necessary field, resulting in false positives." ], "from": "now-9m", "index": [ @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json index 4163e79cfd8db..e74f382e2291f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json @@ -4,7 +4,7 @@ ], "description": "Detects when multiple hosts are using the same agent ID. This could occur in the event of an agent being taken over and used to inject illegitimate documents into an instance as an attempt to spoof events in order to masquerade actual activity to evade detection.", "false_positives": [ - "This is meant to run only on datasources using agents v7.14+ since versions prior to that will be missing the necessary field, resulting in false positives." + "This is meant to run only on datasources using Elastic Agent 7.14+ since versions prior to that will be missing the necessary field, resulting in false positives." ], "from": "now-9m", "index": [ @@ -55,5 +55,5 @@ }, "timestamp_override": "event.ingested", "type": "threshold", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json index 80f855706e0fd..a1905ffa6c9aa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Modification of AmsiEnable Registry Key", - "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path: \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\" and\n registry.data.strings: \"0\"\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\",\n \"HKU\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\"\n ) and\n registry.data.strings: (\"0\", \"0x00000000\")\n", "references": [ "https://hackinparis.com/data/slides/2019/talks/HIP2019-Dominic_Chell-Cracking_The_Perimeter_With_Sharpshooter.pdf", "https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal" @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json index 744543ab8a1f0..cc131b5236a62 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of an AWS log trail. An adversary may delete trails in an attempt to evade defenses.", "false_positives": [ - "Trail deletions may be made by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Trail deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Trail deletions may be made by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Trail deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json index 61806b640fae2..e188a1ee50c65 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of an AWS CloudWatch alarm. An adversary may delete alarms in an attempt to evade defenses.", "false_positives": [ - "Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Alarm deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Alarm deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json index 8b3557e4a8fbd..5571bb2b9e317 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Windows Defender Disabled via Registry Modification", "note": "## Triage and analysis\n\nDetections should be investigated to identify if the hosts and users are authorized to use this tool. As this rule detects post-exploitation process activity, investigations into this should be prioritized.", - "query": "registry where event.type in (\"creation\", \"change\") and\n ((registry.path:\"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings:\"1\") or\n (registry.path:\"HKLM\\\\System\\\\ControlSet*\\\\Services\\\\WinDefend\\\\Start\" and\n registry.data.strings in (\"3\", \"4\")))\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n (\n (\n registry.path:\"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings: (\"1\", \"0x00000001\")\n ) or\n (\n registry.path:\"HKLM\\\\System\\\\*ControlSet*\\\\Services\\\\WinDefend\\\\Start\" and\n registry.data.strings in (\"3\", \"4\", \"0x00000003\", \"0x00000004\")\n )\n )\n", "references": [ "https://thedfirreport.com/2020/12/13/defender-control/" ], @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json index 44fed396b8ea8..ecd200c116b04 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Defender Exclusions Added via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Windows Defender Exclusions\n\nMicrosoft Windows Defender is an anti-virus product built-in within Microsoft Windows. Since this software product is\nused to prevent and stop malware, it's important to monitor what specific exclusions are made to the product's configuration\nsettings. These can often be signs of an adversary or malware trying to bypass Windows Defender's capabilities. One of the more\nnotable [examples](https://www.cyberbit.com/blog/endpoint-security/latest-trickbot-variant-has-new-tricks-up-its-sleeve/) was observed in 2018 where Trickbot incorporated mechanisms to disable Windows Defense to avoid detection.\n\n#### Possible investigation steps:\n- With this specific rule, it's completely possible to trigger detections on network administrative activity or benign users\nusing scripting and PowerShell to configure the different exclusions for Windows Defender. Therefore, it's important to\nidentify the source of the activity first and determine if there is any mal-intent behind the events.\n- The actual exclusion such as the process, the file or directory should be reviewed in order to determine the original\nintent behind the exclusion. Is the excluded file or process malicious in nature or is it related to software that needs\nto be legitimately whitelisted from Windows Defender?\n\n### False Positive Analysis\n- This rule has a higher chance to produce false positives based on the nature around configuring exclusions by possibly\na network administrator. In order to validate the activity further, review the specific exclusion made and determine based\non the exclusion of the original intent behind the exclusion. There are often many legitimate reasons why exclusions are made\nwith Windows Defender so it's important to gain context around the exclusion.\n\n### Related Rules\n- Windows Defender Disabled via Registry Modification\n- Disabling Windows Defender Security Settings via PowerShell\n\n### Response and Remediation\n- Since this is related to post-exploitation activity, immediate response should be taken to review, investigate and\npotentially isolate further activity\n- If further analysis showed malicious intent was behind the Defender exclusions, administrators should remove\nthe exclusion and ensure antimalware capability has not been disabled or deleted\n- Exclusion lists for antimalware capabilities should always be routinely monitored for review\n", + "note": "## Triage and analysis\n\n### Investigating Windows Defender Exclusions\n\nMicrosoft Windows Defender is an anti-virus product built-in within Microsoft Windows. Since this software product is\nused to prevent and stop malware, it's important to monitor what specific exclusions are made to the product's configuration\nsettings. These can often be signs of an adversary or malware trying to bypass Windows Defender's capabilities. One of the more\nnotable [examples](https://www.cyberbit.com/blog/endpoint-security/latest-trickbot-variant-has-new-tricks-up-its-sleeve/) was observed in 2018 where Trickbot incorporated mechanisms to disable Windows Defense to avoid detection.\n\n#### Possible investigation steps:\n- With this specific rule, it's completely possible to trigger detections on network administrative activity or benign users\nusing scripting and PowerShell to configure the different exclusions for Windows Defender. Therefore, it's important to\nidentify the source of the activity first and determine if there is any mal-intent behind the events.\n- The actual exclusion such as the process, the file or directory should be reviewed in order to determine the original\nintent behind the exclusion. Is the excluded file or process malicious in nature or is it related to software that needs\nto be legitimately allowlisted from Windows Defender?\n\n### False Positive Analysis\n- This rule has a higher chance to produce false positives based on the nature around configuring exclusions by possibly\na network administrator. In order to validate the activity further, review the specific exclusion and based on its\nintent. There are many legitimate reasons for exclusions, so it's important to gain context.\n\n### Related Rules\n- Windows Defender Disabled via Registry Modification\n- Disabling Windows Defender Security Settings via PowerShell\n\n### Response and Remediation\n- Since this is related to post-exploitation activity, take immediate action to review, investigate and\npotentially isolate further activity\n- If further analysis showed malicious intent was behind the Defender exclusions, administrators should remove\nthe exclusion and ensure antimalware capability has not been disabled or deleted\n- Exclusion lists for antimalware capabilities should always be routinely monitored for review\n", "query": "process where event.type == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name in (\"powershell.exe\", \"pwsh.dll\", \"powershell_ise.exe\")) and\n process.args : (\"*Add-MpPreference*\", \"*Set-MpPreference*\") and\n process.args : (\"*-Exclusion*\")\n", "references": [ "https://www.bitdefender.com/files/News/CaseStudies/study/400/Bitdefender-PR-Whitepaper-MosaicLoader-creat5540-en-EN.pdf" @@ -80,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json new file mode 100644 index 0000000000000..f3f321fe6918a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to disable PowerShell Script Block Logging via registry modification. Attackers may disable this logging to conceal their activities in the host and evade detection.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "PowerShell Script Block Logging Disabled", + "query": "registry where event.type == \"change\" and\n registry.path : \n \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\PowerShell\\\\ScriptBlockLogging\\\\EnableScriptBlockLogging\"\n and registry.data.strings : (\"0\", \"0x00000000\")\n", + "references": [ + "https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.PowerShell::EnableScriptBlockLogging" + ], + "risk_score": 47, + "rule_id": "818e23e6-2094-4f0e-8c01-22d30f3506c6", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.002", + "name": "Disable Windows Event Logging", + "reference": "https://attack.mitre.org/techniques/T1562/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json index 8c74783642395..d93764d893ef6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of one or more flow logs in AWS Elastic Compute Cloud (EC2). An adversary may delete flow logs in an attempt to evade defenses.", "false_positives": [ - "Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Flow log deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Flow log deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json index b64f7eed4be8d..0e019e84c8298 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of an Amazon Elastic Compute Cloud (EC2) network access control list (ACL) or one of its ingress/egress entries.", "false_positives": [ - "Network ACL's may be deleted by a network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Network ACL deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Network ACL's may be deleted by a network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Network ACL deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -60,5 +60,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json index 5685ac76b3ef9..38f6d22bef189 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json @@ -4,7 +4,7 @@ ], "description": "Identifies when an ElastiCache security group has been created.", "false_positives": [ - "A ElastiCache security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "A ElastiCache security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -57,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json index 83b58c0c046e0..0ba4be1170091 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json @@ -4,7 +4,7 @@ ], "description": "Identifies when an ElastiCache security group has been modified or deleted.", "false_positives": [ - "A ElastiCache security group deletion may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security Group deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "A ElastiCache security group deletion may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security Group deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -57,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json index a8a2f945c76e6..58cd10a68a612 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies an Event Hub deletion in Azure. An Event Hub is an event processing service that ingests and processes large volumes of events and data. An adversary may delete an Event Hub in an attempt to evade detection.", "false_positives": [ - "Event Hub deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Event Hub deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Event Hub deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Event Hub deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json index 98ee5f6eb8cbf..7d5bf5af25ea6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json @@ -2,9 +2,9 @@ "author": [ "Elastic" ], - "description": "Identifies the deletion of a firewall policy in Azure. An adversary may delete a firewall policy in an attempt to evade defenses and/or to eliminate barriers in carrying out their initiative.", + "description": "Identifies the deletion of a firewall policy in Azure. An adversary may delete a firewall policy in an attempt to evade defenses and/or to eliminate barriers to their objective.", "false_positives": [ - "Firewall policy deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Firewall policy deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Firewall policy deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Firewall policy deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -56,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json index c443d45dde4f0..a0b97a358ff07 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json @@ -2,9 +2,9 @@ "author": [ "Austin Songer" ], - "description": "Identifies the deletion of a Frontdoor Web Application Firewall (WAF) Policy in Azure. An adversary may delete a Frontdoor Web Application Firewall (WAF) Policy in an attempt to evade defenses and/or to eliminate barriers in carrying out their initiative.", + "description": "Identifies the deletion of a Frontdoor Web Application Firewall (WAF) Policy in Azure. An adversary may delete a Frontdoor Web Application Firewall (WAF) Policy in an attempt to evade defenses and/or to eliminate barriers to their objective.", "false_positives": [ - "Azure Front Web Application Firewall (WAF) Policy deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Azure Front Web Application Firewall (WAF) Policy deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Azure Front Web Application Firewall (WAF) Policy deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Azure Front Web Application Firewall (WAF) Policy deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -56,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json index d2fd746f8971e..69dfd4c1fad1a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies a Logging bucket deletion in Google Cloud Platform (GCP). Log buckets are containers that store and organize log data. A deleted bucket stays in a pending state for 7 days, and Logging continues to route logs to the bucket during that time. To stop routing logs to a deleted bucket, the log sinks can be deleted that have the bucket as a destination, or the filter for the sinks can be modified to stop routing logs to the deleted bucket. An adversary may delete a log bucket to evade detection.", "false_positives": [ - "Logging bucket deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Logging bucket deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Logging bucket deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Logging bucket deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json index 3e103413967fe..f604365ca46e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies a Logging sink deletion in Google Cloud Platform (GCP). Every time a log entry arrives, Logging compares the log entry to the sinks in that resource. Each sink whose filter matches the log entry writes a copy of the log entry to the sink's export destination. An adversary may delete a Logging sink to evade detection.", "false_positives": [ - "Logging sink deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Logging sink deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Logging sink deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Logging sink deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json index 78435128865f2..b3fe5da917406 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of a subscription in Google Cloud Platform (GCP). In GCP, the publisher-subscriber relationship (Pub/Sub) is an asynchronous messaging service that decouples event-producing and event-processing services. A subscription is a named resource representing the stream of messages to be delivered to the subscribing application.", "false_positives": [ - "Subscription deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Subscription deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Subscription deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Subscription deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json index eb6945e88e3fa..2b90f7601204f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of a topic in Google Cloud Platform (GCP). In GCP, the publisher-subscriber relationship (Pub/Sub) is an asynchronous messaging service that decouples event-producing and event-processing services. A publisher application creates and sends messages to a topic. Deleting a topic can interrupt message flow in the Pub/Sub pipeline.", "false_positives": [ - "Topic deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Topic deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Topic deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Topic deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json index 7132fed195ccf..343fa6ad88956 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of an Amazon GuardDuty detector. Upon deletion, GuardDuty stops monitoring the environment and all existing findings are lost.", "false_positives": [ - "The GuardDuty detector may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Detector deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "The GuardDuty detector may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Detector deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json index c40bbf236d668..709464c4cbb05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json @@ -5,9 +5,7 @@ "description": "Identifies registry write modifications to hide an encoded portable executable. This could be indicative of adversary defense evasion by avoiding the storing of malicious content directly on disk.", "from": "now-9m", "index": [ - "logs-endpoint.events.*", - "winlogbeat-*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -47,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json index 525b45432b00c..33dac6fd4f37a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json @@ -4,7 +4,7 @@ ], "description": "Identifies when events are deleted in Azure Kubernetes. Kubernetes events are objects that log any state changes. Example events are a container creation, an image pull, or a pod scheduling on a node. An adversary may delete events in Azure Kubernetes in an attempt to evade detection.", "false_positives": [ - "Events deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Events deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Events deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Events deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -56,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json index 7ac21a70100c0..54d251f3417a5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies execution from a directory masquerading as the Windows Program Files directories. These paths are trusted and usually host trusted third party programs. An adversary may leverage masquerading, along with low privileges to bypass detections whitelisting those folders.", + "description": "Identifies execution from a directory masquerading as the Windows Program Files directories. These paths are trusted and usually host trusted third party programs. An adversary may leverage masquerading, along with low privileges to bypass detections allowlisting those folders.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json index f4c3e3476c0b6..f9f465ef3ee18 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange DLP Policy Removed", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-DlpPolicy\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-dlppolicy?view=exchange-ps", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json index ab3399bddbe7a..0f2c612324234 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Malware Filter Policy Deletion", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-MalwareFilterPolicy\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-malwarefilterpolicy?view=exchange-ps" @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json index 06728dee5b150..7af0c1ea45d2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Malware Filter Rule Modification", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-MalwareFilterRule\" or \"Disable-MalwareFilterRule\") and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-malwarefilterrule?view=exchange-ps", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json index 50af384100139..38a7edda3a4e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Safe Attachment Rule Disabled", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Disable-SafeAttachmentRule\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/disable-safeattachmentrule?view=exchange-ps" @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json index 169ad751e6144..ae9512e52c705 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json @@ -4,7 +4,7 @@ ], "description": "Detects the occurrence of mailbox audit bypass associations. The mailbox audit is responsible for logging specified mailbox events (like accessing a folder or a message or permanently deleting a message). However, actions taken by some authorized accounts, such as accounts used by third-party tools or accounts used for lawful monitoring, can create a large number of mailbox audit log entries and may not be of interest to your organization. Because of this, administrators can create bypass associations, allowing certain accounts to perform their tasks without being logged. Attackers can abuse this allowlist mechanism to conceal actions taken, as the mailbox audit will log no activity done by the account.", "false_positives": [ - "Legitimate whitelisting of noisy accounts" + "Legitimate allowlisting of noisy accounts" ], "from": "now-30m", "index": [ @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "O365 Mailbox Audit Logging Bypass", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.action:Set-MailboxAuditBypassAssociation and event.outcome:success\n", "references": [ "https://twitter.com/misconfig/status/1476144066807140355" @@ -56,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json index f73b96015f885..9d37136d8db91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Microsoft Windows Defender Tampering", - "query": "registry where event.type in (\"creation\", \"change\") and\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\PUAProtection\" and\n registry.data.strings : \"0\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender Security Center\\\\App and Browser protection\\\\DisallowExploitProtectionOverride\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Features\\\\TamperProtection\" and\n registry.data.strings : \"0\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableRealtimeMonitoring\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableIntrusionPreventionSystem\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableScriptScanning\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Windows Defender Exploit Guard\\\\Controlled Folder Access\\\\EnableControlledFolderAccess\" and\n registry.data.strings : \"0\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableIOAVProtection\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Reporting\\\\DisableEnhancedNotifications\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\DisableBlockAtFirstSeen\" and\n registry.data.strings : \"1\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\SpynetReporting\" and\n registry.data.strings : \"0\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\SubmitSamplesConsent\" and\n registry.data.strings : \"0\") or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableBehaviorMonitoring\" and\n registry.data.strings : \"1\")\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\PUAProtection\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender Security Center\\\\App and Browser protection\\\\DisallowExploitProtectionOverride\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Features\\\\TamperProtection\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableRealtimeMonitoring\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableIntrusionPreventionSystem\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableScriptScanning\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Windows Defender Exploit Guard\\\\Controlled Folder Access\\\\EnableControlledFolderAccess\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableIOAVProtection\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Reporting\\\\DisableEnhancedNotifications\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\DisableBlockAtFirstSeen\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\SpynetReporting\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\SubmitSamplesConsent\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableBehaviorMonitoring\" and\n registry.data.strings : (\"1\", \"0x00000001\"))\n", "references": [ "https://thedfirreport.com/2021/10/18/icedid-to-xinglocker-ransomware-in-24-hours/", "https://www.tenforums.com/tutorials/32236-enable-disable-microsoft-defender-pua-protection-windows-10-a.html", @@ -55,5 +55,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json new file mode 100644 index 0000000000000..bbd6d1840fc0e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json @@ -0,0 +1,67 @@ +{ + "author": [ + "Elastic" + ], + "description": "Microsoft Office Products offers options for users and developers to control the security settings for running and using Macros. Adversaries may abuse these security settings to modify the default behavior of the Office Application to trust future macros and/or disable security warnings, which could increase their chances of establishing persistence.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "MS Office Macro Security Registry Modifications", + "query": "registry where event.type == \"change\" and\n registry.path : (\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\"\n ) and \n registry.data.strings == \"0x00000001\" and\n process.name : (\"cscript.exe\", \"wscript.exe\", \"mshta.exe\", \"mshta.exe\", \"winword.exe\", \"excel.exe\")\n", + "risk_score": 47, + "rule_id": "feeed87c-5e95-4339-aef1-47fd79bcfbe3", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1112", + "name": "Modify Registry", + "reference": "https://attack.mitre.org/techniques/T1112/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1204", + "name": "User Execution", + "reference": "https://attack.mitre.org/techniques/T1204/", + "subtechnique": [ + { + "id": "T1204.002", + "name": "Malicious File", + "reference": "https://attack.mitre.org/techniques/T1204/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json index 030e72dd4a411..135f70740692c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of a Network Watcher in Azure. Network Watchers are used to monitor, diagnose, view metrics, and enable or disable logs for resources in an Azure virtual network. An adversary may delete a Network Watcher in an attempt to evade defenses.", "false_positives": [ - "Network Watcher deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Network Watcher deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Network Watcher deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Network Watcher deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -56,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json index 1050efef9df21..a13aea7c4ee8c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "This rule detects the use of Reflection.Assembly to load PEs and DLLs in memory in Powershell Scripts. Attackers use this method to load executables and DLLs without writing to the disk, bypassing security solutions.", + "description": "Detects the use of Reflection.Assembly to load PEs and DLLs in memory in PowerShell scripts. Attackers use this method to load executables and DLLs without writing to the disk, bypassing security solutions.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -78,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json index 03de37966bbd4..81b17f8aa1039 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json @@ -2,9 +2,9 @@ "author": [ "Elastic" ], - "description": "Identifies the use of .Net functionality for decompression and base64 decoding combined in PowerShell scripts, which Malware and security tools heavily use to deobfuscate payloads and load them directly in memory to bypass defenses.", + "description": "Identifies the use of .NET functionality for decompression and base64 decoding combined in PowerShell scripts, which malware and security tools heavily use to deobfuscate payloads and load them directly in memory to bypass defenses.", "false_positives": [ - "Legitimate PowerShell Scripts which makes use of compression and encoding" + "Legitimate PowerShell Scripts which makes use of compression and encoding." ], "from": "now-9m", "index": [ @@ -71,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json index 873969efa49c0..86f1d853330bb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json @@ -4,7 +4,7 @@ ], "description": "Detects the use of Windows API functions that are commonly abused by malware and security tools to load malicious code or inject it into remote processes.", "false_positives": [ - "Legitimate Powershell Scripts that make use of these Functions" + "Legitimate PowerShell scripts that make use of these functions." ], "from": "now-9m", "index": [ @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Process Injection via PowerShell", - "note": "## Triage and analysis.\n\n### Investigating Potential Process Injection via PowerShell\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nPowerShell also has solid capabilities to make the interaction with the Win32 API in an uncomplicated and reliable way,\nlike the execution of inline C# code, PSReflect, Get-ProcAddress, etc.\n\nRed Team tooling and Malware Developers take advantage of these capabilities to develop stagers and loaders that inject\npayloads directly into the memory, without touching the disk.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree)\n- Inspect any file or network events from the suspicious powershell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis.\n\n### Investigating Potential Process Injection via PowerShell\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nPowerShell also has solid capabilities to make the interaction with the Win32 API in an uncomplicated and reliable way,\nlike the execution of inline C# code, PSReflect, Get-ProcAddress, etc.\n\nRed Team tooling and malware developers take advantage of these capabilities to develop stagers and loaders that inject\npayloads directly into the memory, without touching the disk.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n (VirtualAlloc or VirtualAllocEx or VirtualProtect or LdrLoadDll or LoadLibrary or LoadLibraryA or\n LoadLibraryEx or GetProcAddress or OpenProcess or OpenProcessToken or AdjustTokenPrivileges) and\n (WriteProcessMemory or CreateRemoteThread or NtCreateThreadEx or CreateThread or QueueUserAPC or\n SuspendThread or ResumeThread)\n )\n", "references": [ "https://github.com/EmpireProject/Empire/blob/master/data/module_source/management/Invoke-PSInject.ps1", @@ -62,5 +62,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json index 8c6ae92ef0a2d..d4e49ec00b648 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json @@ -4,7 +4,7 @@ ], "description": "Identifies when the Windows Firewall is disabled using PowerShell cmdlets, which attackers do to evade network constraints, like internet and network lateral communication restrictions.", "false_positives": [ - "Windows Firewall can be disabled may be performed by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Windows Profile being disabled from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Windows Firewall can be disabled by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Windows Profile being disabled by unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-9m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json index d673b7ef324f6..febf708cc12d5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of various Amazon Simple Storage Service (S3) bucket configuration components.", "false_positives": [ - "Bucket components may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Bucket component deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Bucket components may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Bucket component deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -54,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json index 3e1925fce6254..e5d4e23d55bec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Scheduled Tasks AT Command Enabled", - "query": "registry where \n registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\Configuration\\\\EnableAt\" and registry.data.strings == \"1\"\n", + "query": "registry where \n registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\Configuration\\\\EnableAt\" and \n registry.data.strings : (\"1\", \"0x00000001\")\n", "references": [ "https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-scheduledjob" ], @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json index 7e0622cd025dd..9092870cdffa1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json @@ -5,9 +5,7 @@ "description": "Identifies modifications to the registered Subject Interface Package (SIP) providers. SIP providers are used by the Windows cryptographic system to validate file signatures on the system. This may be an attempt to bypass signature validation checks or inject code into critical processes.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -52,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json index 5de655977130f..743ebc561dd1c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "SolarWinds Process Disabling Services via Registry", - "query": "registry where registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\Start\" and registry.data.strings == \"4\" and\n process.name : (\n \"SolarWinds.BusinessLayerHost*.exe\", \n \"ConfigurationWizard*.exe\", \n \"NetflowDatabaseMaintenance*.exe\", \n \"NetFlowService*.exe\", \n \"SolarWinds.Administration*.exe\", \n \"SolarWinds.Collector.Service*.exe\" , \n \"SolarwindsDiagnostics*.exe\")\n", + "query": "registry where registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\Start\" and\n registry.data.strings : (\"4\", \"0x00000004\") and\n process.name : (\n \"SolarWinds.BusinessLayerHost*.exe\", \n \"ConfigurationWizard*.exe\", \n \"NetflowDatabaseMaintenance*.exe\", \n \"NetFlowService*.exe\", \n \"SolarWinds.Administration*.exe\", \n \"SolarWinds.Collector.Service*.exe\" , \n \"SolarwindsDiagnostics*.exe\")\n", "references": [ "https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html" ], @@ -74,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suppression_rule_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suppression_rule_created.json index cce222e233838..d939761852ca8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suppression_rule_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suppression_rule_created.json @@ -2,7 +2,7 @@ "author": [ "Austin Songer" ], - "description": "Identifies the creation of suppression rules in Azure. Suppression rules are a mechanism used to suppress alerts previously identified as False Positives or too noisy to be in Production. This mechanism can be abused or mistakenly configured, resulting in defense evasions and loss of security visibility.", + "description": "Identifies the creation of suppression rules in Azure. Suppression rules are a mechanism used to suppress alerts previously identified as false positives or too noisy to be in production. This mechanism can be abused or mistakenly configured, resulting in defense evasions and loss of security visibility.", "false_positives": [ "Suppression Rules can be created legitimately by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Suppression Rules created by unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json index 9c1f82d12a441..8d02b5b8e52e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies WMIC whitelisting bypass techniques by alerting on suspicious execution of scripts. When WMIC loads scripting libraries it may be indicative of a whitelist bypass.", + "description": "Identifies WMIC allowlist bypass techniques by alerting on suspicious execution of scripts. When WMIC loads scripting libraries it may be indicative of an allowlist bypass.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -41,5 +41,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json index 9a7c7c9f668cc..1b959097e30c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of a specified AWS Web Application Firewall (WAF) access control list.", "false_positives": [ - "Firewall ACL's may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Web ACL deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Firewall ACL's may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Web ACL deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json index 6f42caa63cf49..fb35a65bde354 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of a specified AWS Web Application Firewall (WAF) rule or rule group.", "false_positives": [ - "WAF rules or rule groups may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Rule deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "WAF rules or rule groups may be deleted by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Rule deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json index 9af3832303666..9cf0541d1162e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "AdFind Command Activity", - "note": "## Triage and analysis\n\n### Investigating AdFind Command Activity\n\n[AdFind](http://www.joeware.net/freetools/tools/adfind/) is a freely available command-line tool used to retrieve information from\nActivity Directory (AD). Network discovery and enumeration tools like `AdFind` are useful to adversaries in the same ways\nthey are effective for network administrators. This tool provides quick ability to scope AD person/computer objects and\nunderstand subnets and domain information. There are many [examples](https://thedfirreport.com/category/adfind/)\nobserved where this tool has been adopted by ransomware and criminal groups and used in compromises.\n\n#### Possible investigation steps:\n- `AdFind` is a legitimate Active Directory enumeration tool used by network administrators, it's important to understand\nthe source of the activity. This could involve identifying the account using `AdFind` and determining based on the command-lines\nwhat information was retrieved, then further determining if these actions are in scope of that user's traditional responsibilities.\n- In multiple public references, `AdFind` is leveraged after initial access is achieved, review previous activity on impacted\nmachine looking for suspicious indicators such as previous anti-virus/EDR alerts, phishing emails received, or network traffic\nto suspicious infrastructure\n\n### False Positive Analysis\n- This rule has the high chance to produce false positives as it is a legitimate tool used by network administrators. One\noption could be whitelisting specific users or groups who use the tool as part of their daily responsibilities. This can\nbe done by leveraging the exception workflow in the Kibana Security App or Elasticsearch API to tune this rule to your environment\n- Malicious behavior with `AdFind` should be investigated as part of a step within an attack chain. It doesn't happen in\nisolation, so reviewing previous logs/activity from impacted machines could be very telling.\n\n### Related Rules\n- Windows Network Enumeration\n- Enumeration of Administrator Accounts\n- Enumeration Command Spawned via WMIPrvSE\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior\n- It's important to understand that `AdFind` is an Active Directory enumeration tool and can be used for malicious or legitimate\npurposes, so understanding the intent behind the activity will help determine the appropropriate response.\n", + "note": "## Triage and analysis\n\n### Investigating AdFind Command Activity\n\n[AdFind](http://www.joeware.net/freetools/tools/adfind/) is a freely available command-line tool used to retrieve information from\nActive Directory (AD). Network discovery and enumeration tools like `AdFind` are useful to adversaries in the same ways\nthey are effective for network administrators. This tool provides quick ability to scope AD person/computer objects and\nunderstand subnets and domain information. There are many [examples](https://thedfirreport.com/category/adfind/)\nobserved where this tool has been adopted by ransomware and criminal groups and used in compromises.\n\n#### Possible investigation steps:\n- `AdFind` is a legitimate Active Directory enumeration tool used by network administrators, it's important to understand\nthe source of the activity. This could involve identifying the account using `AdFind` and determining based on the command-lines\nwhat information was retrieved, then further determining if these actions are in scope of that user's traditional responsibilities.\n- In multiple public references, `AdFind` is leveraged after initial access is achieved, review previous activity on impacted\nmachine looking for suspicious indicators such as previous anti-virus/EDR alerts, phishing emails received, or network traffic\nto suspicious infrastructure.\n\n### False Positive Analysis\n- This rule has the high chance to produce false positives as it is a legitimate tool used by network administrators. One\noption could be allowlisting specific users or groups who use the tool as part of their daily responsibilities. This can\nbe done by leveraging the exception workflow in the Kibana Security App or Elasticsearch API to tune this rule to your environment\n- Malicious behavior with `AdFind` should be investigated as part of a step within an attack chain. It doesn't happen in\nisolation, so reviewing previous logs/activity from impacted machines could be very telling.\n\n### Related Rules\n- Windows Network Enumeration\n- Enumeration of Administrator Accounts\n- Enumeration Command Spawned via WMIPrvSE\n\n### Response and Remediation\n- take immediate action to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior\n- It's important to understand that `AdFind` is an Active Directory enumeration tool and can be used for malicious or legitimate\npurposes, so understanding the intent behind the activity will help determine the appropropriate response.\n", "query": "process where event.type in (\"start\", \"process_started\") and \n (process.name : \"AdFind.exe\" or process.pe.original_file_name == \"AdFind.exe\") and \n process.args : (\"objectcategory=computer\", \"(objectcategory=computer)\", \n \"objectcategory=person\", \"(objectcategory=person)\",\n \"objectcategory=subnet\", \"(objectcategory=subnet)\",\n \"objectcategory=group\", \"(objectcategory=group)\", \n \"objectcategory=organizationalunit\", \"(objectcategory=organizationalunit)\",\n \"objectcategory=attributeschema\", \"(objectcategory=attributeschema)\",\n \"domainlist\", \"dcmodes\", \"adinfo\", \"dclist\", \"computers_pwnotreqd\", \"trustdmp\")\n", "references": [ "http://www.joeware.net/freetools/tools/adfind/", @@ -80,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json index dc855f3ed9a57..0ea71e5314ed4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Net command via SYSTEM account", - "query": "process where event.type in (\"start\", \"process_started\") and \n user.id in (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\") and\n process.name : \"whoami.exe\" or\n (process.name : \"net1.exe\" and not process.parent.name : \"net.exe\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and \n (process.Ext.token.integrity_level_name : \"System\" or\n winlog.event_data.IntegrityLevel : \"System\") and\n process.name : \"whoami.exe\" or\n (process.name : \"net1.exe\" and not process.parent.name : \"net.exe\")\n", "risk_score": 21, "rule_id": "2856446a-34e6-435b-9fb5-f8f040bfa7ed", "severity": "low", @@ -42,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json index 6a38ff75d6660..5f5d99e7f17f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json @@ -4,7 +4,7 @@ ], "description": "This rule detects the use of discovery-related Windows API functions in PowerShell Scripts. Attackers can use these functions to perform various situational awareness related activities, like enumerating users, shares, sessions, domain trusts, groups, etc.", "false_positives": [ - "Legitimate Powershell Scripts that make use of these Functions" + "Legitimate PowerShell scripts that make use of these functions." ], "from": "now-9m", "index": [ @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Suspicious Discovery Related Windows API Functions", - "note": "## Triage and analysis.\n\n### Investigating PowerShell Suspicious Discovery Related Windows API Functions\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can use PowerShell to interact with the Win32 API to bypass file based AntiVirus detections, using libraries\nlike PSReflect or Get-ProcAddress Cmdlet.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious powershell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis.\n\n### Investigating PowerShell Suspicious Discovery Related Windows API Functions\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can use PowerShell to interact with the Win32 API to bypass file based antivirus detections, using libraries\nlike PSReflect or Get-ProcAddress Cmdlet.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n NetShareEnum or\n NetWkstaUserEnum or\n NetSessionEnum or\n NetLocalGroupEnum or\n NetLocalGroupGetMembers or\n DsGetSiteName or\n DsEnumerateDomainTrusts or\n WTSEnumerateSessionsEx or\n WTSQuerySessionInformation or\n LsaGetLogonSessionData or\n QueryServiceObjectSecurity\n )\n", "references": [ "https://github.com/BC-SECURITY/Empire/blob/9259e5106986847d2bb770c4289c0c0f1adf2344/data/module_source/situational_awareness/network/powerview.ps1#L21413", @@ -88,5 +88,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json index 9beafd16f7956..1e687851af5f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json @@ -8,9 +8,7 @@ ], "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -49,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json index 5594a5e583a1c..467d5ecc88977 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Enumeration of Privileged Local Groups Membership", - "note": "## Config\n\nThis will require Windows security event 4799 by enabling audit success for the windows Account Management category and\nthe Security Group Management subcategory.\n", + "note": "## Config\n\nThis will require Windows security event 4799 by enabling audit success for the Windows Account Management category and\nthe Security Group Management subcategory.\n", "query": "iam where event.action == \"user-member-enumerated\" and\n\n /* noisy and usual legit processes excluded */\n not winlog.event_data.CallerProcessName:\n (\"?:\\\\Windows\\\\System32\\\\VSSVC.exe\",\n \"?:\\\\Windows\\\\System32\\\\SearchIndexer.exe\",\n \"?:\\\\Windows\\\\System32\\\\CompatTelRunner.exe\",\n \"?:\\\\Windows\\\\System32\\\\oobe\\\\msoobe.exe\",\n \"?:\\\\Windows\\\\System32\\\\net1.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\Netplwiz.exe\",\n \"?:\\\\Windows\\\\System32\\\\msiexec.exe\",\n \"?:\\\\Windows\\\\System32\\\\CloudExperienceHostBroker.exe\",\n \"?:\\\\Windows\\\\System32\\\\wbem\\\\WmiPrvSE.exe\",\n \"?:\\\\Windows\\\\System32\\\\SrTasks.exe\",\n \"?:\\\\Windows\\\\System32\\\\lsass.exe\",\n \"?:\\\\Windows\\\\System32\\\\diskshadow.exe\",\n \"?:\\\\Windows\\\\System32\\\\dfsrs.exe\",\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\") and\n /* privileged local groups */\n (group.name:(\"admin*\",\"RemoteDesktopUsers\") or\n winlog.event_data.TargetSid:(\"S-1-5-32-544\",\"S-1-5-32-555\"))\n", "risk_score": 43, "rule_id": "291a0de9-937a-4189-94c0-3e847c8b13e4", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json index f4e07c6fa15b5..38dfa02f40560 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious Portable Executable Encoded in Powershell Script", - "note": "## Triage and analysis.\n\n### Investigating Suspicious Portable Executable Encoded in Powershell Script\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can abuse PowerShell In-Memory capabilities to inject executables into memory without touching the disk, bypassing\nAntiVirus software. These executables are generally base64 encoded.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious powershell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell Reflection Assembly Load - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis.\n\n### Investigating Suspicious Portable Executable Encoded in Powershell Script\n\nPowerShell is one of the main tools used by system administrators for automation, report routines, and other tasks.\n\nAttackers can abuse PowerShell in-memory capabilities to inject executables into memory without touching the disk,\nbypassing antivirus software. These executables are generally base64 encoded.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n\n- PowerShell Reflection Assembly Load - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and Remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n TVqQAAMAAAAEAAAA\n )\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0109_windows_powershell_script_block_log.md" @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json index 4cdb6edcde1b6..6ff1d2b8fff9d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json @@ -4,7 +4,7 @@ ], "description": "Detects the use of PSReflect in PowerShell scripts. Attackers leverage PSReflect as a library that enables PowerShell to access win32 API functions.", "false_positives": [ - "Legitimate Powershell Scripts that make use of PSReflect to access the win32 API" + "Legitimate PowerShell scripts that make use of PSReflect to access the win32 API" ], "from": "now-9m", "index": [ @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell PSReflect Script", - "note": "## Triage and analysis\n### Investigating PowerShell PSReflect Script\n\nPowerShell is one of the main tools in the belt of system administrators for automation, report routines, and other tasks.\n\nPSReflect is a library that enables PowerShell to access win32 API functions in an uncomplicated way. It also helps to\ncreate enums and structs easily\u2014all without touching the disk.\n\nAlthough this is an interesting project for every developer and admin out there, it is mainly used in the red team and\nmalware tooling for its capabilities.\n\nDetecting the core implementation of PSReflect means detecting most of the tooling that uses windows API through\nPowerShell, enabling the defender to discover tools being dropped in the environment.\n\n#### Possible investigation steps:\n- Check for additional PowerShell logs that indicate that the script/command was run.\n- Gather the script content that may be split into multiple script blocks, and identify its capabilities.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n- Look for additional alerts involving the host and the user.\n\n### False Positive Analysis\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- PowerShell Reflection Assembly Load - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\nThe 'PowerShell Script Block Logging' logging policy is required be configured (Enable).\n\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\nSteps to implement the logging policy via registry:\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n### Investigating PowerShell PSReflect Script\n\nPowerShell is one of the main tools in the belt of system administrators for automation, report routines, and other tasks.\n\nPSReflect is a library that enables PowerShell to access win32 API functions in an uncomplicated way. It also helps to\ncreate enums and structs easily\u2014all without touching the disk.\n\nAlthough this is an interesting project for every developer and admin out there, it is mainly used in the red team and\nmalware tooling for its capabilities.\n\nDetecting the core implementation of PSReflect means detecting most of the tooling that uses Windows API through\nPowerShell, enabling the defender to discover tools being dropped in the environment.\n\n#### Possible investigation steps:\n- Check for additional PowerShell logs that indicate that the script/command was run.\n- Gather the script content that may be split into multiple script blocks, and identify its capabilities.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n- Look for additional alerts involving the host and the user.\n\n### False Positive Analysis\n- Verify whether the script content is malicious/harmful.\n\n### Related Rules\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- PowerShell Reflection Assembly Load - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate, and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\nThe 'PowerShell Script Block Logging' logging policy is required be configured (Enable).\n\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\nSteps to implement the logging policy via registry:\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text:(\n New-InMemoryModule or\n Add-Win32Type or\n psenum or\n DefineDynamicAssembly or\n DefineDynamicModule or\n Reflection.TypeAttributes or\n Reflection.Emit.OpCodes or\n Reflection.Emit.CustomAttributeBuilder or\n Runtime.InteropServices.DllImportAttribute\n )\n", "references": [ "https://github.com/mattifestation/PSReflect/blob/master/PSReflect.psm1", @@ -61,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json index 2ccc730c3fa01..34ec31d697fa4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via Registration Utility", - "query": "sequence by process.entity_id\n [process where event.type == \"start\" and\n process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not (\n user.id == \"S-1-5-18\" and\n (process.parent.name : \"msiexec.exe\" or process.parent.executable : (\"C:\\\\Program Files (x86)\\\\*.exe\", \"C:\\\\Program Files\\\\*.exe\"))\n )\n ]\n [network where process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\",\n \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\",\n \"100.64.0.0/10\", \"192.175.48.0/24\",\"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\") and network.protocol != \"dns\"]\n", + "query": "sequence by process.entity_id\n [process where event.type == \"start\" and\n process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not (\n (process.Ext.token.integrity_level_name : \"System\" or winlog.event_data.IntegrityLevel : \"System\") and\n (process.parent.name : \"msiexec.exe\" or process.parent.executable : (\"C:\\\\Program Files (x86)\\\\*.exe\", \"C:\\\\Program Files\\\\*.exe\"))\n )\n ]\n [network where process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\",\n \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\",\n \"100.64.0.0/10\", \"192.175.48.0/24\",\"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\") and network.protocol != \"dns\"]\n", "references": [ "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" ], @@ -63,5 +63,5 @@ } ], "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_java_netcon_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_java_netcon_childproc.json new file mode 100644 index 0000000000000..fe04cfe35b363 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_java_netcon_childproc.json @@ -0,0 +1,63 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies an outbound network connection by JAVA to LDAP, RMI or DNS standard ports followed by a suspicious JAVA child processes. This may indicate an attempt to exploit a JAVA/NDI (Java Naming and Directory Interface) injection vulnerability.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential JAVA/JNDI Exploitation Attempt", + "query": "sequence by host.id with maxspan=1m\n [network where event.action == \"connection_attempted\" and\n process.name : \"java\" and\n /*\n outbound connection attempt to\n LDAP, RMI or DNS standard ports\n by JAVA process\n */\n destination.port in (1389, 389, 1099, 53, 5353)] by process.pid\n [process where event.type == \"start\" and\n\n /* Suspicious JAVA child process */\n process.parent.name : \"java\" and\n process.name : (\"sh\",\n \"bash\",\n \"dash\",\n \"ksh\",\n \"tcsh\",\n \"zsh\",\n \"curl\",\n \"perl*\",\n \"python*\",\n \"ruby*\",\n \"php*\",\n \"wget\")] by process.parent.pid\n", + "references": [ + "https://www.lunasec.io/docs/blog/log4j-zero-day/", + "https://github.com/christophetd/log4shell-vulnerable-app", + "https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf" + ], + "risk_score": 73, + "rule_id": "c3f5e1d8-910e-43b4-8d44-d748e498ca86", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.007", + "name": "JavaScript", + "reference": "https://attack.mitre.org/techniques/T1059/007/" + } + ] + }, + { + "id": "T1203", + "name": "Exploitation for Client Execution", + "reference": "https://attack.mitre.org/techniques/T1203/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json index a6a9e24ccb63a..25ae6170a7ca5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Transport Rule Creation", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-TransportRule\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/new-transportrule?view=exchange-ps", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json index 836ade6c0b80f..b0e311200ce2e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Transport Rule Modification", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-TransportRule\" or \"Disable-TransportRule\") and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-transportrule?view=exchange-ps", @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_restored.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_restored.json index c396c9a2cce17..36f53d2b60072 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_restored.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_restored.json @@ -4,7 +4,7 @@ ], "description": "Identifies when an attempt was made to restore an RDS Snapshot. Snapshots are sometimes shared by threat actors in order to exfiltrate bulk data. If the permissions were modified, verify if the snapshot was shared with an unauthorized or unexpected AWS account.", "false_positives": [ - "Restoring snapshots may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Snapshot restoration from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Restoring snapshots may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Snapshot restoration by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json index 034a6ebfc02f3..61903a15c0993 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json @@ -4,7 +4,7 @@ ], "description": "Identifies when a user has disabled or deleted an EventBridge rule. This activity can result in an unintended loss of visibility in applications or a break in the flow with other AWS services.", "false_positives": [ - "EventBridge Rules could be deleted or disabled by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. EventBridge Rules being deleted or disabled from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "EventBridge Rules could be deleted or disabled by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. EventBridge Rules being deleted or disabled by unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-20m", "index": [ @@ -44,5 +44,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json index f92d6ec44d655..9ad5f631ad307 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of a specified AWS CloudWatch log group. When a log group is deleted, all the archived log events associated with the log group are also permanently deleted.", "false_positives": [ - "Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Log group deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Log group deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -73,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json index 46ac2d19889f2..930dc822eea03 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of an AWS CloudWatch log stream, which permanently deletes all associated archived log events with the stream.", "false_positives": [ - "A log stream may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Log stream deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "A log stream may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Log stream deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -73,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json index 52fadfdf8ee19..9465922d7c34f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json @@ -4,7 +4,7 @@ ], "description": "Detects when a EFS File System or Mount is deleted. An adversary could break any file system using the mount target that is being deleted, which might disrupt instances or applications using those mounts. The mount must be deleted prior to deleting the File System, or the adversary will be unable to delete the File System.", "false_positives": [ - "File System or Mount being deleted may be performed by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. File System Mount deleted from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "File System or Mount being deleted may be performed by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. File System Mount deletion by unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json index 76901da74ce96..f67419f5746ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies an Identity and Access Management (IAM) role deletion in Google Cloud Platform (GCP). A role contains a set of permissions that allows you to perform specific actions on Google Cloud resources. An adversary may delete an IAM role to inhibit access to accounts utilized by legitimate users.", "false_positives": [ - "Role deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Role deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Role deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Role deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json index c3aa1523e4d1e..9d28009acb13b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json @@ -4,7 +4,7 @@ ], "description": "Identifies when a Google Cloud Platform (GCP) storage bucket is deleted. An adversary may delete a storage bucket in order to disrupt their target's business operations.", "false_positives": [ - "Storage buckets may be deleted by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Bucket deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Storage buckets may be deleted by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Bucket deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json index ecf52f8e8830c..143d7138d8bad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies when a Virtual Private Cloud a virtual private cloud (VPC) route is created in Google Cloud Platform (GCP). Google Cloud routes define the paths that network traffic takes from a virtual machine (VM) instance to other destinations. These destinations can be inside a Google VPC network or outside it. An adversary may create a route in order to impact the flow of network traffic in their target's cloud environment.", + "description": "Identifies when a virtual private cloud (VPC) route is created in Google Cloud Platform (GCP). Google Cloud routes define the paths that network traffic takes from a virtual machine (VM) instance to other destinations. These destinations can be inside a Google VPC network or outside it. An adversary may create a route in order to impact the flow of network traffic in their target's cloud environment.", "false_positives": [ "Virtual Private Cloud routes may be created by system administrators. Verify that the configuration change was expected. Exceptions can be added to this rule to filter expected behavior." ], @@ -32,5 +32,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json index e1f5fcbf4836e..9aea3879d2545 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of a specified AWS Identity and Access Management (IAM) resource group. Deleting a resource group does not delete resources that are members of the group; it only deletes the group structure.", "false_positives": [ - "A resource group may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Resource group deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "A resource group may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Resource group deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_kubernetes_pod_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_kubernetes_pod_deleted.json index 0612c3868b9aa..456eff557ccf1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_kubernetes_pod_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_kubernetes_pod_deleted.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of Azure Kubernetes Pods. Adversaries may delete a Kubernetes pod to disrupt the normal behavior of the environment.", "false_positives": [ - "Pods may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Pods deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Pods may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Pods deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json index 46e760bb32243..14a2f6ddeb095 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Potential ransomware activity", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", "query": "event.dataset:o365.audit and event.provider:SecurityComplianceCenter and event.category:web and event.action:\"Potential ransomware activity\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/cloud-app-security/anomaly-detection-policy", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json index c3a53310781df..1534e4ba9f75c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json @@ -2,7 +2,7 @@ "author": [ "Austin Songer" ], - "description": "Identifies that a user has deleted an unusually large volume of files as reported by Microsoft Cloud App Security.", + "description": "Identifies that a user has deleted an unusually large volume of files as reported by Microsoft Cloud App Security.", "false_positives": [ "Users or System Administrator cleaning out folders." ], @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Unusual Volume of File Deletion", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", "query": "event.dataset:o365.audit and event.provider:SecurityComplianceCenter and event.category:web and event.action:\"Unusual volume of file deletion\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/cloud-app-security/anomaly-detection-policy", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json index 08ae6ce11bbb5..c114c26556033 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of an Amazon Relational Database Service (RDS) Aurora database cluster or global database cluster.", "false_positives": [ - "Clusters may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Cluster deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Clusters may be deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Cluster deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json index c60b467fa238b..5f13c9ca2ecb8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json @@ -5,7 +5,7 @@ ], "description": "Identifies the deletion of an Amazon Relational Database Service (RDS) Security group.", "false_positives": [ - "An RDS security group deletion may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "An RDS security group deletion may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_virtual_network_device_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_virtual_network_device_modified.json index a378a3d607abb..bf988e5dd1e0c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_virtual_network_device_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_virtual_network_device_modified.json @@ -2,9 +2,9 @@ "author": [ "Austin Songer" ], - "description": "Identifies when a virtual network device is being modified or deleted. This can be a network virtual appliance, virtual hub, or virtual router.", + "description": "Identifies when a virtual network device is modified or deleted. This can be a network virtual appliance, virtual hub, or virtual router.", "false_positives": [ - "Virtual Network Device being modified or deleted may be performed by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Virtual Network Device modified or deleted from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Virtual Network Device modification or deletion may be performed by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Virtual Network Device modification or deletion by unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Virtual Network Device Modified or Deleted", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:(\"MICROSOFT.NETWORK/NETWORKINTERFACES/TAPCONFIGURATIONS/WRITE\" or\n\"MICROSOFT.NETWORK/NETWORKINTERFACES/TAPCONFIGURATIONS/DELETE\" or \"MICROSOFT.NETWORK/NETWORKINTERFACES/WRITE\" or\n\"MICROSOFT.NETWORK/NETWORKINTERFACES/JOIN/ACTION\" or \"MICROSOFT.NETWORK/NETWORKINTERFACES/DELETE\"or\n\"MICROSOFT.NETWORK/NETWORKVIRTUALAPPLIANCES/DELETE\" or \"MICROSOFT.NETWORK/NETWORKVIRTUALAPPLIANCES/WRITE\" or\n\"MICROSOFT.NETWORK/VIRTUALHUBS/DELETE\" or \"MICROSOFT.NETWORK/VIRTUALHUBS/WRITE\" or\n\"MICROSOFT.NETWORK/VIRTUALROUTERS/WRITE\" or \"MICROSOFT.NETWORK/VIRTUALROUTERS/DELETE\") and \nevent.outcome:(Success or success)\n", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:(\"MICROSOFT.NETWORK/NETWORKINTERFACES/TAPCONFIGURATIONS/WRITE\" or\n\"MICROSOFT.NETWORK/NETWORKINTERFACES/TAPCONFIGURATIONS/DELETE\" or \"MICROSOFT.NETWORK/NETWORKINTERFACES/WRITE\" or\n\"MICROSOFT.NETWORK/NETWORKINTERFACES/JOIN/ACTION\" or \"MICROSOFT.NETWORK/NETWORKINTERFACES/DELETE\" or\n\"MICROSOFT.NETWORK/NETWORKVIRTUALAPPLIANCES/DELETE\" or \"MICROSOFT.NETWORK/NETWORKVIRTUALAPPLIANCES/WRITE\" or\n\"MICROSOFT.NETWORK/VIRTUALHUBS/DELETE\" or \"MICROSOFT.NETWORK/VIRTUALHUBS/WRITE\" or\n\"MICROSOFT.NETWORK/VIRTUALROUTERS/WRITE\" or \"MICROSOFT.NETWORK/VIRTUALROUTERS/DELETE\") and \nevent.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations" ], @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json index 940229bf63751..fb7121abee4c7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Volume Shadow Copy Deleted or Resized via VssAdmin", - "query": "process where event.type in (\"start\", \"process_started\") and event.action == \"start\" \n and (process.name : \"vssadmin.exe\" or process.pe.original_file_name == \"VSSADMIN.EXE\") and\n process.args in (\"delete\", \"resize\") and process.args : \"shadows*\"\n", + "query": "process where event.type in (\"start\", \"process_started\")\n and (process.name : \"vssadmin.exe\" or process.pe.original_file_name == \"VSSADMIN.EXE\") and\n process.args in (\"delete\", \"resize\") and process.args : \"shadows*\"\n", "risk_score": 73, "rule_id": "b5ea4bfe-a1b2-421f-9d47-22a75a6f2921", "severity": "high", @@ -42,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 10 + "version": 11 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts index eba920055a4f2..db98e7af01c3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -522,129 +522,148 @@ import rule509 from './persistence_periodic_tasks_file_mdofiy.json'; import rule510 from './persistence_via_atom_init_file_modification.json'; import rule511 from './privilege_escalation_lsa_auth_package.json'; import rule512 from './privilege_escalation_port_monitor_print_pocessor_abuse.json'; -import rule513 from './credential_access_dumping_hashes_bi_cmds.json'; -import rule514 from './lateral_movement_mounting_smb_share.json'; -import rule515 from './privilege_escalation_echo_nopasswd_sudoers.json'; -import rule516 from './privilege_escalation_ld_preload_shared_object_modif.json'; -import rule517 from './privilege_escalation_root_crontab_filemod.json'; -import rule518 from './defense_evasion_create_mod_root_certificate.json'; -import rule519 from './privilege_escalation_sudo_buffer_overflow.json'; -import rule520 from './execution_installer_spawned_network_event.json'; -import rule521 from './initial_access_suspicious_ms_exchange_files.json'; -import rule522 from './initial_access_suspicious_ms_exchange_process.json'; -import rule523 from './initial_access_suspicious_ms_exchange_worker_child_process.json'; -import rule524 from './persistence_evasion_registry_startup_shell_folder_modified.json'; -import rule525 from './persistence_local_scheduled_job_creation.json'; -import rule526 from './persistence_via_wmi_stdregprov_run_services.json'; -import rule527 from './credential_access_persistence_network_logon_provider_modification.json'; -import rule528 from './lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json'; -import rule529 from './collection_microsoft_365_new_inbox_rule.json'; -import rule530 from './ml_high_count_network_denies.json'; -import rule531 from './ml_high_count_network_events.json'; -import rule532 from './ml_rare_destination_country.json'; -import rule533 from './ml_spike_in_traffic_to_a_country.json'; -import rule534 from './command_and_control_tunneling_via_earthworm.json'; -import rule535 from './lateral_movement_evasion_rdp_shadowing.json'; -import rule536 from './threat_intel_fleet_integrations.json'; -import rule537 from './exfiltration_ec2_vm_export_failure.json'; -import rule538 from './exfiltration_ec2_full_network_packet_capture_detected.json'; -import rule539 from './impact_azure_service_principal_credentials_added.json'; -import rule540 from './persistence_ec2_security_group_configuration_change_detection.json'; -import rule541 from './defense_evasion_disabling_windows_logs.json'; -import rule542 from './persistence_route_53_domain_transfer_lock_disabled.json'; -import rule543 from './persistence_route_53_domain_transferred_to_another_account.json'; -import rule544 from './initial_access_okta_user_attempted_unauthorized_access.json'; -import rule545 from './credential_access_user_excessive_sso_logon_errors.json'; -import rule546 from './persistence_exchange_suspicious_mailbox_right_delegation.json'; -import rule547 from './privilege_escalation_new_or_modified_federation_domain.json'; -import rule548 from './privilege_escalation_sts_assumerole_usage.json'; -import rule549 from './privilege_escalation_sts_getsessiontoken_abuse.json'; -import rule550 from './defense_evasion_suspicious_execution_from_mounted_device.json'; -import rule551 from './defense_evasion_unusual_network_connection_via_dllhost.json'; -import rule552 from './defense_evasion_amsienable_key_mod.json'; -import rule553 from './impact_rds_group_deletion.json'; -import rule554 from './persistence_rds_group_creation.json'; -import rule555 from './persistence_route_table_created.json'; -import rule556 from './persistence_route_table_modified_or_deleted.json'; -import rule557 from './exfiltration_rds_snapshot_export.json'; -import rule558 from './persistence_rds_instance_creation.json'; -import rule559 from './privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json'; -import rule560 from './ml_auth_rare_hour_for_a_user_to_logon.json'; -import rule561 from './ml_auth_rare_source_ip_for_a_user.json'; -import rule562 from './ml_auth_rare_user_logon.json'; -import rule563 from './ml_auth_spike_in_failed_logon_events.json'; -import rule564 from './ml_auth_spike_in_logon_events.json'; -import rule565 from './ml_auth_spike_in_logon_events_from_a_source_ip.json'; -import rule566 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; -import rule567 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; -import rule568 from './defense_evasion_kubernetes_events_deleted.json'; -import rule569 from './impact_kubernetes_pod_deleted.json'; -import rule570 from './exfiltration_rds_snapshot_restored.json'; -import rule571 from './privilege_escalation_printspooler_malicious_driver_file_changes.json'; -import rule572 from './privilege_escalation_printspooler_malicious_registry_modification.json'; -import rule573 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; -import rule574 from './privilege_escalation_unusual_printspooler_childprocess.json'; -import rule575 from './defense_evasion_disabling_windows_defender_powershell.json'; -import rule576 from './defense_evasion_enable_network_discovery_with_netsh.json'; -import rule577 from './defense_evasion_execution_windefend_unusual_path.json'; -import rule578 from './defense_evasion_agent_spoofing_mismatched_id.json'; -import rule579 from './defense_evasion_agent_spoofing_multiple_hosts.json'; -import rule580 from './defense_evasion_parent_process_pid_spoofing.json'; -import rule581 from './impact_microsoft_365_potential_ransomware_activity.json'; -import rule582 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; -import rule583 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; -import rule584 from './defense_evasion_elasticache_security_group_creation.json'; -import rule585 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; -import rule586 from './impact_volume_shadow_copy_deletion_via_powershell.json'; -import rule587 from './persistence_route_53_hosted_zone_associated_with_a_vpc.json'; -import rule588 from './defense_evasion_defender_exclusion_via_powershell.json'; -import rule589 from './defense_evasion_dns_over_https_enabled.json'; -import rule590 from './defense_evasion_whitespace_padding_in_command_line.json'; -import rule591 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; -import rule592 from './credential_access_azure_full_network_packet_capture_detected.json'; -import rule593 from './persistence_webshell_detection.json'; -import rule594 from './defense_evasion_suppression_rule_created.json'; -import rule595 from './impact_efs_filesystem_or_mount_deleted.json'; -import rule596 from './defense_evasion_execution_control_panel_suspicious_args.json'; -import rule597 from './defense_evasion_azure_blob_permissions_modified.json'; -import rule598 from './privilege_escalation_aws_suspicious_saml_activity.json'; -import rule599 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; -import rule600 from './discovery_virtual_machine_fingerprinting_grep.json'; -import rule601 from './impact_backup_file_deletion.json'; -import rule602 from './credential_access_posh_minidump.json'; -import rule603 from './persistence_screensaver_engine_unexpected_child_process.json'; -import rule604 from './persistence_screensaver_plist_file_modification.json'; -import rule605 from './credential_access_suspicious_lsass_access_memdump.json'; -import rule606 from './defense_evasion_suspicious_process_access_direct_syscall.json'; -import rule607 from './discovery_posh_suspicious_api_functions.json'; -import rule608 from './privilege_escalation_via_rogue_named_pipe.json'; -import rule609 from './credential_access_suspicious_lsass_access_via_snapshot.json'; -import rule610 from './defense_evasion_posh_process_injection.json'; -import rule611 from './collection_posh_keylogger.json'; -import rule612 from './defense_evasion_posh_assembly_load.json'; -import rule613 from './defense_evasion_powershell_windows_firewall_disabled.json'; -import rule614 from './execution_posh_portable_executable.json'; -import rule615 from './execution_posh_psreflect.json'; -import rule616 from './credential_access_suspicious_comsvcs_imageload.json'; -import rule617 from './impact_aws_eventbridge_rule_disabled_or_deleted.json'; -import rule618 from './defense_evasion_microsoft_defender_tampering.json'; -import rule619 from './initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json'; -import rule620 from './persistence_remote_password_reset.json'; -import rule621 from './privilege_escalation_azure_kubernetes_rolebinding_created.json'; -import rule622 from './collection_posh_audio_capture.json'; -import rule623 from './collection_posh_screen_grabber.json'; -import rule624 from './defense_evasion_posh_compressed.json'; -import rule625 from './defense_evasion_suspicious_process_creation_calltrace.json'; -import rule626 from './privilege_escalation_group_policy_iniscript.json'; -import rule627 from './privilege_escalation_group_policy_privileged_groups.json'; -import rule628 from './privilege_escalation_group_policy_scheduled_task.json'; -import rule629 from './defense_evasion_clearing_windows_console_history.json'; -import rule630 from './threat_intel_filebeat8x.json'; -import rule631 from './privilege_escalation_installertakeover.json'; -import rule632 from './credential_access_via_snapshot_lsass_clone_creation.json'; -import rule633 from './persistence_via_bits_job_notify_command.json'; -import rule634 from './credential_access_symbolic_link_to_shadow_copy_createdcredential_access_symbolic_link_to_shadow_copy_created.json'; -import rule635 from './defense_evasion_microsoft_365_mailboxauditbypassassociation.json'; +import rule513 from './credential_access_posh_request_ticket.json'; +import rule514 from './credential_access_dumping_hashes_bi_cmds.json'; +import rule515 from './lateral_movement_mounting_smb_share.json'; +import rule516 from './privilege_escalation_echo_nopasswd_sudoers.json'; +import rule517 from './privilege_escalation_ld_preload_shared_object_modif.json'; +import rule518 from './privilege_escalation_root_crontab_filemod.json'; +import rule519 from './defense_evasion_create_mod_root_certificate.json'; +import rule520 from './privilege_escalation_sudo_buffer_overflow.json'; +import rule521 from './execution_installer_spawned_network_event.json'; +import rule522 from './initial_access_suspicious_ms_exchange_files.json'; +import rule523 from './initial_access_suspicious_ms_exchange_process.json'; +import rule524 from './initial_access_suspicious_ms_exchange_worker_child_process.json'; +import rule525 from './persistence_evasion_registry_startup_shell_folder_modified.json'; +import rule526 from './persistence_local_scheduled_job_creation.json'; +import rule527 from './persistence_via_wmi_stdregprov_run_services.json'; +import rule528 from './credential_access_persistence_network_logon_provider_modification.json'; +import rule529 from './lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json'; +import rule530 from './collection_microsoft_365_new_inbox_rule.json'; +import rule531 from './ml_high_count_network_denies.json'; +import rule532 from './ml_high_count_network_events.json'; +import rule533 from './ml_rare_destination_country.json'; +import rule534 from './ml_spike_in_traffic_to_a_country.json'; +import rule535 from './command_and_control_tunneling_via_earthworm.json'; +import rule536 from './lateral_movement_evasion_rdp_shadowing.json'; +import rule537 from './threat_intel_fleet_integrations.json'; +import rule538 from './exfiltration_ec2_vm_export_failure.json'; +import rule539 from './exfiltration_ec2_full_network_packet_capture_detected.json'; +import rule540 from './impact_azure_service_principal_credentials_added.json'; +import rule541 from './persistence_ec2_security_group_configuration_change_detection.json'; +import rule542 from './defense_evasion_disabling_windows_logs.json'; +import rule543 from './persistence_route_53_domain_transfer_lock_disabled.json'; +import rule544 from './persistence_route_53_domain_transferred_to_another_account.json'; +import rule545 from './initial_access_okta_user_attempted_unauthorized_access.json'; +import rule546 from './credential_access_user_excessive_sso_logon_errors.json'; +import rule547 from './persistence_exchange_suspicious_mailbox_right_delegation.json'; +import rule548 from './privilege_escalation_new_or_modified_federation_domain.json'; +import rule549 from './privilege_escalation_sts_assumerole_usage.json'; +import rule550 from './privilege_escalation_sts_getsessiontoken_abuse.json'; +import rule551 from './defense_evasion_suspicious_execution_from_mounted_device.json'; +import rule552 from './defense_evasion_unusual_network_connection_via_dllhost.json'; +import rule553 from './defense_evasion_amsienable_key_mod.json'; +import rule554 from './impact_rds_group_deletion.json'; +import rule555 from './persistence_rds_group_creation.json'; +import rule556 from './persistence_route_table_created.json'; +import rule557 from './persistence_route_table_modified_or_deleted.json'; +import rule558 from './exfiltration_rds_snapshot_export.json'; +import rule559 from './persistence_rds_instance_creation.json'; +import rule560 from './privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json'; +import rule561 from './ml_auth_rare_hour_for_a_user_to_logon.json'; +import rule562 from './ml_auth_rare_source_ip_for_a_user.json'; +import rule563 from './ml_auth_rare_user_logon.json'; +import rule564 from './ml_auth_spike_in_failed_logon_events.json'; +import rule565 from './ml_auth_spike_in_logon_events.json'; +import rule566 from './ml_auth_spike_in_logon_events_from_a_source_ip.json'; +import rule567 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; +import rule568 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; +import rule569 from './defense_evasion_kubernetes_events_deleted.json'; +import rule570 from './impact_kubernetes_pod_deleted.json'; +import rule571 from './exfiltration_rds_snapshot_restored.json'; +import rule572 from './privilege_escalation_printspooler_malicious_driver_file_changes.json'; +import rule573 from './privilege_escalation_printspooler_malicious_registry_modification.json'; +import rule574 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; +import rule575 from './privilege_escalation_unusual_printspooler_childprocess.json'; +import rule576 from './defense_evasion_disabling_windows_defender_powershell.json'; +import rule577 from './defense_evasion_enable_network_discovery_with_netsh.json'; +import rule578 from './defense_evasion_execution_windefend_unusual_path.json'; +import rule579 from './defense_evasion_agent_spoofing_mismatched_id.json'; +import rule580 from './defense_evasion_agent_spoofing_multiple_hosts.json'; +import rule581 from './defense_evasion_parent_process_pid_spoofing.json'; +import rule582 from './impact_microsoft_365_potential_ransomware_activity.json'; +import rule583 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; +import rule584 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; +import rule585 from './defense_evasion_elasticache_security_group_creation.json'; +import rule586 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; +import rule587 from './impact_volume_shadow_copy_deletion_via_powershell.json'; +import rule588 from './persistence_route_53_hosted_zone_associated_with_a_vpc.json'; +import rule589 from './defense_evasion_defender_exclusion_via_powershell.json'; +import rule590 from './defense_evasion_dns_over_https_enabled.json'; +import rule591 from './defense_evasion_whitespace_padding_in_command_line.json'; +import rule592 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; +import rule593 from './credential_access_azure_full_network_packet_capture_detected.json'; +import rule594 from './persistence_webshell_detection.json'; +import rule595 from './defense_evasion_suppression_rule_created.json'; +import rule596 from './impact_efs_filesystem_or_mount_deleted.json'; +import rule597 from './defense_evasion_execution_control_panel_suspicious_args.json'; +import rule598 from './defense_evasion_azure_blob_permissions_modified.json'; +import rule599 from './privilege_escalation_aws_suspicious_saml_activity.json'; +import rule600 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; +import rule601 from './discovery_virtual_machine_fingerprinting_grep.json'; +import rule602 from './impact_backup_file_deletion.json'; +import rule603 from './credential_access_posh_minidump.json'; +import rule604 from './persistence_screensaver_engine_unexpected_child_process.json'; +import rule605 from './persistence_screensaver_plist_file_modification.json'; +import rule606 from './credential_access_suspicious_lsass_access_memdump.json'; +import rule607 from './defense_evasion_suspicious_process_access_direct_syscall.json'; +import rule608 from './discovery_posh_suspicious_api_functions.json'; +import rule609 from './privilege_escalation_via_rogue_named_pipe.json'; +import rule610 from './credential_access_suspicious_lsass_access_via_snapshot.json'; +import rule611 from './defense_evasion_posh_process_injection.json'; +import rule612 from './collection_posh_keylogger.json'; +import rule613 from './defense_evasion_posh_assembly_load.json'; +import rule614 from './defense_evasion_powershell_windows_firewall_disabled.json'; +import rule615 from './execution_posh_portable_executable.json'; +import rule616 from './execution_posh_psreflect.json'; +import rule617 from './credential_access_suspicious_comsvcs_imageload.json'; +import rule618 from './impact_aws_eventbridge_rule_disabled_or_deleted.json'; +import rule619 from './defense_evasion_microsoft_defender_tampering.json'; +import rule620 from './initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json'; +import rule621 from './persistence_remote_password_reset.json'; +import rule622 from './privilege_escalation_azure_kubernetes_rolebinding_created.json'; +import rule623 from './collection_posh_audio_capture.json'; +import rule624 from './collection_posh_screen_grabber.json'; +import rule625 from './defense_evasion_posh_compressed.json'; +import rule626 from './defense_evasion_suspicious_process_creation_calltrace.json'; +import rule627 from './privilege_escalation_group_policy_iniscript.json'; +import rule628 from './privilege_escalation_group_policy_privileged_groups.json'; +import rule629 from './privilege_escalation_group_policy_scheduled_task.json'; +import rule630 from './defense_evasion_clearing_windows_console_history.json'; +import rule631 from './threat_intel_filebeat8x.json'; +import rule632 from './privilege_escalation_installertakeover.json'; +import rule633 from './credential_access_via_snapshot_lsass_clone_creation.json'; +import rule634 from './persistence_via_bits_job_notify_command.json'; +import rule635 from './execution_suspicious_java_netcon_childproc.json'; +import rule636 from './privilege_escalation_samaccountname_spoofing_attack.json'; +import rule637 from './credential_access_symbolic_link_to_shadow_copy_createdcredential_access_symbolic_link_to_shadow_copy_created.json'; +import rule638 from './credential_access_mfa_push_brute_force.json'; +import rule639 from './persistence_azure_global_administrator_role_assigned.json'; +import rule640 from './persistence_microsoft_365_global_administrator_role_assign.json'; +import rule641 from './lateral_movement_malware_uploaded_onedrive.json'; +import rule642 from './lateral_movement_malware_uploaded_sharepoint.json'; +import rule643 from './defense_evasion_ms_office_suspicious_regmod.json'; +import rule644 from './initial_access_o365_user_reported_phish_malware.json'; +import rule645 from './defense_evasion_microsoft_365_mailboxauditbypassassociation.json'; +import rule646 from './credential_access_disable_kerberos_preauth.json'; +import rule647 from './credential_access_shadow_credentials.json'; +import rule648 from './privilege_escalation_pkexec_envar_hijack.json'; +import rule649 from './credential_access_seenabledelegationprivilege_assigned_to_user.json'; +import rule650 from './persistence_msds_alloweddelegateto_krbtgt.json'; +import rule651 from './defense_evasion_disable_posh_scriptblocklogging.json'; +import rule652 from './persistence_ad_adminsdholder.json'; +import rule653 from './privilege_escalation_windows_service_via_unusual_client.json'; +import rule654 from './credential_access_dcsync_replication_rights.json'; export const rawRules = [ rule1, @@ -1282,4 +1301,23 @@ export const rawRules = [ rule633, rule634, rule635, + rule636, + rule637, + rule638, + rule639, + rule640, + rule641, + rule642, + rule643, + rule644, + rule645, + rule646, + rule647, + rule648, + rule649, + rule650, + rule651, + rule652, + rule653, + rule654, ]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json index 7cd94103c6395..2624371aa5a62 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json @@ -4,7 +4,7 @@ ], "description": "Identifies an Identity and Access Management (IAM) custom role creation in Google Cloud Platform (GCP). Custom roles are user-defined, and allow for the bundling of one or more supported permissions to meet specific needs. Custom roles will not be updated automatically and could lead to privilege creep if not carefully scrutinized.", "false_positives": [ - "Custom role creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Role creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Custom role creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Role creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -63,5 +63,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json index d98c6d1fb2260..defd5ce4da497 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Anti-Phish Policy Deletion", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-AntiPhishPolicy\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-antiphishpolicy?view=exchange-ps", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json index 554f5f80be246..9a14545f39745 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Anti-Phish Rule Modification", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-AntiPhishRule\" or \"Disable-AntiPhishRule\") and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-antiphishrule?view=exchange-ps", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json index 0f936a91023f2..eafb3b71584e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Safe Link Policy Disabled", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Disable-SafeLinksRule\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/disable-safelinksrule?view=exchange-ps", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json index 31950fc345c0e..ce2a713a5c7d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 User Restricted from Sending Email", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", "query": "event.dataset:o365.audit and event.provider:SecurityComplianceCenter and event.category:web and event.action:\"User restricted from sending email\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/cloud-app-security/anomaly-detection-policy", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_o365_user_reported_phish_malware.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_o365_user_reported_phish_malware.json new file mode 100644 index 0000000000000..8ceca899412b2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_o365_user_reported_phish_malware.json @@ -0,0 +1,65 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detects the occurrence of emails reported as Phishing or Malware by Users. Security Awareness training is essential to stay ahead of scammers and threat actors, as security products can be bypassed, and the user can still receive a malicious message. Educating users to report suspicious messages can help identify gaps in security controls and prevent malware infections and Business Email Compromise attacks.", + "false_positives": [ + "Legitimate files reported by the users" + ], + "from": "now-30m", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "O365 Email Reported by User as Malware or Phish", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:SecurityComplianceCenter and event.action:AlertTriggered and rule.name:\"Email reported by user as malware or phish\"\n", + "references": [ + "https://support.microsoft.com/en-us/office/use-the-report-message-add-in-b5caa9f1-cdf3-4443-af8c-ff724ea719d2?ui=en-us&rs=en-us&ad=us" + ], + "risk_score": 47, + "rule_id": "5930658c-2107-4afc-91af-e0e55b7f7184", + "severity": "medium", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1566", + "name": "Phishing", + "reference": "https://attack.mitre.org/techniques/T1566/", + "subtechnique": [ + { + "id": "T1566.001", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1566/001/" + }, + { + "id": "T1566.002", + "name": "Spearphishing Link", + "reference": "https://attack.mitre.org/techniques/T1566/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json index 222d30723bc9e..d016add9637e3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json @@ -3,7 +3,7 @@ "Elastic", "Austin Songer" ], - "description": "Identifies when an unauthorized access attempt is made by a user for an Okta application.", + "description": "Identifies unauthorized access attempts to Okta applications.", "index": [ "filebeat-*", "logs-okta*" @@ -70,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json index 1ccabf42237ef..25b6e42b2312f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json @@ -5,9 +5,7 @@ "description": "Identifies NullSessionPipe registry modifications that specify which pipes can be accessed anonymously. This could be indicative of adversary lateral movement preparation by making the added pipe available to everyone.", "from": "now-9m", "index": [ - "logs-endpoint.events.*", - "winlogbeat-*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -53,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json index 6e11258e23d00..ca6484afd077c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json @@ -13,7 +13,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Abnormally Large DNS Response", - "note": "## Triage and analysis\n\n### Investigating Large DNS Responses\nDetection alerts from this rule indicate possible anomalous activity around large byte DNS responses from a Windows DNS\nserver. This detection rule was created based on activity represented in exploitation of vulnerability (CVE-2020-1350)\nalso known as [SigRed](https://www.elastic.co/blog/detection-rules-for-sigred-vulnerability) during July 2020.\n\n#### Possible investigation steps:\n- This specific rule is sourced from network log activity such as DNS or network level data. It's important to validate\nthe source of the incoming traffic and determine if this activity has been observed previously within an environment.\n- Activity can be further investigated and validated by reviewing available corresponding Intrusion Detection Signatures (IDS) alerts associated with activity.\n- Further examination can be made by reviewing the `dns.question_type` network fieldset with a protocol analyzer, such as Zeek, Packetbeat, or Suricata, for `SIG` or `RRSIG` data.\n- Validate the patch level and OS of the targeted DNS server to validate the observed activity was not large-scale Internet vulnerability scanning.\n- Validate that the source of the network activity was not from an authorized vulnerability scan or compromise assessment.\n\n#### False Positive Analysis\n- Based on this rule which looks for a threshold of 60k bytes, it is possible for activity to be generated under 65k bytes\nand related to legitimate behavior. In packet capture files received by the [SANS Internet Storm Center](https://isc.sans.edu/forums/diary/PATCH+NOW+SIGRed+CVE20201350+Microsoft+DNS+Server+Vulnerability/26356/), byte responses\nwere all observed as greater than 65k bytes.\n- This activity has the ability to be triggered from compliance/vulnerability scanning or compromise assessment, it's\nimportant to determine the source of the activity and potential whitelist the source host\n\n\n### Related Rules\n- Unusual Child Process of dns.exe\n- Unusual File Modification by dns.exe\n\n### Response and Remediation\n- Review and implement the above detection logic within your environment using technology such as Endpoint security, Winlogbeat, Packetbeat, or network security monitoring (NSM) platforms such as Zeek or Suricata.\n- Ensure that you have deployed the latest Microsoft [Security Update](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1350) (Monthly Rollup or Security Only) and restart the\npatched machines. If unable to patch immediately: Microsoft [released](https://support.microsoft.com/en-us/help/4569509/windows-dns-server-remote-code-execution-vulnerability) a registry-based workaround that doesn\u2019t require a\nrestart. This can be used as a temporary solution before the patch is applied.\n- Maintain backups of your critical systems to aid in quick recovery.\n- Perform routine vulnerability scans of your systems, monitor [CISA advisories](https://us-cert.cisa.gov/ncas/current-activity) and patch identified vulnerabilities.\n- If observed true positive activity, implement a remediation plan and monitor host-based artifacts for additional post-exploitation behavior.\n", + "note": "## Triage and analysis\n\n### Investigating Large DNS Responses\nDetection alerts from this rule indicate possible anomalous activity around large byte DNS responses from a Windows DNS\nserver. This detection rule was created based on activity represented in exploitation of vulnerability (CVE-2020-1350)\nalso known as [SigRed](https://www.elastic.co/blog/detection-rules-for-sigred-vulnerability) during July 2020.\n\n#### Possible investigation steps:\n- This specific rule is sourced from network log activity such as DNS or network level data. It's important to validate\nthe source of the incoming traffic and determine if this activity has been observed previously within an environment.\n- Activity can be further investigated and validated by reviewing available corresponding Intrusion Detection Signatures (IDS) alerts associated with activity.\n- Further examination can include a review of the `dns.question_type` network fieldset with a protocol analyzer, such as Zeek, Packetbeat, or Suricata, for `SIG` or `RRSIG` data.\n- Validate the patch level and OS of the targeted DNS server to validate the observed activity was not large-scale Internet vulnerability scanning.\n- Validate that the source of the network activity was not from an authorized vulnerability scan or compromise assessment.\n\n#### False Positive Analysis\n- Based on this rule which looks for a threshold of 60k bytes, it is possible for activity to be generated under 65k bytes\nand related to legitimate behavior. In packet capture files received by the [SANS Internet Storm Center](https://isc.sans.edu/forums/diary/PATCH+NOW+SIGRed+CVE20201350+Microsoft+DNS+Server+Vulnerability/26356/), byte responses\nwere all observed as greater than 65k bytes.\n- This activity can be triggered by compliance/vulnerability scanning or compromise assessment, it's\nimportant to determine the source of the activity and potentially allowlist the source host.\n\n\n### Related Rules\n- Unusual Child Process of dns.exe\n- Unusual File Modification by dns.exe\n\n### Response and Remediation\n- Review and implement the above detection logic within your environment using technology such as Endpoint security, Winlogbeat, Packetbeat, or network security monitoring (NSM) platforms such as Zeek or Suricata.\n- Ensure that you have deployed the latest Microsoft [Security Update](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1350) (Monthly Rollup or Security Only) and restart the\npatched machines. If unable to patch immediately: Microsoft [released](https://support.microsoft.com/en-us/help/4569509/windows-dns-server-remote-code-execution-vulnerability) a registry-based workaround that doesn\u2019t require a\nrestart. This can be used as a temporary solution before the patch is applied.\n- Maintain backups of your critical systems to aid in quick recovery.\n- Perform routine vulnerability scans of your systems, monitor [CISA advisories](https://us-cert.cisa.gov/ncas/current-activity) and patch identified vulnerabilities.\n- If you observe a true positive, implement a remediation plan and monitor host-based artifacts for additional post-exploitation behavior.\n", "query": "event.category:(network or network_traffic) and destination.port:53 and\n (event.dataset:zeek.dns or type:dns or event.type:connection) and network.bytes > 60000\n", "references": [ "https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_onedrive.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_onedrive.json new file mode 100644 index 0000000000000..37f2066e7b9b4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_onedrive.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the occurence of files uploaded to OneDrive being detected as Malware by the file scanning engine. Attackers can use File Sharing and Organization Repositories to spread laterally within the company and amplify their access. Users can inadvertently share these files without knowing their maliciousness, giving adversaries opportunity to gain initial access to other endpoints in the environment.", + "false_positives": [ + "Benign files can trigger signatures in the built-in virus protection" + ], + "from": "now-30m", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "OneDrive Malware File Upload", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:OneDrive and event.code:SharePointFileOperation and event.action:FileMalwareDetected\n", + "references": [ + "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/virus-detection-in-spo?view=o365-worldwide" + ], + "risk_score": 73, + "rule_id": "bba1b212-b85c-41c6-9b28-be0e5cdfc9b1", + "severity": "high", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1080", + "name": "Taint Shared Content", + "reference": "https://attack.mitre.org/techniques/T1080/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_sharepoint.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_sharepoint.json new file mode 100644 index 0000000000000..52b06e00d68f6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_sharepoint.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the occurence of files uploaded to SharePoint being detected as Malware by the file scanning engine. Attackers can use File Sharing and Organization Repositories to spread laterally within the company and amplify their access. Users can inadvertently share these files without knowing their maliciousness, giving adversaries opportunity to gain initial access to other endpoints in the environment.", + "false_positives": [ + "Benign files can trigger signatures in the built-in virus protection" + ], + "from": "now-30m", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "SharePoint Malware File Upload", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:SharePoint and event.code:SharePointFileOperation and event.action:FileMalwareDetected\n", + "references": [ + "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/virus-detection-in-spo?view=o365-worldwide" + ], + "risk_score": 73, + "rule_id": "0e52157a-8e96-4a95-a6e3-5faae5081a74", + "severity": "high", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1080", + "name": "Taint Shared Content", + "reference": "https://attack.mitre.org/techniques/T1080/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json index 584f24cfb30f3..46a3a25877b5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "RDP Enabled via Registry", - "query": "registry where\nregistry.path : \"HKLM\\\\SYSTEM\\\\ControlSet*\\\\Control\\\\Terminal Server\\\\fDenyTSConnections\" and\nregistry.data.strings == \"0\" and not (process.name : \"svchost.exe\" and user.domain == \"NT AUTHORITY\") and\nnot process.executable : \"C:\\\\Windows\\\\System32\\\\SystemPropertiesRemote.exe\"\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Terminal Server\\\\fDenyTSConnections\" and\n registry.data.strings : (\"0\", \"0x00000000\") and not (process.name : \"svchost.exe\" and user.domain == \"NT AUTHORITY\") and\n not process.executable : \"C:\\\\Windows\\\\System32\\\\SystemPropertiesRemote.exe\"\n", "risk_score": 47, "rule_id": "58aa72ca-d968-4f34-b9f7-bea51d75eb50", "severity": "medium", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json index e5bfc3242be34..c4001b6ed88cf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json @@ -5,9 +5,7 @@ "description": "Identifies potential behavior of SharpRDP, which is a tool that can be used to perform authenticated command execution against a remote target via Remote Desktop Protocol (RDP) for the purposes of lateral movement.", "from": "now-9m", "index": [ - "logs-endpoint.events.*", - "winlogbeat-*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -52,5 +50,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json index 710f08ce5213a..95170577bc8c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote Scheduled Task Creation", - "note": "## Triage and analysis\n\n### Investigating Creation of Remote Scheduled Tasks\n\n[Scheduled tasks](https://docs.microsoft.com/en-us/windows/win32/taskschd/about-the-task-scheduler) are a great mechanism used for persistence and executing programs. These features can\nbe used remotely for a variety of legitimate reasons, but at the same time used by malware and adversaries.\nWhen investigating scheduled tasks that have been set-up remotely, one of the first methods should be determining the\noriginal intent behind the configuration and verify if the activity is tied to benign behavior such as software installations or any kind\nof network administrator work. One objective for these alerts is to understand the configured action within the scheduled\ntask, this is captured within the registry event data for this rule and can be base64 decoded to view the value.\n\n#### Possible investigation steps:\n- Review the base64 encoded tasks actions registry value to investigate the task configured action.\n- Determine if task is related to legitimate or benign behavior based on the corresponding process or program tied to the\nscheduled task.\n- Further examination should include both the source and target machines where host-based artifacts and network logs\nshould be reviewed further around the time window of the creation of the scheduled task.\n\n### False Positive Analysis\n- There is a high possibility of benign activity tied to the creation of remote scheduled tasks as it is a general feature\nwithin Windows and used for legitimate purposes for a wide range of activity. Any kind of context should be found to\nfurther understand the source of the activity and determine the intent based on the scheduled task contents.\n\n### Related Rules\n- Service Command Lateral Movement\n- Remotely Started Services via RPC\n\n### Response and Remediation\n- This behavior represents post-exploitation actions such as persistence or lateral movement, immediate response should\nbe taken to review and investigate the activity and potentially isolate involved machines to prevent further post-compromise\nbehavior.\n- Remove scheduled task and any other related artifacts to the activity.\n- Review privileged account management and user account management settings such as implementing GPO policies to further\nrestrict activity or configure settings that only allow Administrators to create remote scheduled tasks.\n", + "note": "## Triage and analysis\n\n### Investigating Creation of Remote Scheduled Tasks\n\n[Scheduled tasks](https://docs.microsoft.com/en-us/windows/win32/taskschd/about-the-task-scheduler) are a great\nmechanism for persistence and program execution. These features can\nbe used remotely for a variety of legitimate reasons, but at the same time used by malware and adversaries.\nWhen investigating scheduled tasks that were set up remotely, one of the first steps should be to determine the\noriginal intent behind the configuration and to verify if the activity is tied to benign behavior such as software installation or any kind\nof network administrator work. One objective for these alerts is to understand the configured action within the scheduled\ntask. This is captured within the registry event data for this rule and can be base64 decoded to view the value.\n\n#### Possible investigation steps:\n- Review the base64 encoded tasks actions registry value to investigate the task configured action.\n- Determine if task is related to legitimate or benign behavior based on the corresponding process or program tied to the\nscheduled task.\n- Further examination should include both the source and target machines where host-based artifacts and network logs\nshould be reviewed further around the time window of the creation of the scheduled task.\n\n### False Positive Analysis\n- There is a high possibility of benign activity tied to the creation of remote scheduled tasks as it is a general feature\nwithin Windows and used for legitimate purposes for a wide range of activity. Any kind of context should be found to\nfurther understand the source of the activity and determine the intent based on the scheduled task contents.\n\n### Related Rules\n- Service Command Lateral Movement\n- Remotely Started Services via RPC\n\n### Response and Remediation\n- This behavior represents post-exploitation actions such as persistence or lateral movement, immediately review and\ninvestigate the activity and potentially isolate involved machines to prevent further post-compromise\nbehavior.\n- Remove scheduled task and any other related artifacts to the activity.\n- Review privileged account management and user account management settings such as implementing GPO policies to further\nrestrict activity or configure settings that only allow Administrators to create remote scheduled tasks.\n", "query": "/* Task Scheduler service incoming connection followed by TaskCache registry modification */\n\nsequence by host.id, process.entity_id with maxspan = 1m\n [network where process.name : \"svchost.exe\" and\n network.direction : (\"incoming\", \"ingress\") and source.port >= 49152 and destination.port >= 49152 and\n source.ip != \"127.0.0.1\" and source.ip != \"::1\"\n ]\n [registry where registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\TaskCache\\\\Tasks\\\\*\\\\Actions\"]\n", "risk_score": 47, "rule_id": "954ee7c8-5437-49ae-b2d6-2960883898e9", @@ -64,5 +64,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json index 61234f392158f..3cf70a8f26739 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json @@ -5,14 +5,14 @@ "description": "Identifies Service Control (sc.exe) spawning from script interpreter processes to create, modify, or start services. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.", "from": "now-9m", "index": [ - "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-system.*", + "winlogbeat-*" ], "language": "eql", "license": "Elastic License v2", "name": "Service Control Spawned via Script Interpreter", - "query": "process where event.type == \"start\" and\n (process.name : \"sc.exe\" or process.pe.original_file_name == \"sc.exe\") and\n process.parent.name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\",\n \"wmic.exe\", \"mshta.exe\",\"powershell.exe\", \"pwsh.exe\") and\n process.args:(\"config\", \"create\", \"start\", \"delete\", \"stop\", \"pause\") and\n /* exclude SYSTEM SID - look for service creations by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", + "query": "/* This rule is not compatible with Sysmon due to user.id issues */\n\nprocess where event.type == \"start\" and\n (process.name : \"sc.exe\" or process.pe.original_file_name == \"sc.exe\") and\n process.parent.name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\",\n \"wmic.exe\", \"mshta.exe\",\"powershell.exe\", \"pwsh.exe\") and\n process.args:(\"config\", \"create\", \"start\", \"delete\", \"stop\", \"pause\") and\n /* exclude SYSTEM SID - look for service creations by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", "risk_score": 21, "rule_id": "e8571d5f-bea1-46c2-9f56-998de2d3ed95", "severity": "low", @@ -42,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json index 6933a81a22944..6c8b7805bbb06 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange DKIM Signing Configuration Disabled", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Set-DkimSigningConfig\" and o365.audit.Parameters.Enabled:False and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/set-dkimsigningconfig?view=exchange-ps" @@ -32,5 +32,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json index add3495d03271..c2074329fb5ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Teams Custom Application Interaction Allowed", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:MicrosoftTeams and\nevent.category:web and event.action:TeamsTenantSettingChanged and\no365.audit.Name:\"Allow sideloading and interaction of custom apps\" and\no365.audit.NewValue:True and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload" @@ -32,5 +32,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json index 1b64f1d85301a..a751087df9676 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "machine_learning_job_id": "high_distinct_count_error_message", "name": "Spike in AWS Error Messages", - "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating Spikes in CloudTrail Errors\n\nCloudTrail logging provides visibility on actions taken within an AWS environment. By monitoring these events and understanding\nwhat is considered normal behavior within an organization, suspicious or malicious activity can be spotted when deviations\nare observed. This example rule triggers from a large spike in the number of CloudTrail log messages that contain a\nparticular error message. The error message in question was associated with the response to an AWS API command or method call,\nthis has the potential to uncover unknown threats or activity.\n\n#### Possible investigation steps:\n- Examine the history of the error. Has it manifested before? If the error, which is visible in the `aws.cloudtrail.error_message` field, only manifested recently, it might be related to recent changes in an automation module or script.\n- Examine the request parameters. These may provide indications as to the nature of the task being performed when the error occurred. Is the error related to unsuccessful attempts to enumerate or access objects, data, or secrets? If so, this can sometimes be a byproduct of discovery, privilege escalation or lateral movement attempts.\n- Consider the user as identified by the `user.name field`. Is this activity part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n\n### False Positive Analysis\n- This rule has the possibility to produce false positives based on unexpected activity occurring such as bugs or recent\nchanges to automation modules or scripting.\n- Adoption of new services or implementing new functionality to scripts may generate false positives\n\n### Related Rules\n- Unusual AWS Command for a User\n- Rare AWS Error Code\n\n### Response and Remediation\n- If activity is observed as suspicious or malicious, immediate response should be looked into rotating and deleting AWS IAM access keys\n- Validate if any unauthorized new users were created, remove these accounts and request password resets for other IAM users\n- Look into enabling multi-factor authentication for users\n- Follow security best practices [outlined](https://aws.amazon.com/premiumsupport/knowledge-center/security-best-practices/) by AWS\n", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating Spikes in CloudTrail Errors\n\nCloudTrail logging provides visibility on actions taken within an AWS environment. By monitoring these events and understanding\nwhat is considered normal behavior within an organization, suspicious or malicious activity can be spotted when deviations\nare observed. This example rule triggers from a large spike in the number of CloudTrail log messages that contain a\nparticular error message. The error message in question was associated with the response to an AWS API command or method call,\nthis has the potential to uncover unknown threats or activity.\n\n#### Possible investigation steps:\n- Examine the history of the error. Has it manifested before? If the error, which is visible in the `aws.cloudtrail.error_message` field, only manifested recently, it might be related to recent changes in an automation module or script.\n- Examine the request parameters. These may provide indications as to the nature of the task being performed when the error occurred. Is the error related to unsuccessful attempts to enumerate or access objects, data, or secrets? If so, this can sometimes be a byproduct of discovery, privilege escalation or lateral movement attempts.\n- Consider the user as identified by the `user.name field`. Is this activity part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n\n### False Positive Analysis\n- This rule has the possibility to produce false positives based on unexpected activity occurring such as bugs or recent\nchanges to automation modules or scripting.\n- The adoption of new services or the addition of new functionality to scripts may generate false positives.\n\n### Related Rules\n- Unusual AWS Command for a User\n- Rare AWS Error Code\n\n### Response and Remediation\n- If suspicious or malicious activity is observed, immediately rotate and delete relevant AWS IAM access keys\n- Validate if any unauthorized new users were created, remove these accounts and request password resets for other IAM users\n- Look into enabling multi-factor authentication for users\n- Follow security best practices [outlined](https://aws.amazon.com/premiumsupport/knowledge-center/security-best-practices/) by AWS\n", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -26,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json index 4576b080e1ea6..a9483c3f981c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "machine_learning_job_id": "rare_method_for_a_country", "name": "Unusual Country For an AWS Command", - "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual Country For an AWS Command\n\nCloudTrail logging provides visibility on actions taken within an AWS environment. By monitoring these events and understanding\nwhat is considered normal behavior within an organization, suspicious or malicious activity can be spotted when deviations\nare observed. This example rule focuses on AWS command activity where the country from the source of the activity has been\nconsidered unusual based on previous history.\n\n#### Possible investigation steps:\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n\n### False Positive Analysis\n- False positives can occur if activity is coming from new employees based in a country with no previous history in AWS,\ntherefore it's important to validate the activity listed in the investigation steps above.\n\n### Related Rules\n- Unusual City For an AWS Command\n- Unusual AWS Command for a User\n- Rare AWS Error Code\n\n### Response and Remediation\n- If activity is observed as suspicious or malicious, immediate response should be looked into rotating and deleting AWS IAM access keys\n- Validate if any unauthorized new users were created, remove these accounts and request password resets for other IAM users\n- Look into enabling multi-factor authentication for users\n- Follow security best practices [outlined](https://aws.amazon.com/premiumsupport/knowledge-center/security-best-practices/) by AWS\n", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual Country For an AWS Command\n\nCloudTrail logging provides visibility on actions taken within an AWS environment. By monitoring these events and understanding\nwhat is considered normal behavior within an organization, suspicious or malicious activity can be spotted when deviations\nare observed. This example rule focuses on AWS command activity where the country from the source of the activity has been\nconsidered unusual based on previous history.\n\n#### Possible investigation steps:\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n\n### False Positive Analysis\n- False positives can occur if activity is coming from new employees based in a country with no previous history in AWS,\ntherefore it's important to validate the activity listed in the investigation steps above.\n\n### Related Rules\n- Unusual City For an AWS Command\n- Unusual AWS Command for a User\n- Rare AWS Error Code\n\n### Response and Remediation\n- If suspicious or malicious activity is observed, immediately rotate and delete relevant AWS IAM access keys\n- Validate if any unauthorized new users were created, remove these accounts and request password resets for other IAM users\n- Look into enabling multi-factor authentication for users\n- Follow security best practices [outlined](https://aws.amazon.com/premiumsupport/knowledge-center/security-best-practices/) by AWS\n", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -26,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json index d8bf26884b16f..85b3ec0d846e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json @@ -15,7 +15,7 @@ "v2_rare_process_by_host_windows_ecs" ], "name": "Unusual Process For a Windows Host", - "note": "## Triage and analysis\n\n### Investigating an Unusual Windows Process\n\nSearching for abnormal Windows processes is a good methodology to find potentially malicious activity within a network.\nBy understanding what is commonly run within an environment and developing baselines for legitimate activity can help\nuncover potential malware and suspicious behaviors.\n\n#### Possible investigation steps:\n- Consider the user as identified by the `user.name` field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process only manifested recently, it might be part of a new software package. If it has a consistent cadence (for example if it runs monthly or quarterly), it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package.\n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools.\n\n### False Positive Analysis\n- Validate the unusual Windows process is not related to new benign software installation activity. If related to\nlegitimate software, this can be done by leveraging the exception workflow in the Kibana Security App or Elasticsearch\nAPI to tune this rule to your environment\n- Try to understand the context of the execution by thinking about the user, machine, or business purpose. It's possible that a small number of endpoints\nsuch as servers that have very unique software that might appear to be unusual, but satisfy a specific business need.\n\n### Related Rules\n- Anomalous Windows Process Creation\n- Unusual Windows Path Activity\n- Unusual Windows Process Calling the Metadata Service\n\n### Response and Remediation\n- This rule is related to process execution events and should be immediately reviewed and investigated to determine if malicious\n- Based on validation and if malicious, the impacted machine should be isolated and analyzed to determine other post-compromise\nbehavior such as setting up persistence or performing lateral movement.\n- Look into preventive measures such as Windows Defender Application Control and AppLocker to gain better control on\nwhat is allowed to run on Windows infrastructure.\n", + "note": "## Triage and analysis\n\n### Investigating an Unusual Windows Process\n\nSearching for abnormal Windows processes is a good methodology to find potentially malicious activity within a network.\nUnderstanding what is commonly run within an environment and developing baselines for legitimate activity can help\nuncover potential malware and suspicious behaviors.\n\n#### Possible investigation steps:\n- Consider the user as identified by the `user.name` field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process only manifested recently, it might be part of a new software package. If it has a consistent cadence (for example if it runs monthly or quarterly), it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package.\n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools.\n\n### False Positive Analysis\n- Validate the unusual Windows process is not related to new benign software installation activity. If related to\nlegitimate software, this can be done by leveraging the exception workflow in the Kibana Security App or Elasticsearch\nAPI to tune this rule to your environment\n- Try to understand the context of the execution by thinking about the user, machine, or business purpose. It's possible that a small number of endpoints\nsuch as servers that have very unique software that might appear to be unusual, but satisfy a specific business need.\n\n### Related Rules\n- Anomalous Windows Process Creation\n- Unusual Windows Path Activity\n- Unusual Windows Process Calling the Metadata Service\n\n### Response and Remediation\n- This rule is related to process execution events and should be immediately reviewed and investigated to determine if malicious.\n- Based on validation and if malicious, the impacted machine should be isolated and analyzed to determine other post-compromise\nbehavior such as setting up persistence or performing lateral movement.\n- Look into preventive measures such as Windows Defender Application Control and AppLocker to gain better control on\nwhat is allowed to run on Windows infrastructure.\n", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -30,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ad_adminsdholder.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ad_adminsdholder.json new file mode 100644 index 0000000000000..2aa6e71b4f845 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ad_adminsdholder.json @@ -0,0 +1,45 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detects modifications in the AdminSDHolder object. Attackers can abuse the SDProp process to implement a persistent backdoor in Active Directory. SDProp compares the permissions on protected objects with those defined on the AdminSDHolder object. If the permissions on any of the protected accounts and groups do not match, the permissions on the protected accounts and groups are reset to match those of the domain's AdminSDHolder object, regaining their Administrative Privileges.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*", + "logs-system.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "AdminSDHolder Backdoor", + "query": "event.action:\"Directory Service Changes\" and event.code:5136 and winlog.event_data.ObjectDN:CN=AdminSDHolder,CN=System*\n", + "references": [ + "https://adsecurity.org/?p=1906", + "https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory#adminsdholder" + ], + "risk_score": 73, + "rule_id": "6e9130a5-9be6-48e5-943a-9628bfc74b18", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence", + "Active Directory" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json index a154bc4e60bfb..dee8ce22bed2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Registry Persistence via AppCert DLL", - "query": "registry where\n/* uncomment once stable length(bytes_written_string) > 0 and */\n registry.path : \"HKLM\\\\SYSTEM\\\\ControlSet*\\\\Control\\\\Session Manager\\\\AppCertDLLs\\\\*\"\n", + "query": "registry where\n/* uncomment once stable length(bytes_written_string) > 0 and */\n registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Session Manager\\\\AppCertDLLs\\\\*\"\n", "risk_score": 47, "rule_id": "513f0ffd-b317-4b9c-9494-92ce861f22c7", "severity": "medium", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_global_administrator_role_assigned.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_global_administrator_role_assigned.json new file mode 100644 index 0000000000000..c5420012e7a39 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_global_administrator_role_assigned.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "In Azure Active Directory (Azure AD), permissions to manage resources are assigned using Roles. The Global Administrator is a role that enables users to have access to all administrative features in Azure AD and services that use Azure Active Directory identities like the Microsoft 365 Defender portal, the Microsoft 365 compliance center, Exchange, SharePoint Online, and Skype for Business Online. Attackers can add users as Global Administrators to maintain access and manage all subscriptions and their settings and resources.", + "from": "now-25m", + "index": [ + "filebeat-*", + "logs-azure*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Azure AD Global Administrator Role Assigned", + "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.properties.category:RoleManagement and\nazure.auditlogs.operation_name:\"Add member to role\" and\nazure.auditlogs.properties.target_resources.0.modified_properties.1.new_value:\"\\\"Global Administrator\\\"\"\n", + "references": [ + "https://docs.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#global-administrator" + ], + "risk_score": 47, + "rule_id": "04c5a96f-19c5-44fd-9571-a0b033f9086f", + "severity": "medium", + "tags": [ + "Elastic", + "Cloud", + "Azure", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/", + "subtechnique": [ + { + "id": "T1098.003", + "name": "Add Office 365 Global Administrator Role", + "reference": "https://attack.mitre.org/techniques/T1098/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json index a545ae77d03ea..9002924a3df32 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json @@ -4,7 +4,7 @@ ], "description": "Identifies the creation of an AWS Elastic Compute Cloud (EC2) network access control list (ACL) or an entry in a network ACL with a specified rule number.", "false_positives": [ - "Network ACL's may be created by a network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Network ACL creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Network ACL's may be created by a network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Network ACL creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json index b7421934ba8e8..5856c5a7a0111 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json @@ -5,7 +5,7 @@ ], "description": "Identifies a change to an AWS Security Group Configuration. A security group is like a virtual firewall, and modifying configurations may allow unauthorized access. Threat actors may abuse this to establish persistence, exfiltrate data, or pivot in an AWS environment.", "false_positives": [ - "A security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "A security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-30m", "index": [ @@ -67,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json index 3da2a6e436686..3acae337041b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json @@ -5,9 +5,7 @@ "description": "The Debugger and SilentProcessExit registry keys can allow an adversary to intercept the execution of files, causing a different process to be executed. This functionality can be abused by an adversary to establish persistence.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -52,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json index 21ad9c5161541..a658ae60cd03d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json @@ -5,14 +5,12 @@ "description": "Identifies suspicious startup shell folder modifications to change the default Startup directory in order to bypass detections monitoring file creation in the Windows Startup folder.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", "name": "Suspicious Startup Shell Folder Modification", - "note": "## Triage and analysis\n\n### Investigating Suspicious Startup Shell Activity\n\nTechniques used within malware and by adversaries often leverage the Windows registry to store malicious programs for\npersistence. Startup shell folders are often targeted as they are not as prevalent as normal Startup folder paths so this\nbehavior may evade existing AV/EDR solutions. Another preference is that these programs might run with higher privileges\nwhich can be ideal for an attacker.\n\n#### Possible investigation steps:\n- Review the source process and related file tied to the Windows Registry entry\n- Validate the activity is not related to planned patches, updates, network administrator activity or legitimate software\ninstallations\n- Determine if activity is unique by validating if other machines in same organization have similar entry\n\n### False Positive Analysis\n- There is a high possibility of benign legitimate programs being added to Shell folders. This activity could be based\non new software installations, patches, or any kind of network administrator related activity. Before entering further\ninvestigation, this activity should be validated that is it not related to benign activity\n\n### Related Rules\n- Startup or Run Key Registry Modification\n- Persistent Scripts in the Startup Directory\n\n### Response and Remediation\n- Activity should first be validated as a true positive event if so then immediate response should be taken to review,\ninvestigate and potentially isolate activity to prevent further post-compromise behavior\n- The respective binary or program tied to this persistence method should be further analyzed and reviewed to understand\nit's behavior and capabilities\n- Since this activity is considered post-exploitation behavior, it's important to understand how the behavior was first\ninitialized such as through a macro-enabled document that was attached in a phishing email. By understanding the source\nof the attack, this information can then be used to search for similar indicators on other machines in the same environment.\n", + "note": "## Triage and analysis\n\n### Investigating Suspicious Startup Shell Activity\n\nTechniques used within malware and by adversaries often leverage the Windows registry to store malicious programs for\npersistence. Startup shell folders are often targeted as they are not as prevalent as normal Startup folder paths so this\nbehavior may evade existing AV/EDR solutions. These programs may also run with higher privileges which can be ideal for\nan attacker.\n\n#### Possible investigation steps:\n- Review the source process and related file tied to the Windows Registry entry\n- Validate the activity is not related to planned patches, updates, network administrator activity or legitimate software\ninstallations\n- Determine if activity is unique by validating if other machines in same organization have similar entry\n\n### False Positive Analysis\n- There is a high possibility of benign legitimate programs being added to Shell folders. This activity could be based\non new software installations, patches, or any kind of network administrator related activity. Before entering further\ninvestigation, it should be verified that this activity is not benign.\n\n### Related Rules\n- Startup or Run Key Registry Modification\n- Persistent Scripts in the Startup Directory\n\n### Response and Remediation\n- Activity should first be validated as a true positive event if so then take immediate action to review,\ninvestigate and potentially isolate activity to prevent further post-compromise behavior\n- The respective binary or program tied to this persistence method should be further analyzed and reviewed to understand\nits behavior and capabilities\n- Since this activity is considered post-exploitation behavior, it's important to understand how the behavior was first\ninitialized such as through a macro-enabled document that was attached in a phishing email. By understanding the source\nof the attack, this information can then be used to search for similar indicators on other machines in the same environment.\n", "query": "registry where\n registry.path : (\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\User Shell Folders\\\\Common Startup\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\\\\Common Startup\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\User Shell Folders\\\\Startup\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\\\\Startup\"\n ) and\n registry.data.strings != null and\n /* Normal Startup Folder Paths */\n not registry.data.strings : (\n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"%ProgramData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"%USERPROFILE%\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\"\n )\n", "risk_score": 73, "rule_id": "c8b150f0-0164-475b-a75e-74b47800a9ff", @@ -50,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json index e950569f19878..6946dd97761b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json @@ -3,7 +3,7 @@ "Elastic", "Austin Songer" ], - "description": "Identifies the assignment of rights to accesss content from another mailbox. An adversary may use the compromised account to send messages to other accounts in the network of the target business while creating inbox rules, so messages can evade spam/phishing detection mechanisms.", + "description": "Identifies the assignment of rights to access content from another mailbox. An adversary may use the compromised account to send messages to other accounts in the network of the target organization while creating inbox rules, so messages can evade spam/phishing detection mechanisms.", "false_positives": [ "Assignment of rights to a service account." ], @@ -14,8 +14,8 @@ "language": "kuery", "license": "Elastic License v2", "name": "O365 Exchange Suspicious Mailbox Right Delegation", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.action:Add-MailboxPermission and \no365.audit.Parameters.AccessRights:(FullAccess or SendAs or SendOnBehalf) and event.outcome:success\n", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.action:Add-MailboxPermission and \no365.audit.Parameters.AccessRights:(FullAccess or SendAs or SendOnBehalf) and event.outcome:success and\nnot user.id : \"NT AUTHORITY\\SYSTEM (Microsoft.Exchange.Servicehost)\"\n", "risk_score": 21, "rule_id": "0ce6487d-8069-4888-9ddd-61b52490cebc", "severity": "low", @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json index 07372389f48c3..d586982dcf513 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json @@ -4,7 +4,7 @@ ], "description": "Identifies the deletion of an Identity and Access Management (IAM) service account key in Google Cloud Platform (GCP). Each service account is associated with two sets of public/private RSA key pairs that are used to authenticate. If a key is deleted, the application will no longer be able to access Google Cloud resources using that key. A security best practice is to rotate your service account keys regularly.", "false_positives": [ - "Service account key deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Key deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Service account key deletions may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Key deletions by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "index": [ "filebeat-*", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json index 084ccb4da74f2..bced9ad8da3b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json @@ -4,7 +4,7 @@ ], "description": "Identifies the creation of a group in AWS Identity and Access Management (IAM). Groups specify permissions for multiple users. Any user in a group automatically has the permissions that are assigned to the group.", "false_positives": [ - "A group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Group creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "A group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Group creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_creation.json index 3c9626dcf5513..0c58ee2696e58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_creation.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Local Scheduled Task Creation", - "query": "sequence with maxspan=1m\n [process where event.type != \"end\" and\n ((process.name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\", \"wmic.exe\", \"mshta.exe\",\n \"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\", \"WmiPrvSe.exe\", \"wsmprovhost.exe\", \"winrshost.exe\") or\n process.pe.original_file_name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\", \"wmic.exe\", \"mshta.exe\",\n \"powershell.exe\", \"pwsh.dll\", \"powershell_ise.exe\", \"WmiPrvSe.exe\", \"wsmprovhost.exe\",\n \"winrshost.exe\")) or\n process.code_signature.trusted == false)] by process.entity_id\n [process where event.type == \"start\" and\n (process.name : \"schtasks.exe\" or process.pe.original_file_name == \"schtasks.exe\") and\n process.args : (\"/create\", \"-create\") and process.args : (\"/RU\", \"/SC\", \"/TN\", \"/TR\", \"/F\", \"/XML\") and\n /* exclude SYSTEM SIDs - look for task creations by non-SYSTEM user */\n not user.id : (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\")] by process.parent.entity_id\n", + "query": "sequence with maxspan=1m\n [process where event.type != \"end\" and\n ((process.name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\", \"wmic.exe\", \"mshta.exe\",\n \"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\", \"WmiPrvSe.exe\", \"wsmprovhost.exe\", \"winrshost.exe\") or\n process.pe.original_file_name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\", \"wmic.exe\", \"mshta.exe\",\n \"powershell.exe\", \"pwsh.dll\", \"powershell_ise.exe\", \"WmiPrvSe.exe\", \"wsmprovhost.exe\",\n \"winrshost.exe\")) or\n process.code_signature.trusted == false)] by process.entity_id\n [process where event.type == \"start\" and\n (process.name : \"schtasks.exe\" or process.pe.original_file_name == \"schtasks.exe\") and\n process.args : (\"/create\", \"-create\") and process.args : (\"/RU\", \"/SC\", \"/TN\", \"/TR\", \"/F\", \"/XML\") and\n /* exclude SYSTEM Integrity Level - look for task creations by non-SYSTEM user */\n not (process.Ext.token.integrity_level_name : \"System\" or winlog.event_data.IntegrityLevel : \"System\")\n ] by process.parent.entity_id\n", "risk_score": 21, "rule_id": "afcce5ad-65de-4ed2-8516-5e093d3ac99a", "severity": "low", @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json index b145642a01968..832677a04f32b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Exchange Management Group Role Assignment", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-ManagementRoleAssignment\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/new-managementroleassignment?view=exchange-ps", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_global_administrator_role_assign.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_global_administrator_role_assign.json new file mode 100644 index 0000000000000..701a4f8cfad0c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_global_administrator_role_assign.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "In Azure Active Directory (Azure AD), permissions to manage resources are assigned using Roles. The Global Administrator is a role that enables users to have access to all administrative features in Azure AD and services that use Azure Active Directory identities like the Microsoft 365 Defender portal, the Microsoft 365 compliance center, Exchange, SharePoint Online, and Skype for Business Online. Attackers can add users as Global Administrators to maintain access and manage all subscriptions and their settings and resources.", + "from": "now-25m", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Microsoft 365 Global Administrator Role Assigned", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.code:\"AzureActiveDirectory\" and event.action:\"Add member to role.\" and\no365.audit.ModifiedProperties.Role_DisplayName.NewValue:\"Global Administrator\"\n", + "references": [ + "https://docs.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#global-administrator" + ], + "risk_score": 47, + "rule_id": "88671231-6626-4e1b-abb7-6e361a171fbb", + "severity": "medium", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/", + "subtechnique": [ + { + "id": "T1098.003", + "name": "Add Office 365 Global Administrator Role", + "reference": "https://attack.mitre.org/techniques/T1098/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json index f3ad4d22cf14c..c622572b8a796 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Teams External Access Enabled", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:(SkypeForBusiness or MicrosoftTeams) and\nevent.category:web and event.action:\"Set-CsTenantFederationConfiguration\" and\no365.audit.Parameters.AllowFederatedUsers:True and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/microsoftteams/manage-external-access" @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json index 93f72b401d51e..81af49294a639 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Microsoft 365 Teams Guest Access Enabled", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:(SkypeForBusiness or MicrosoftTeams) and\nevent.category:web and event.action:\"Set-CsTeamsClientConfiguration\" and\no365.audit.Parameters.AllowGuestUser:True and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/skype/get-csteamsclientconfiguration?view=skype-ps" @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_msds_alloweddelegateto_krbtgt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_msds_alloweddelegateto_krbtgt.json new file mode 100644 index 0000000000000..5d5b7b2c10236 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_msds_alloweddelegateto_krbtgt.json @@ -0,0 +1,67 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the modification of the msDS-AllowedToDelegateTo attribute to KRBTGT. Attackers can use this technique to maintain persistence to the domain by having the ability to request tickets for the KRBTGT service.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*", + "logs-system.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "KRBTGT Delegation Backdoor", + "note": "## Config\n\nThe 'Audit User Account Management' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nAccount Management > \nAudit User Account Management (Success,Failure)\n```\n", + "query": "event.action:modified-user-account and event.code:4738 and winlog.event_data.AllowedToDelegateTo:*krbtgt*\n", + "references": [ + "https://skyblue.team/posts/delegate-krbtgt", + "https://github.com/atc-project/atomic-threat-coverage/blob/master/Atomic_Threat_Coverage/Logging_Policies/LP_0026_windows_audit_user_account_management.md" + ], + "risk_score": 73, + "rule_id": "e052c845-48d0-4f46-8a13-7d0aba05df82", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence", + "Active Directory" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json index 4f28f277d21e1..864827b99f31e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json @@ -4,7 +4,7 @@ ], "description": "Identifies the creation of a new Amazon Relational Database Service (RDS) Aurora DB cluster or global database spread across multiple regions.", "false_positives": [ - "Valid clusters may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Cluster creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "Valid clusters may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Cluster creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -62,5 +62,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json index fc72e25299dba..f5a8b12e5485a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json @@ -5,7 +5,7 @@ ], "description": "Identifies the creation of an Amazon Relational Database Service (RDS) Security group.", "false_positives": [ - "An RDS security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "An RDS security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -58,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json index 4ea6631025c11..0bc967072e0c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json @@ -5,7 +5,7 @@ ], "description": "Identifies the creation of an Amazon Relational Database Service (RDS) Aurora database instance.", "false_positives": [ - "A database instance may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Instances creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "A database instance may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Instances creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-60m", "index": [ @@ -45,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json index 2b94ded55e7d4..70f46879835fd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json @@ -5,9 +5,7 @@ "description": "Detects changes to registry persistence keys that are uncommonly used or modified by legitimate programs. This could be an indication of an adversary's attempt to persist in a stealthy manner.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -67,5 +65,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_remote_password_reset.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_remote_password_reset.json index 6db40da8bf9ef..cb79c3be9968a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_remote_password_reset.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_remote_password_reset.json @@ -14,7 +14,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Account Password Reset Remotely", - "query": "sequence by host.id with maxspan=5m\n [authentication where event.action == \"logged-in\" and\n /* event 4624 need to be logged */\n winlog.logon.type : \"Network\" and event.outcome == \"success\" and source.ip != null and\n not source.ip in (\"127.0.0.1\", \"::1\")] by winlog.event_data.TargetLogonId\n /* event 4724 need to be logged */\n [iam where event.action == \"reset-password\"] by winlog.event_data.SubjectLogonId\n", + "query": "sequence by host.id with maxspan=5m\n [authentication where event.action == \"logged-in\" and\n /* event 4624 need to be logged */\n winlog.logon.type : \"Network\" and event.outcome == \"success\" and source.ip != null and\n source.ip != \"127.0.0.1\" and source.ip != \"::1\"] by winlog.event_data.TargetLogonId\n /* event 4724 need to be logged */\n [iam where event.action == \"reset-password\"] by winlog.event_data.SubjectLogonId\n", "references": [ "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4724", "https://stealthbits.com/blog/manipulating-user-passwords-with-mimikatz/", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json index 1784c34feb085..3b7ab83f4f289 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json @@ -5,7 +5,7 @@ ], "description": "Identifies when an AWS Route Table has been created.", "false_positives": [ - "Route Table being created may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Route Table being created from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule. Automated processes that uses Terraform may lead to false positives." + "Route Tables may be created by a system or network administrators. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Route Table creation by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule. Automated processes that use Terraform may lead to false positives." ], "from": "now-60m", "index": [ @@ -47,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json index 54180a3a59a54..51ac1d7e37760 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json @@ -5,7 +5,7 @@ ], "description": "Identifies when an AWS Route Table has been modified or deleted.", "false_positives": [ - "Route Table could be modified or deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Route Table being modified from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule. Also automated processes that uses Terraform may lead to false positives." + "Route Table could be modified or deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Route Table being modified from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule. Also automated processes that use Terraform may lead to false positives." ], "from": "now-60m", "index": [ @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json index 46bbeb00f4d05..06405f2497f2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json @@ -5,9 +5,7 @@ "description": "Identifies run key or startup key registry modifications. In order to survive reboots and other system interrupts, attackers will modify run keys within the registry or leverage startup folder items as a form of persistence.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -49,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json index 544049d2c2df1..a81074f4f866a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Unexpected Child Process of macOS Screensaver Engine", - "note": "## Triage and analysis\n\n- Analyze the descendant processes of the ScreenSaverEngine process for malicious code and suspicious behavior such\nas downloading a payload from a server\n- Review the installed and activated screensaver on the host. Triage the screensaver (.saver) file that was triggered to\nidentify whether the file is malicious or not.\n", + "note": "## Triage and analysis\n\n- Analyze the descendant processes of the ScreenSaverEngine process for malicious code and suspicious behavior such\nas a download of a payload from a server\n- Review the installed and activated screensaver on the host. Triage the screensaver (.saver) file that was triggered to\nidentify whether the file is malicious or not.\n", "query": "process where event.type == \"start\" and process.parent.name == \"ScreenSaverEngine\"\n", "references": [ "https://posts.specterops.io/saving-your-access-d562bf5bf90b", @@ -46,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json index 64b9aba81551d..c44e78f95845d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json @@ -5,9 +5,7 @@ "description": "Identifies processes modifying the services registry key directly, instead of through the expected Windows APIs. This could be an indication of an adversary attempting to stealthily persist through abnormal service creation or modification of an existing service.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -49,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json index 2545dec90f75d..150b886c70b87 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json @@ -5,9 +5,7 @@ "description": "Identifies Component Object Model (COM) hijacking via registry modification. Adversaries may establish persistence by executing malicious content triggered by hijacked references to COM objects.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -52,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json index 117bf78d76287..11e7195a8de4b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json @@ -5,9 +5,7 @@ "description": "Identifies the creation of a suspicious ImagePath value. This could be an indication of an adversary attempting to stealthily persist or escalate privileges through abnormal service creation.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -49,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json index 4abba51668c0f..30900394ec9c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json @@ -5,9 +5,7 @@ "description": "Windows operating systems are utilizing the time provider architecture in order to obtain accurate time stamps from other network devices or clients in the network. Time providers are implemented in the form of a DLL file which resides in System32 folder. The service W32Time initiates during the startup of Windows and loads w32time.dll. Adversaries may abuse this architecture to establish persistence, specifically by registering and enabling a malicious DLL as a time provider.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -52,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json index da32a291e3574..f7589be60ff6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Persistence via Hidden Run Key Detected", - "query": "/* Registry Path ends with backslash */\nregistry where /* length(registry.data.strings) > 0 and */\n registry.path : (\"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\\", \n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\\", \n \"HKLM\\\\Software\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\\", \n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\\", \n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\\")\n", + "query": "/* Registry Path ends with backslash */\nregistry where /* length(registry.data.strings) > 0 and */\n registry.path : (\"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\\",\n \"HKU\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\\", \n \"HKLM\\\\Software\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\\", \n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\\",\n \"HKU\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\\")\n", "references": [ "https://github.com/outflanknl/SharpHide", "https://github.com/ewhitehats/InvisiblePersistence/blob/master/InvisibleRegValues_Whitepaper.pdf" @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json index 7375dbce0f795..5e796de0460aa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Installation of Security Support Provider", - "query": "registry where\n registry.path : (\"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Control\\\\Lsa\\\\Security Packages*\", \n \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Control\\\\Lsa\\\\OSConfig\\\\Security Packages*\") and\n not process.executable : (\"C:\\\\Windows\\\\System32\\\\msiexec.exe\", \"C:\\\\Windows\\\\SysWOW64\\\\msiexec.exe\")\n", + "query": "registry where\n registry.path : (\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Lsa\\\\Security Packages*\", \n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Lsa\\\\OSConfig\\\\Security Packages*\") and\n not process.executable : (\"C:\\\\Windows\\\\System32\\\\msiexec.exe\", \"C:\\\\Windows\\\\SysWOW64\\\\msiexec.exe\")\n", "risk_score": 47, "rule_id": "e86da94d-e54b-4fb5-b96c-cecff87e8787", "severity": "medium", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json index 3fdb1e1ebc96d..d35fbf97ee846 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json @@ -5,9 +5,7 @@ "description": "Identifies use of the Windows Management Instrumentation StdRegProv (registry provider) to modify commonly abused registry locations for persistence.", "from": "now-9m", "index": [ - "logs-endpoint.events.*", - "winlogbeat-*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -79,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json index 22e3aa6cce8d9..de17b7a5b30a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json @@ -4,7 +4,7 @@ ], "description": "Identifies when SAML activity has occurred in AWS. An adversary could manipulate SAML to maintain access to the target.", "false_positives": [ - "SAML Provider could be updated by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. SAML Provider being updated from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + "SAML Provider could be updated by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. SAML Provider updates by unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], "from": "now-25m", "index": [ @@ -72,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json index 26dbed5e681d3..c31cec2aeaa40 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Disabling User Account Control via Registry Modification", - "query": "registry where event.type == \"change\" and\n registry.path :\n (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\EnableLUA\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\ConsentPromptBehaviorAdmin\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\PromptOnSecureDesktop\"\n ) and\n registry.data.strings : \"0\"\n", + "query": "registry where event.type == \"change\" and\n registry.path :\n (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\EnableLUA\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\ConsentPromptBehaviorAdmin\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\PromptOnSecureDesktop\"\n ) and\n registry.data.strings : (\"0\", \"0x00000000\")\n", "references": [ "https://www.greyhathacker.net/?p=796", "https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/user-account-control-group-policy-and-registry-key-settings", @@ -76,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json index fbba3ff429579..bc44e9cf35116 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json @@ -3,7 +3,7 @@ "Elastic", "Austin Songer" ], - "description": "Identifies the creation or patching of potential malicious rolebinding. You can assign these roles to Kubernetes subjects (users, groups, or service accounts) with role bindings and cluster role bindings.", + "description": "Identifies the creation or patching of potentially malicious role bindings. Users can use role bindings and cluster role bindings to assign roles to Kubernetes subjects (users, groups, or service accounts).", "from": "now-20m", "index": [ "filebeat-*", @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "GCP Kubernetes Rolebindings Created or Patched", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:(io.k8s.authorization.rbac.v*.clusterrolebindings.create or \nio.k8s.authorization.rbac.v*.rolebindings.create or io.k8s.authorization.rbac.v*.clusterrolebindings.patch or \nio.k8s.authorization.rbac.v*.rolebindings.patch) and event.outcome:success\n", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:(io.k8s.authorization.rbac.v*.clusterrolebindings.create or \nio.k8s.authorization.rbac.v*.rolebindings.create or io.k8s.authorization.rbac.v*.clusterrolebindings.patch or \nio.k8s.authorization.rbac.v*.rolebindings.patch) and event.outcome:success and\nnot gcp.audit.authentication_info.principal_email:\"system:addon-manager\"\n", "references": [ "https://cloud.google.com/kubernetes-engine/docs/how-to/audit-logging", "https://unofficial-kubernetes.readthedocs.io/en/latest/admin/authorization/rbac/", @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json index df4716596039d..f08fc5979a24c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json @@ -13,7 +13,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Startup/Logon Script added to Group Policy Object", - "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects can be used by attackers as a mechanism for an attacker to instruct an arbitrarily large group of clients to\nexecute specified commands at Startup, Logon, Shutdown, and Logoff. This is done by creating/modifying the `scripts.ini` or \n`psscripts.ini` files. The scripts are stored in the following path: `\\Machine\\Scripts\\`, `\\User\\Scripts\\`\n\n#### Possible investigation steps:\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the activity is legitimate\nand the administrator is authorized to perform this operation.\n- Retrieve the contents of the script file, check for any potentially malicious commands and binaries.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n- Verify if the execution is allowed and done under change management, and legitimate.\n\n### Related Rules\n- Group Policy Abuse for Privilege Addition - b9554892-5e0e-424b-83a0-5aef95aa43bf\n- Scheduled Task Execution at Scale via GPO - 15a8ba77-1c13-4274-88fe-6bd14133861e\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects can be used by attackers as a mechanism for an attacker to instruct an arbitrarily large group of clients to\nexecute specified commands at startup, logon, shutdown, and logoff. This is done by creating/modifying the `scripts.ini` or \n`psscripts.ini` files. The scripts are stored in the following path: `\\Machine\\Scripts\\`, `\\User\\Scripts\\`\n\n#### Possible investigation steps:\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the activity is legitimate\nand the administrator is authorized to perform this operation.\n- Retrieve the contents of the script file, and check for any potentially malicious commands and binaries.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n- Verify if the execution is allowed and done under change management, and legitimate.\n\n### Related Rules\n- Group Policy Abuse for Privilege Addition - b9554892-5e0e-424b-83a0-5aef95aa43bf\n- Scheduled Task Execution at Scale via GPO - 15a8ba77-1c13-4274-88fe-6bd14133861e\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate, and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", "query": "(\n event.code:5136 and winlog.event_data.AttributeLDAPDisplayName:(gPCMachineExtensionNames or gPCUserExtensionNames) and\n winlog.event_data.AttributeValue:(*42B5FAAE-6536-11D2-AE5A-0000F87571E3* and\n (*40B66650-4972-11D1-A7CA-0000F87571E3* or *40B6664F-4972-11D1-A7CA-0000F87571E3*))\n)\nor\n(\n event.code:5145 and winlog.event_data.ShareName:\\\\\\\\*\\\\SYSVOL and\n winlog.event_data.RelativeTargetName:(*\\\\scripts.ini or *\\\\psscripts.ini) and\n (message:WriteData or winlog.event_data.AccessList:*%%4417*)\n)\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0025_windows_audit_directory_service_changes.md", @@ -62,5 +62,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json index d25b729d967c3..e2f46bc0dd897 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "This rule detects the first occurrence of a modification to Group Policy Object Attributes to add privileges to user accounts or use them to add users as local admins.", + "description": "Detects the first occurrence of a modification to Group Policy Object Attributes to add privileges to user accounts or use them to add users as local admins.", "index": [ "winlogbeat-*", "logs-system.*" @@ -10,7 +10,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Group Policy Abuse for Privilege Addition", - "note": "## Triage and analysis\n\n### Investigating Group Policy Abuse for Privilege Addition\n\nGroup Policy Objects can be used to add rights and/or modify Group Membership on GPOs by changing the contents of an INF file named\nGptTmpl.inf, which is responsible for storing every setting under the Security Settings container in the GPO, this file is unique\nfor each GPO, and only exists if the GPO contains security settings.\nExample Path: \"\\\\DC.com\\SysVol\\DC.com\\Policies\\{21B9B880-B2FB-4836-9C2D-2013E0D832E9}\\Machine\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf\"\n\n#### Possible investigation steps:\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the activity is legitimate\nand the administrator is authorized to perform this operation.\n- Retrieve the contents of the `GptTmpl.inf` file, under the `Privilege Rights` section, look for potentially dangerous high privileges,\nfor example: SeTakeOwnershipPrivilege, SeEnableDelegationPrivilege, etc.\n- Inspect the user SIDs associated with these privileges\n\n### False Positive Analysis\n- Verify if these User SIDs should have these privileges enabled.\n- Inspect whether the user that has done these modifications should be allowed to do it. The user name can be found in the\n`winlog.event_data.SubjectUserName` field\n\n### Related Rules\n- Scheduled Task Execution at Scale via GPO\n- Startup/Logon Script added to Group Policy Object\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'Audit Directory Service Changes' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Group Policy Abuse for Privilege Addition\n\nGroup Policy Objects can be used to add rights and/or modify Group Membership on GPOs by changing the contents of an INF\nfile named GptTmpl.inf, which is responsible for storing every setting under the Security Settings container in the GPO.\nThis file is unique for each GPO, and only exists if the GPO contains security settings.\nExample Path: \"\\\\DC.com\\SysVol\\DC.com\\Policies\\{21B9B880-B2FB-4836-9C2D-2013E0D832E9}\\Machine\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf\"\n\n#### Possible investigation steps:\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the activity\nis legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the `GptTmpl.inf` file, and under the `Privilege Rights` section, look for potentially\ndangerous high privileges, for example: SeTakeOwnershipPrivilege, SeEnableDelegationPrivilege, etc.\n- Inspect the user SIDs associated with these privileges\n\n### False Positive Analysis\n- Verify if these User SIDs should have these privileges enabled.\n- Inspect whether the user that has done these modifications should be allowed to do it. The user name can be found in the\n`winlog.event_data.SubjectUserName` field.\n\n### Related Rules\n- Scheduled Task Execution at Scale via GPO\n- Startup/Logon Script added to Group Policy Object\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate, and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'Audit Directory Service Changes' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", "query": "event.code: \"5136\" and winlog.event_data.AttributeLDAPDisplayName:\"gPCMachineExtensionNames\" and \nwinlog.event_data.AttributeValue:(*827D319E-6EAC-11D2-A4EA-00C04F79F83A* and *803E14A0-B4FB-11D0-A0D0-00A0C90F574B*)\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0025_windows_audit_directory_service_changes.md", @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json index bad9948b08e1c..01586f1b7409c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json @@ -10,7 +10,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Scheduled Task Execution at Scale via GPO", - "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects can be used by attackers to execute Scheduled Tasks at scale to compromise Objects controlled by a given GPO,\nthis is done by changing the contents of the `\\Machine\\Preferences\\ScheduledTasks\\ScheduledTasks.xml` file.\n\n#### Possible investigation steps:\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the activity is legitimate\nand the administrator is authorized to perform this operation.\n- Retrieve the contents of the `ScheduledTasks.xml` file, check the `` and `` XML tags for any potentially malicious\ncommands and binaries.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n- Verify if the execution is allowed and done under change management, and if the execution is legitimate.\n\n### Related Rules\n- Group Policy Abuse for Privilege Addition\n- Startup/Logon Script added to Group Policy Object\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects can be used by attackers to execute scheduled tasks at scale to compromise objects controlled by a\ngiven GPO. This is done by changing the contents of the `\\Machine\\Preferences\\ScheduledTasks\\ScheduledTasks.xml`\nfile.\n\n#### Possible investigation steps:\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the activity\nis legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the `ScheduledTasks.xml` file, \u00e1nd check the `` and `` XML tags for any\npotentially malicious commands and binaries.\n- If the action is suspicious for the user, check for any other activities done by the user in the last 48 hours.\n\n### False Positive Analysis\n- Verify if the execution is allowed and done under change management, and if the execution is legitimate.\n\n### Related Rules\n- Group Policy Abuse for Privilege Addition\n- Startup/Logon Script added to Group Policy Object\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate, and potentially isolate activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy is required be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", "query": "(event.code: \"5136\" and winlog.event_data.AttributeLDAPDisplayName:(\"gPCMachineExtensionNames\" or \"gPCUserExtensionNames\") and \n winlog.event_data.AttributeValue:(*CAB54552-DEEA-4691-817E-ED4A4D1AFC72* and *AADCED64-746C-4633-A97C-D61349046527*)) \nor\n(event.code: \"5145\" and winlog.event_data.ShareName: \"\\\\\\\\*\\\\SYSVOL\" and winlog.event_data.RelativeTargetName: *ScheduledTasks.xml and\n (message: WriteData or winlog.event_data.AccessList: *%%4417*))\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0025_windows_audit_directory_service_changes.md", @@ -68,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json index a8ad50511283a..6681bac66c053 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json @@ -12,8 +12,8 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Privilege Escalation via InstallerFileTakeOver", - "note": "## Triage and analysis.\n\n### Investigating Potential Priivilege Escalation via InstallerFileTakeOver\n\nInstallerFileTakeOver is a weaponized EoP PoC to the CVE-2021-41379 vulnerability. Upon successful exploitation,\nan unprivileged user will escalate privileges to SYSTEM/NT AUTHORITY.\n\nThis rule detects the default execution of the PoC, which overwrites the `elevation_service.exe` DACL and copy itself\nto the location to escalate privileges. An attacker is able to still take over any file that is not in use (locked), which is outside the scope of this rule.\n\n#### Possible investigation steps:\n\n- Check for the digital signature of the executable\n- Look for additional processes spawned by the process, command lines and network communications.\n- Look for additional alerts involving the host and the user.\n\n### False Positive Analysis\n\n- Verify whether the digital signature exists in the executable, and if it is valid.\n\n### Related Rules\n\n- Suspicious DLL Loaded for Persistence or Privilege Escalation - bfeaf89b-a2a7-48a3-817f-e41829dc61ee\n\n### Response and Remediation\n\n- Immediate response should be taken to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior.\n", - "query": "/* This rule is compatible with both Sysmon and Elastic Endpoint */\n\nprocess where event.type == \"start\" and \n user.id : \"S-1-5-18\" and\n (\n (process.name : \"elevation_service.exe\" and \n not process.pe.original_file_name == \"elevation_service.exe\") or\n\n (process.parent.name : \"elevation_service.exe\" and \n process.name : (\"rundll32.exe\", \"cmd.exe\", \"powershell.exe\")) \n )\n", + "note": "## Triage and analysis.\n\n### Investigating Potential Priivilege Escalation via InstallerFileTakeOver\n\nInstallerFileTakeOver is a weaponized EoP PoC to the CVE-2021-41379 vulnerability. Upon successful exploitation,\nan unprivileged user will escalate privileges to SYSTEM/NT AUTHORITY.\n\nThis rule detects the default execution of the PoC, which overwrites the `elevation_service.exe` DACL and copies itself\nto the location to escalate privileges. An attacker is able to still take over any file that is not in use (locked), which is outside the scope of this rule.\n\n#### Possible investigation steps:\n\n- Check for the digital signature of the executable.\n- Look for additional processes spawned by the process, command lines, and network communications.\n- Look for additional alerts involving the host and the user.\n\n### False Positive Analysis\n\n- Verify whether the digital signature exists in the executable, and if it is valid.\n\n### Related Rules\n\n- Suspicious DLL Loaded for Persistence or Privilege Escalation - bfeaf89b-a2a7-48a3-817f-e41829dc61ee\n\n### Response and Remediation\n\n- Immediate response should be taken to validate activity, investigate, and potentially isolate activity to prevent further\npost-compromise behavior.\n", + "query": "/* This rule is compatible with both Sysmon and Elastic Endpoint */\n\nprocess where event.type == \"start\" and \n (process.Ext.token.integrity_level_name : \"System\" or\n winlog.event_data.IntegrityLevel : \"System\") and\n (\n (process.name : \"elevation_service.exe\" and \n not process.pe.original_file_name == \"elevation_service.exe\") or\n\n (process.parent.name : \"elevation_service.exe\" and \n process.name : (\"rundll32.exe\", \"cmd.exe\", \"powershell.exe\")) \n )\n", "references": [ "https://github.com/klinix5/InstallerFileTakeOver" ], @@ -46,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json index ef92d2179a60c..9b5ae8b997b74 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json @@ -5,9 +5,7 @@ "description": "Adversaries can use the autostart mechanism provided by the Local Security Authority (LSA) authentication packages for privilege escalation or persistence by placing a reference to a binary in the Windows registry. The binary will then be executed by SYSTEM when the authentication packages are loaded.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -71,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json index 2a1231e96d8a5..c808635f20fb7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json @@ -10,7 +10,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "New or Modified Federation Domain", - "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Set-AcceptedDomain\" or \n\"Set-MsolDomainFederationSettings\" or \"Add-FederatedDomain\" or \"New-AcceptedDomain\" or \"Remove-AcceptedDomain\" or \"Remove-FederatedDomain\") and \nevent.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-accepteddomain?view=exchange-ps", @@ -57,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pkexec_envar_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pkexec_envar_hijack.json new file mode 100644 index 0000000000000..402876ab4ccf7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pkexec_envar_hijack.json @@ -0,0 +1,70 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempt to exploit a local privilege escalation in polkit pkexec (CVE-2021-4034) via unsecure environment variable injection. Successful exploitation allows an unprivileged user to escalate to the root user.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential Privilege Escalation via PKEXEC", + "query": "file where file.path : \"/*GCONV_PATH*\"\n", + "references": [ + "https://seclists.org/oss-sec/2022/q1/80", + "https://haxx.in/files/blasty-vs-pkexec.c" + ], + "risk_score": 73, + "rule_id": "8da41fc9-7735-4b24-9cc6-c78dfc9fc9c9", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1068", + "name": "Exploitation for Privilege Escalation", + "reference": "https://attack.mitre.org/techniques/T1068/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.007", + "name": "Path Interception by PATH Environment Variable", + "reference": "https://attack.mitre.org/techniques/T1574/007/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json index aa9f0c21ec272..0eb2a7e01a1d1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json @@ -5,14 +5,12 @@ "description": "Identifies port monitor and print processor registry modifications. Adversaries may abuse port monitor and print processors to run malicious DLLs during system boot that will be executed as SYSTEM for privilege escalation and/or persistence, if permissions allow writing a fully-qualified pathname for that DLL.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", "name": "Potential Port Monitor or Print Processor Registration Abuse", - "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Monitors\\\\*\",\n \"HLLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Environments\\\\Windows*\\\\Print Processors\\\\*\") and\n registry.data.strings : \"*.dll\" and\n /* exclude SYSTEM SID - look for changes by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Monitors\\\\*\",\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Environments\\\\Windows*\\\\Print Processors\\\\*\") and\n registry.data.strings : \"*.dll\" and\n /* exclude SYSTEM SID - look for changes by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", "references": [ "https://www.welivesecurity.com/2020/05/21/no-game-over-winnti-group/" ], @@ -74,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_malicious_registry_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_malicious_registry_modification.json index c0ebdf3cac6b3..7f76c27f369af 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_malicious_registry_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_malicious_registry_modification.json @@ -5,8 +5,7 @@ "description": "Detects attempts to exploit privilege escalation vulnerabilities related to the Print Spooler service. For more information refer to CVE-2021-34527 and verify that the impacted system is investigated.", "from": "now-9m", "index": [ - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -45,5 +44,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json index 2f7ea1a1869e1..3d84e33c83791 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json @@ -5,9 +5,7 @@ "description": "Detects attempts to exploit a privilege escalation vulnerability (CVE-2020-1030) related to the print spooler service. Exploitation involves chaining multiple primitives to load an arbitrary DLL into the print spooler process running as SYSTEM.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -46,5 +44,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json index c07f9aa3467d1..d94fdbc846847 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json @@ -5,9 +5,7 @@ "description": "Identifies a privilege escalation attempt via a rogue Windows directory (Windir) environment variable. This is a known primitive that is often combined with other vulnerabilities to elevate privileges.", "from": "now-9m", "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" + "logs-endpoint.events.*" ], "language": "eql", "license": "Elastic License v2", @@ -52,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_samaccountname_spoofing_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_samaccountname_spoofing_attack.json new file mode 100644 index 0000000000000..b84b434e1d221 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_samaccountname_spoofing_attack.json @@ -0,0 +1,75 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies a suspicious computer account name rename event, which may indicate an attempt to exploit CVE-2021-42278 to elevate privileges from a standard domain user to a user with domain admin privileges. CVE-2021-42278 is a security vulnerability that allows potential attackers to impersonate a domain controller via samAccountName attribute spoofing.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential Privileged Escalation via SamAccountName Spoofing", + "query": "iam where event.action == \"renamed-user-account\" and\n /* machine account name renamed to user like account name */\n winlog.event_data.OldTargetUserName : \"*$\" and not winlog.event_data.NewTargetUserName : \"*$\"\n", + "references": [ + "https://support.microsoft.com/en-us/topic/kb5008102-active-directory-security-accounts-manager-hardening-changes-cve-2021-42278-5975b463-4c95-45e1-831a-d120004e258e", + "https://cloudbrothers.info/en/exploit-kerberos-samaccountname-spoofing/", + "https://github.com/cube0x0/noPac", + "https://twitter.com/exploitph/status/1469157138928914432", + "https://exploit.ph/cve-2021-42287-cve-2021-42278-weaponisation.html" + ], + "risk_score": 73, + "rule_id": "bdcf646b-08d4-492c-870a-6c04e3700034", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.002", + "name": "Domain Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json index 37fcef44719af..4688d4cb79b81 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json @@ -4,7 +4,7 @@ ], "description": "Identifies the use of AssumeRole. AssumeRole returns a set of temporary security credentials that can be used to access AWS resources. An adversary could use those credentials to move laterally and escalate privileges.", "false_positives": [ - "Automated processes that uses Terraform may lead to false positives." + "Automated processes that use Terraform may lead to false positives." ], "index": [ "filebeat-*", @@ -70,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json index 10115d932fc07..17b5df39f8835 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Unusual Print Spooler Child Process", - "query": "process where event.type == \"start\" and\n process.parent.name : \"spoolsv.exe\" and user.id : \"S-1-5-18\" and\n\n /* exclusions for FP control below */\n not process.name : (\"splwow64.exe\", \"PDFCreator.exe\", \"acrodist.exe\", \"spoolsv.exe\", \"msiexec.exe\", \"route.exe\", \"WerFault.exe\") and\n not process.command_line : \"*\\\\WINDOWS\\\\system32\\\\spool\\\\DRIVERS*\" and\n not (process.name : \"net.exe\" and process.command_line : (\"*stop*\", \"*start*\")) and\n not (process.name : (\"cmd.exe\", \"powershell.exe\") and process.command_line : (\"*.spl*\", \"*\\\\program files*\", \"*route add*\")) and\n not (process.name : \"netsh.exe\" and process.command_line : (\"*add portopening*\", \"*rule name*\")) and\n not (process.name : \"regsvr32.exe\" and process.command_line : \"*PrintConfig.dll*\")\n", + "query": "process where event.type == \"start\" and\n process.parent.name : \"spoolsv.exe\" and\n (process.Ext.token.integrity_level_name : \"System\" or\n winlog.event_data.IntegrityLevel : \"System\") and\n\n /* exclusions for FP control below */\n not process.name : (\"splwow64.exe\", \"PDFCreator.exe\", \"acrodist.exe\", \"spoolsv.exe\", \"msiexec.exe\", \"route.exe\", \"WerFault.exe\") and\n not process.command_line : \"*\\\\WINDOWS\\\\system32\\\\spool\\\\DRIVERS*\" and\n not (process.name : \"net.exe\" and process.command_line : (\"*stop*\", \"*start*\")) and\n not (process.name : (\"cmd.exe\", \"powershell.exe\") and process.command_line : (\"*.spl*\", \"*\\\\program files*\", \"*route add*\")) and\n not (process.name : \"netsh.exe\" and process.command_line : (\"*add portopening*\", \"*rule name*\")) and\n not (process.name : \"regsvr32.exe\" and process.command_line : \"*PrintConfig.dll*\")\n", "references": [ "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527", "https://github.com/afwu/PrintNightmare" @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_windows_service_via_unusual_client.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_windows_service_via_unusual_client.json new file mode 100644 index 0000000000000..469b1a4e9096f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_windows_service_via_unusual_client.json @@ -0,0 +1,58 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a Windows service by an unusual client process. Services may be created with administrator privileges but are executed under SYSTEM privileges, so an adversary may also use a service to escalate privileges from administrator to SYSTEM.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-system.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Windows Service Installed via an Unusual Client", + "note": "## Config\n\nThe 'Audit Security System Extension' logging policy must be configured for (Success)\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nSystem > \nAudit Security System Extension (Success)\n```\n", + "query": "event.action:\"service-installed\" and (winlog.event_data.ClientProcessId:\"0\" or winlog.event_data.ParentProcessId:\"0\")\n", + "references": [ + "https://www.x86matthew.com/view_post?id=create_svc_rpc", + "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4697", + "https://github.com/atc-project/atomic-threat-coverage/blob/master/Atomic_Threat_Coverage/Logging_Policies/LP_0100_windows_audit_security_system_extension.md" + ], + "risk_score": 73, + "rule_id": "55c2bf58-2a39-4c58-a384-c8b1978153c2", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/", + "subtechnique": [ + { + "id": "T1543.003", + "name": "Windows Service", + "reference": "https://attack.mitre.org/techniques/T1543/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json index 426e37c3e904c..a4d5c9b315e0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json @@ -16,7 +16,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Threat Intel Filebeat Module (v8.x) Indicator Match", - "note": "## Triage and Analysis\n\n### Investigating Threat Intel Indicator Matches\n\nThreat Intel indicator match rules allow matching from a local observation such as an endpoint event that records a file\nhash with an entry of a file hash stored within the Threat Intel integrations. Other examples of matches can occur on\nan IP address, registry path, URL and imphash.\n\nThe matches will be based on the incoming last 30 days feed data so it's important to validate the data and review the results by\ninvestigating the associated activity to determine if it requires further investigation.\n\nIf an indicator matches a local observation, the following enriched fields will be generated to identify the indicator, field, and type matched.\n\n- `threat.indicator.matched.atomic` - this identifies the atomic indicator that matched the local observation\n- `threat.indicator.matched.field` - this identifies the indicator field that matched the local observation\n- `threat.indicator.matched.type` - this identifies the indicator type that matched the local observation\n\n#### Possible investigation steps:\n- Investigation should be validated and reviewed based on the data (file hash, registry path, URL, imphash) that was matched\nand viewing the source of that activity.\n- Consider the history of the indicator that was matched. Has it happened before? Is it happening on multiple machines?\nThese kinds of questions can help understand if the activity is related to legitimate behavior.\n- Consider the user and their role within the company, is this something related to their job or work function?\n\n### False Positive Analysis\n- For any matches found, it's important to consider the initial release date of that indicator. Threat intelligence can\nbe a great tool for augmenting existing security processes, while at the same time it should be understood that threat\nintelligence can represent a specific set of activity observed at a point in time. For example, an IP address\nmay have hosted malware observed in a Dridex campaign month ago, but it's possible that IP has been remediated and\nno longer represents any threat.\n- Adversaries often use legitimate tools as network administrators such as `PsExec` or `AdFind`, these tools often find their\nway into indicator lists creating the potential for false positives.\n- It's possible after large and publicly written campaigns, curious employees might end up going directly to attacker infrastructure and generating these rules\n\n### Response and Remediation\n- If suspicious or malicious behavior is observed, immediate response should be taken to isolate activity to prevent further\npost-compromise behavior.\n- One example of a response if a machine matched a command and control IP address would be to add an entry to a network\ndevice such as a firewall or proxy appliance to prevent any outbound activity from leaving that machine.\n- Another example of a response with a malicious file hash match would involve validating if the file was properly quarantined,\nreview current running processes looking for any abnormal activity, and investigating for any other follow-up actions such as persistence or lateral movement\n", + "note": "## Triage and Analysis\n\n### Investigating Threat Intel Indicator Matches\n\nThreat Intel indicator match rules allow matching from a local observation such as an endpoint event that records a file\nhash with an entry of a file hash stored within the Threat Intel integrations. Matches can also occur on\nan IP address, registry path, URL, or imphash.\n\nThe matches will be based on the incoming last 30 days feed data so it's important to validate the data and review the results by\ninvestigating the associated activity to determine if it requires further investigation.\n\nIf an indicator matches a local observation, the following enriched fields will be generated to identify the indicator, field, and type matched.\n\n- `threat.indicator.matched.atomic` - this identifies the atomic indicator that matched the local observation\n- `threat.indicator.matched.field` - this identifies the indicator field that matched the local observation\n- `threat.indicator.matched.type` - this identifies the indicator type that matched the local observation\n\n#### Possible investigation steps:\n- Investigation should be validated and reviewed based on the data (file hash, registry path, URL, imphash) that was matched\nand by viewing the source of that activity.\n- Consider the history of the indicator that was matched. Has it happened before? Is it happening on multiple machines?\nThese kinds of questions can help understand if the activity is related to legitimate behavior.\n- Consider the user and their role within the company: is this something related to their job or work function?\n\n### False Positive Analysis\n- For any matches found, it's important to consider the initial release date of that indicator. Threat intelligence can\nbe a great tool for augmenting existing security processes, while at the same time it should be understood that threat\nintelligence can represent a specific set of activity observed at a point in time. For example, an IP address\nmay have hosted malware observed in a Dridex campaign months ago, but it's possible that IP has been remediated and\nno longer represents any threat.\n- Adversaries often use legitimate tools as network administrators such as `PsExec` or `AdFind`; these tools often find their\nway into indicator lists creating the potential for false positives.\n- It's possible after large and publicly written campaigns, curious employees might end up going directly to attacker infrastructure and triggering these rules.\n\n### Response and Remediation\n- If suspicious or malicious behavior is observed, take immediate action to isolate activity to prevent further\npost-compromise behavior.\n- One example of a response if a machine matched a command and control IP address would be to add an entry to a network\ndevice such as a firewall or proxy appliance to prevent any outbound activity from leaving that machine.\n- Another example of a response with a malicious file hash match would involve validating if the file was properly quarantined,\nreviewing current running processes for any abnormal activity, and investigating for any other follow-up actions such as persistence or lateral movement.\n", "query": "file.hash.*:* or file.pe.imphash:* or source.ip:* or destination.ip:* or url.full:* or registry.path:*\n", "references": [ "https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-threatintel.html" @@ -40,16 +40,16 @@ }, "meta": { "disabled": false, - "key": "event.dataset", + "key": "event.module", "negate": false, "params": { - "query": "ti_*" + "query": "threatintel" }, "type": "phrase" }, "query": { "match_phrase": { - "event.dataset": "ti_*" + "event.module": "threatintel" } } }, @@ -190,9 +190,9 @@ ] } ], - "threat_query": "@timestamp >= \"now-30d\" and event.dataset:ti_* and (threat.indicator.file.hash.*:* or threat.indicator.file.pe.imphash:* or threat.indicator.ip:* or threat.indicator.registry.path:* or threat.indicator.url.full:*)", + "threat_query": "@timestamp >= \"now-30d/d\" and event.module:threatintel and (threat.indicator.file.hash.*:* or threat.indicator.file.pe.imphash:* or threat.indicator.ip:* or threat.indicator.registry.path:* or threat.indicator.url.full:*)", "timeline_id": "495ad7a7-316e-4544-8a0f-9c098daee76e", "timeline_title": "Generic Threat Match Timeline", "type": "threat_match", - "version": 1 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json index 21f1029f4c8aa..2612a8139e30e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "This rule is triggered when indicators from the Threat Intel integrations has a match against local file or network observations.", + "description": "This rule is triggered when indicators from the Threat Intel integrations have a match against local file or network observations.", "from": "now-65m", "index": [ "auditbeat-*", @@ -16,7 +16,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Threat Intel Indicator Match", - "note": "## Triage and Analysis\n\n### Investigating Threat Intel Indicator Matches\n\nThreat Intel indicator match rules allow matching from a local observation such as an endpoint event that records a file\nhash with an entry of a file hash stored within the Threat Intel integrations. Other examples of matches can occur on\nan IP address, registry path, URL and imphash.\n\nThe matches will be based on the incoming last 30 days feed data so it's important to validate the data and review the results by\ninvestigating the associated activity to determine if it requires further investigation.\n\nIf an indicator matches a local observation, the following enriched fields will be generated to identify the indicator, field, and type matched.\n\n- `threat.indicator.matched.atomic` - this identifies the atomic indicator that matched the local observation\n- `threat.indicator.matched.field` - this identifies the indicator field that matched the local observation\n- `threat.indicator.matched.type` - this identifies the indicator type that matched the local observation\n\n#### Possible investigation steps:\n- Investigation should be validated and reviewed based on the data (file hash, registry path, URL, imphash) that was matched\nand viewing the source of that activity.\n- Consider the history of the indicator that was matched. Has it happened before? Is it happening on multiple machines?\nThese kinds of questions can help understand if the activity is related to legitimate behavior.\n- Consider the user and their role within the company, is this something related to their job or work function?\n\n### False Positive Analysis\n- For any matches found, it's important to consider the initial release date of that indicator. Threat intelligence can\nbe a great tool for augmenting existing security processes, while at the same time it should be understood that threat\nintelligence can represent a specific set of activity observed at a point in time. For example, an IP address\nmay have hosted malware observed in a Dridex campaign month ago, but it's possible that IP has been remediated and\nno longer represents any threat.\n- Adversaries often use legitimate tools as network administrators such as `PsExec` or `AdFind`, these tools often find their\nway into indicator lists creating the potential for false positives.\n- It's possible after large and publicly written campaigns, curious employees might end up going directly to attacker infrastructure and generating these rules\n\n### Response and Remediation\n- If suspicious or malicious behavior is observed, immediate response should be taken to isolate activity to prevent further\npost-compromise behavior.\n- One example of a response if a machine matched a command and control IP address would be to add an entry to a network\ndevice such as a firewall or proxy appliance to prevent any outbound activity from leaving that machine.\n- Another example of a response with a malicious file hash match would involve validating if the file was properly quarantined,\nreview current running processes looking for any abnormal activity, and investigating for any other follow-up actions such as persistence or lateral movement\n", + "note": "## Triage and Analysis\n\n### Investigating Threat Intel Indicator Matches\n\nThreat Intel indicator match rules allow matching from a local observation such as an endpoint event that records a file\nhash with an entry of a file hash stored within the Threat Intel integrations. Matches can also occur on\nan IP address, registry path, URL, or imphash.\n\nThe matches will be based on the incoming last 30 days feed data so it's important to validate the data and review the results by\ninvestigating the associated activity to determine if it requires further investigation.\n\nIf an indicator matches a local observation, the following enriched fields will be generated to identify the indicator, field, and type matched.\n\n- `threat.indicator.matched.atomic` - this identifies the atomic indicator that matched the local observation\n- `threat.indicator.matched.field` - this identifies the indicator field that matched the local observation\n- `threat.indicator.matched.type` - this identifies the indicator type that matched the local observation\n\n#### Possible investigation steps:\n- Investigation should be validated and reviewed based on the data (file hash, registry path, URL, imphash) that was matched\nand by viewing the source of that activity.\n- Consider the history of the indicator that was matched. Has it happened before? Is it happening on multiple machines?\nThese kinds of questions can help understand if the activity is related to legitimate behavior.\n- Consider the user and their role within the company: is this something related to their job or work function?\n\n### False Positive Analysis\n- For any matches found, it's important to consider the initial release date of that indicator. Threat intelligence can\nbe a great tool for augmenting existing security processes, while at the same time it should be understood that threat\nintelligence can represent a specific set of activity observed at a point in time. For example, an IP address\nmay have hosted malware observed in a Dridex campaign months ago, but it's possible that IP has been remediated and\nno longer represents any threat.\n- Adversaries often use legitimate tools as network administrators such as `PsExec` or `AdFind`; these tools often find their\nway into indicator lists creating the potential for false positives.\n- It's possible after large and publicly written campaigns, curious employees might end up going directly to attacker infrastructure and triggering these rules.\n\n### Response and Remediation\n- If suspicious or malicious behavior is observed, take immediate action to isolate activity to prevent further\npost-compromise behavior.\n- One example of a response if a machine matched a command and control IP address would be to add an entry to a network\ndevice such as a firewall or proxy appliance to prevent any outbound activity from leaving that machine.\n- Another example of a response with a malicious file hash match would involve validating if the file was properly quarantined,\nreviewing current running processes for any abnormal activity, and investigating for any other follow-up actions such as persistence or lateral movement.\n", "query": "file.hash.*:* or file.pe.imphash:* or source.ip:* or destination.ip:* or url.full:* or registry.path:*\n", "references": [ "https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-threatintel.html" @@ -190,9 +190,9 @@ ] } ], - "threat_query": "@timestamp >= \"now-30d\" and event.dataset:ti_* and (threat.indicator.file.hash.*:* or threat.indicator.file.pe.imphash:* or threat.indicator.ip:* or threat.indicator.registry.path:* or threat.indicator.url.full:*)", + "threat_query": "@timestamp >= \"now-30d/d\" and event.dataset:ti_* and (threat.indicator.file.hash.*:* or threat.indicator.file.pe.imphash:* or threat.indicator.ip:* or threat.indicator.registry.path:* or threat.indicator.url.full:*)", "timeline_id": "495ad7a7-316e-4544-8a0f-9c098daee76e", "timeline_title": "Generic Threat Match Timeline", "type": "threat_match", - "version": 1 + "version": 3 } 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 f726434b279f2..74fb5bfe672a0 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 @@ -199,13 +199,13 @@ export interface CreateRulesOptions { export interface UpdateRulesOptions { rulesClient: RulesClient; defaultOutputIndex: string; - existingRule: SanitizedAlert | null | undefined; + existingRule: RuleAlertType | null | undefined; ruleUpdate: UpdateRulesSchema; } export interface PatchRulesOptions extends Partial { rulesClient: RulesClient; - rule: SanitizedAlert | null | undefined; + rule: RuleAlertType | null | undefined; } interface PatchRulesFieldsOptions { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts index 7cc709bbe8994..88d6114387aa3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts @@ -27,13 +27,13 @@ export const alertInstanceFactoryStub = < return {} as unknown as TInstanceState; }, replaceState(state: TInstanceState) { - return new Alert({ + return new Alert('', { state: {} as TInstanceState, meta: { lastScheduledActions: { group: 'default', date: new Date() } }, }); }, scheduleActions(actionGroup: TActionGroupIds, alertcontext: TInstanceContext) { - return new Alert({ + return new Alert('', { state: {} as TInstanceState, meta: { lastScheduledActions: { group: 'default', date: new Date() } }, }); @@ -43,9 +43,21 @@ export const alertInstanceFactoryStub = < subgroup: string, alertcontext: TInstanceContext ) { - return new Alert({ + return new Alert('', { state: {} as TInstanceState, meta: { lastScheduledActions: { group: 'default', date: new Date() } }, }); }, + setContext(alertContext: TInstanceContext) { + return new Alert('', { + state: {} as TInstanceState, + meta: { lastScheduledActions: { group: 'default', date: new Date() } }, + }); + }, + getContext() { + return {} as unknown as TInstanceContext; + }, + hasContext() { + return false; + }, }); diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/validation.test.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/validation.test.ts index 53d6279b06f63..61f305c22d796 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/validation.test.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/validation.test.ts @@ -5,29 +5,29 @@ * 2.0. */ -import { toHttpError, throwHttpError } from './validation'; +import { toAuthzError, throwAuthzError } from './validation'; -describe('toHttpError', () => { +describe('toAuthzError', () => { it('returns nothing if validation is valid', () => { - expect(toHttpError({ valid: true, message: undefined })).toBeUndefined(); + expect(toAuthzError({ valid: true, message: undefined })).toBeUndefined(); }); it('returns an HTTP error if validation is invalid', () => { - const error = toHttpError({ valid: false, message: 'validation message' }); + const error = toAuthzError({ valid: false, message: 'validation message' }); expect(error?.statusCode).toEqual(403); expect(error?.message).toEqual('validation message'); }); }); -describe('throwHttpError', () => { +describe('throwAuthzError', () => { it('does nothing if validation is valid', () => { - expect(() => throwHttpError({ valid: true, message: undefined })).not.toThrowError(); + expect(() => throwAuthzError({ valid: true, message: undefined })).not.toThrowError(); }); it('throws an error if validation is invalid', () => { let error; try { - throwHttpError({ valid: false, message: 'validation failed' }); + throwAuthzError({ valid: false, message: 'validation failed' }); } catch (e) { error = e; } diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/validation.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/validation.ts index 614eff01c2bbf..45d6ebf7a905f 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/validation.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/validation.ts @@ -20,14 +20,14 @@ export class HttpAuthzError extends Error { } } -export const toHttpError = (validation: Validation): HttpAuthzError | undefined => { +export const toAuthzError = (validation: Validation): HttpAuthzError | undefined => { if (!validation.valid) { return new HttpAuthzError(validation.message); } }; -export const throwHttpError = (validation: Validation): void => { - const error = toHttpError(validation); +export const throwAuthzError = (validation: Validation): void => { + const error = toAuthzError(validation); if (error) { throw error; } diff --git a/x-pack/plugins/security_solution/server/utils/promise_pool.test.ts b/x-pack/plugins/security_solution/server/utils/promise_pool.test.ts index 585044de5856a..1203c35a4dc61 100644 --- a/x-pack/plugins/security_solution/server/utils/promise_pool.test.ts +++ b/x-pack/plugins/security_solution/server/utils/promise_pool.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; import { initPromisePool } from './promise_pool'; const nextTick = () => new Promise((resolve) => setImmediate(resolve)); @@ -50,7 +51,11 @@ describe('initPromisePool', () => { executor: async (x) => x, }); - expect(results).toEqual([1, 2, 3]); + expect(results).toEqual([ + { item: 1, result: 1 }, + { item: 2, result: 2 }, + { item: 3, result: 3 }, + ]); expect(errors).toEqual([]); }); @@ -65,9 +70,9 @@ describe('initPromisePool', () => { expect(results).toEqual([]); expect(errors).toEqual([ - new Error(`Error processing 1`), - new Error(`Error processing 2`), - new Error(`Error processing 3`), + { item: 1, error: new Error(`Error processing 1`) }, + { item: 2, error: new Error(`Error processing 2`) }, + { item: 3, error: new Error(`Error processing 3`) }, ]); }); @@ -104,7 +109,7 @@ describe('initPromisePool', () => { asyncTasks[3].resolve(); await nextTick(); - // Check that all taks have been settled + // Check that all tasks have been settled expect(asyncTasks).toEqual({ 1: expect.objectContaining({ status: 'resolved' }), 2: expect.objectContaining({ status: 'rejected' }), @@ -114,8 +119,11 @@ describe('initPromisePool', () => { const { results, errors } = await promisePool; // Check final results - expect(results).toEqual([1, 3]); - expect(errors).toEqual([new Error(`Error processing 2`)]); + expect(results).toEqual([ + { item: 1, result: 1 }, + { item: 3, result: 3 }, + ]); + expect(errors).toEqual([{ item: 2, error: new Error(`Error processing 2`) }]); }); it('should be possible to configure concurrency', async () => { @@ -157,7 +165,7 @@ describe('initPromisePool', () => { asyncTasks[5].resolve(); await nextTick(); - // Check that all taks have been settled + // Check that all tasks have been settled expect(asyncTasks).toEqual({ 1: expect.objectContaining({ status: 'resolved' }), 2: expect.objectContaining({ status: 'rejected' }), @@ -169,8 +177,15 @@ describe('initPromisePool', () => { const { results, errors } = await promisePool; // Check final results - expect(results).toEqual([1, 4, 5]); - expect(errors).toEqual([new Error(`Error processing 2`), new Error(`Error processing 3`)]); + expect(results).toEqual([ + { item: 1, result: 1 }, + { item: 4, result: 4 }, + { item: 5, result: 5 }, + ]); + expect(errors).toEqual([ + { item: 2, error: new Error(`Error processing 2`) }, + { item: 3, error: new Error(`Error processing 3`) }, + ]); }); it('should not execute tasks if abortSignal is aborted', async () => { @@ -183,12 +198,17 @@ describe('initPromisePool', () => { abortSignal as AbortSignal ); - const { results, errors, abortedExecutionsCount } = await promisePool; + const { results, errors } = await promisePool; // Check final results expect(results).toEqual([]); - expect(errors).toEqual([]); - expect(abortedExecutionsCount).toEqual(5); + expect(errors).toEqual([ + { item: 1, error: new AbortError() }, + { item: 2, error: new AbortError() }, + { item: 3, error: new AbortError() }, + { item: 4, error: new AbortError() }, + { item: 5, error: new AbortError() }, + ]); }); it('should abort executions of tasks if abortSignal was set to aborted during execution', async () => { @@ -209,11 +229,13 @@ describe('initPromisePool', () => { abortSignal.aborted = true; - const { results, errors, abortedExecutionsCount } = await promisePool; + const { results, errors } = await promisePool; // Check final results - expect(results).toEqual([1]); - expect(errors).toEqual([]); - expect(abortedExecutionsCount).toEqual(2); + expect(results).toEqual([{ item: 1, result: 1 }]); + expect(errors).toEqual([ + { item: 2, error: new AbortError() }, + { item: 3, error: new AbortError() }, + ]); }); }); diff --git a/x-pack/plugins/security_solution/server/utils/promise_pool.ts b/x-pack/plugins/security_solution/server/utils/promise_pool.ts index ed0922b952c77..766ba873fad53 100644 --- a/x-pack/plugins/security_solution/server/utils/promise_pool.ts +++ b/x-pack/plugins/security_solution/server/utils/promise_pool.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; + interface PromisePoolArgs { concurrency?: number; items: Item[]; @@ -12,6 +14,21 @@ interface PromisePoolArgs { abortSignal?: AbortSignal; } +export interface PromisePoolError { + item: Item; + error: Error; +} + +export interface PromisePoolResult { + item: Item; + result: Result; +} + +export interface PromisePoolOutcome { + results: Array>; + errors: Array>; +} + /** * Runs promises in batches. It ensures that the number of running async tasks * doesn't exceed the concurrency parameter passed to the function. @@ -24,15 +41,14 @@ interface PromisePoolArgs { * @returns Struct holding results or errors of async tasks, aborted executions count if applicable */ -export const initPromisePool = async ({ +export const initPromisePool = async ({ concurrency = 1, items, executor, abortSignal, -}: PromisePoolArgs) => { +}: PromisePoolArgs): Promise> => { const tasks: Array> = []; - const results: Result[] = []; - const errors: unknown[] = []; + const outcome: PromisePoolOutcome = { results: [], errors: [] }; for (const item of items) { // Check if the pool is full @@ -41,17 +57,20 @@ export const initPromisePool = async ({ await Promise.race(tasks); } - // if abort signal was sent stop processing tasks further - if (abortSignal?.aborted === true) { - break; - } + const executeItem = async () => { + // if abort signal was sent stop processing tasks further + if (abortSignal?.aborted === true) { + throw new AbortError(); + } + return executor(item); + }; - const task: Promise = executor(item) + const task: Promise = executeItem() .then((result) => { - results.push(result); + outcome.results.push({ item, result }); }) .catch(async (error) => { - errors.push(error); + outcome.errors.push({ item, error }); }) .finally(() => { tasks.splice(tasks.indexOf(task), 1); @@ -63,10 +82,5 @@ export const initPromisePool = async ({ // Wait for all remaining tasks to finish await Promise.all(tasks); - const aborted = - abortSignal?.aborted === true - ? { abortedExecutionsCount: items.length - results.length - errors.length } - : undefined; - - return { results, errors, ...aborted }; + return outcome; }; diff --git a/x-pack/plugins/snapshot_restore/common/constants.ts b/x-pack/plugins/snapshot_restore/common/constants.ts index c4b64bb9395f8..d8b159fd01e96 100644 --- a/x-pack/plugins/snapshot_restore/common/constants.ts +++ b/x-pack/plugins/snapshot_restore/common/constants.ts @@ -35,12 +35,15 @@ export enum REPOSITORY_TYPES { } // Deliberately do not include `source` as a default repository since we treat it as a flag -export const DEFAULT_REPOSITORY_TYPES: RepositoryType[] = [ +export const ON_PREM_REPOSITORY_TYPES: RepositoryType[] = [ + REPOSITORY_TYPES.fs, + REPOSITORY_TYPES.url, +]; + +export const MODULE_REPOSITORY_TYPES: RepositoryType[] = [ REPOSITORY_TYPES.azure, REPOSITORY_TYPES.gcs, REPOSITORY_TYPES.s3, - REPOSITORY_TYPES.fs, - REPOSITORY_TYPES.url, ]; export const PLUGIN_REPOSITORY_TYPES: RepositoryType[] = [REPOSITORY_TYPES.hdfs]; diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts index 844be90f9b842..f29a84beaecf8 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { DEFAULT_REPOSITORY_TYPES, REPOSITORY_PLUGINS_MAP } from '../../../common'; +import { + ON_PREM_REPOSITORY_TYPES, + MODULE_REPOSITORY_TYPES, + REPOSITORY_PLUGINS_MAP, +} from '../../../common'; import { addBasePath } from '../helpers'; import { registerRepositoriesRoutes } from './repositories'; import { RouterMock, routeDependencies, RequestMock } from '../../test/helpers'; @@ -253,52 +257,59 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { path: addBasePath('repository_types'), }; - it('returns default types if no repository plugins returned from ES', async () => { - nodesInfoFn.mockResolvedValue({ nodes: { testNodeId: { plugins: [] } } }); - - const expectedResponse = [...DEFAULT_REPOSITORY_TYPES]; - await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); - }); - - it('returns default types with any repository plugins returned from ES', async () => { - const pluginNames = Object.keys(REPOSITORY_PLUGINS_MAP); - const pluginTypes = Object.entries(REPOSITORY_PLUGINS_MAP).map(([key, value]) => value); - - const mockEsResponse = { - nodes: { testNodeId: { plugins: [...pluginNames.map((key) => ({ name: key }))] } }, - }; - nodesInfoFn.mockResolvedValue(mockEsResponse); - - const expectedResponse = [...DEFAULT_REPOSITORY_TYPES, ...pluginTypes]; - await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); - }); - - it(`doesn't return non-repository plugins returned from ES`, async () => { - const pluginNames = ['foo-plugin', 'bar-plugin']; - const mockEsResponse = { - nodes: { testNodeId: { plugins: [...pluginNames.map((key) => ({ name: key }))] } }, - }; - nodesInfoFn.mockResolvedValue(mockEsResponse); - - const expectedResponse = [...DEFAULT_REPOSITORY_TYPES]; - - await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); - }); - - it(`doesn't return repository plugins that are not installed on all nodes`, async () => { - const dataNodePlugins = ['repository-hdfs']; - const masterNodePlugins: string[] = []; - const mockEsResponse = { - nodes: { - dataNode: { plugins: [...dataNodePlugins.map((key) => ({ name: key }))] }, - masterNode: { plugins: [...masterNodePlugins.map((key) => ({ name: key }))] }, - }, - }; - nodesInfoFn.mockResolvedValue(mockEsResponse); + // TODO add Cloud specific tests for repo types + describe('on prem', () => { + it('returns module types and on-prem types if no repository plugins returned from ES', async () => { + nodesInfoFn.mockResolvedValue({ nodes: { testNodeId: { plugins: [] } } }); + + const expectedResponse = [...MODULE_REPOSITORY_TYPES, ...ON_PREM_REPOSITORY_TYPES]; + await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); + }); + + it('returns module types and on-prem types with any repository plugins returned from ES', async () => { + const pluginNames = Object.keys(REPOSITORY_PLUGINS_MAP); + const pluginTypes = Object.entries(REPOSITORY_PLUGINS_MAP).map(([key, value]) => value); + + const mockEsResponse = { + nodes: { testNodeId: { plugins: [...pluginNames.map((key) => ({ name: key }))] } }, + }; + nodesInfoFn.mockResolvedValue(mockEsResponse); + + const expectedResponse = [ + ...MODULE_REPOSITORY_TYPES, + ...ON_PREM_REPOSITORY_TYPES, + ...pluginTypes, + ]; + await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); + }); + + it(`doesn't return non-repository plugins returned from ES`, async () => { + const pluginNames = ['foo-plugin', 'bar-plugin']; + const mockEsResponse = { + nodes: { testNodeId: { plugins: [...pluginNames.map((key) => ({ name: key }))] } }, + }; + nodesInfoFn.mockResolvedValue(mockEsResponse); + + const expectedResponse = [...MODULE_REPOSITORY_TYPES, ...ON_PREM_REPOSITORY_TYPES]; + + await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); + }); + + it(`doesn't return repository plugins that are not installed on all nodes`, async () => { + const dataNodePlugins = ['repository-hdfs']; + const masterNodePlugins: string[] = []; + const mockEsResponse = { + nodes: { + dataNode: { plugins: [...dataNodePlugins.map((key) => ({ name: key }))] }, + masterNode: { plugins: [...masterNodePlugins.map((key) => ({ name: key }))] }, + }, + }; + nodesInfoFn.mockResolvedValue(mockEsResponse); - const expectedResponse = [...DEFAULT_REPOSITORY_TYPES]; + const expectedResponse = [...MODULE_REPOSITORY_TYPES, ...ON_PREM_REPOSITORY_TYPES]; - await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); + await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse }); + }); }); it('should throw if ES error', async () => { diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts index 22c4d4a2b3e91..4666870133f1f 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts @@ -12,7 +12,11 @@ import type { PluginStats, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { DEFAULT_REPOSITORY_TYPES, REPOSITORY_PLUGINS_MAP } from '../../../common'; +import { + ON_PREM_REPOSITORY_TYPES, + REPOSITORY_PLUGINS_MAP, + MODULE_REPOSITORY_TYPES, +} from '../../../common'; import { Repository, RepositoryType } from '../../../common/types'; import { RouteDependencies } from '../../types'; import { addBasePath } from '../helpers'; @@ -154,8 +158,11 @@ export function registerRepositoriesRoutes({ { path: addBasePath('repository_types'), validate: false }, license.guardApiRoute(async (ctx, req, res) => { const { client: clusterClient } = ctx.core.elasticsearch; - // In ECE/ESS, do not enable the default types - const types: RepositoryType[] = isCloudEnabled ? [] : [...DEFAULT_REPOSITORY_TYPES]; + // module repo types are available everywhere out of the box + // on-prem repo types are not available on Cloud + const types: RepositoryType[] = isCloudEnabled + ? [...MODULE_REPOSITORY_TYPES] + : [...MODULE_REPOSITORY_TYPES, ...ON_PREM_REPOSITORY_TYPES]; try { const { nodes } = await clusterClient.asCurrentUser.nodes.info({ diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx index a625d9c193cd6..402d6fca7a1a9 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx @@ -13,6 +13,8 @@ import { XJsonMode } from '@kbn/ace'; import 'brace/theme/github'; import { + EuiFlexGroup, + EuiFlexItem, EuiButtonEmpty, EuiSpacer, EuiFormRow, @@ -20,6 +22,7 @@ import { EuiText, EuiTitle, EuiLink, + EuiIconTip, } from '@elastic/eui'; import { DocLinksStart, HttpSetup } from 'kibana/public'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -347,14 +350,31 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< )} - -

- + + +
+ +
+
+
+ + -
- + + ); return alertInstance; }, + done: () => ({ getRecoveredAlerts: () => [] }), }); describe('geo_containment', () => { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts index 9a5e0de9d53bf..4d8c1dc3d9b9f 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts @@ -96,6 +96,36 @@ describe('ActionContext', () => { - Value: 4 - Conditions Met: count between 4 and 5 over 5m +- Timestamp: 2020-01-01T00:00:00.000Z` + ); + }); + + it('generates expected properties if value is string', async () => { + const params = ParamsSchema.validate({ + index: '[index]', + timeField: '[timeField]', + aggType: 'count', + groupBy: 'top', + termField: 'x', + termSize: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: 'between', + threshold: [4, 5], + }); + const base: BaseActionContext = { + date: '2020-01-01T00:00:00.000Z', + group: '[group]', + value: 'unknown', + conditions: 'count between 4 and 5', + }; + const context = addMessages({ name: '[alert-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot(`"alert [alert-name] group [group] met threshold"`); + expect(context.message).toEqual( + `alert '[alert-name]' is active for group '[group]': + +- Value: unknown +- Conditions Met: count between 4 and 5 over 5m - Timestamp: 2020-01-01T00:00:00.000Z` ); }); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts index 69ca1c2700ebe..02450da5bbdf7 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts @@ -11,7 +11,7 @@ import { AlertExecutorOptions, AlertInstanceContext } from '../../../../alerting // alert type context provided to actions -type AlertInfo = Pick; +type RuleInfo = Pick; export interface ActionContext extends BaseActionContext { // a short pre-constructed message which may be used in an action field @@ -27,43 +27,72 @@ export interface BaseActionContext extends AlertInstanceContext { // the date the alert was run as an ISO date date: string; // the value that met the threshold - value: number; + value: number | string; // threshold conditions conditions: string; } -export function addMessages( - alertInfo: AlertInfo, - baseContext: BaseActionContext, - params: Params -): ActionContext { - const title = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeContextSubjectTitle', { +const DEFAULT_TITLE = (name: string, group: string) => + i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeContextSubjectTitle', { defaultMessage: 'alert {name} group {group} met threshold', + values: { name, group }, + }); + +const RECOVERY_TITLE = (name: string, group: string) => + i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeRecoveryContextSubjectTitle', { + defaultMessage: 'alert {name} group {group} recovered', + values: { name, group }, + }); + +const DEFAULT_MESSAGE = (name: string, context: BaseActionContext, window: string) => + i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeContextMessageDescription', { + defaultMessage: `alert '{name}' is active for group '{group}': + +- Value: {value} +- Conditions Met: {conditions} over {window} +- Timestamp: {date}`, values: { - name: alertInfo.name, - group: baseContext.group, + name, + group: context.group, + value: context.value, + conditions: context.conditions, + window, + date: context.date, }, }); - const window = `${params.timeWindowSize}${params.timeWindowUnit}`; - const message = i18n.translate( - 'xpack.stackAlerts.indexThreshold.alertTypeContextMessageDescription', - { - defaultMessage: `alert '{name}' is active for group '{group}': +const RECOVERY_MESSAGE = (name: string, context: BaseActionContext, window: string) => + i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeRecoveryContextMessageDescription', { + defaultMessage: `alert '{name}' is recovered for group '{group}': - Value: {value} - Conditions Met: {conditions} over {window} - Timestamp: {date}`, - values: { - name: alertInfo.name, - group: baseContext.group, - value: baseContext.value, - conditions: baseContext.conditions, - window, - date: baseContext.date, - }, - } - ); + values: { + name, + group: context.group, + value: context.value, + conditions: context.conditions, + window, + date: context.date, + }, + }); + +export function addMessages( + ruleInfo: RuleInfo, + baseContext: BaseActionContext, + params: Params, + isRecoveryMessage?: boolean +): ActionContext { + const title = isRecoveryMessage + ? RECOVERY_TITLE(ruleInfo.name, baseContext.group) + : DEFAULT_TITLE(ruleInfo.name, baseContext.group); + + const window = `${params.timeWindowSize}${params.timeWindowUnit}`; + + const message = isRecoveryMessage + ? RECOVERY_MESSAGE(ruleInfo.name, baseContext, window) + : DEFAULT_MESSAGE(ruleInfo.name, baseContext, window); return { ...baseContext, title, message }; } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index 0eb2810626ac3..7725721ed8efa 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -128,12 +128,13 @@ export function getAlertType( isExportable: true, executor, producer: STACK_ALERTS_FEATURE_ID, + doesSetRecoveryContext: true, }; async function executor( options: AlertExecutorOptions ) { - const { alertId, name, services, params } = options; + const { alertId: ruleId, name, services, params } = options; const { alertFactory, search } = services; const compareFn = ComparatorFns.get(params.thresholdComparator); @@ -173,19 +174,22 @@ export function getAlertType( abortableEsClient, query: queryParams, }); - logger.debug(`alert ${ID}:${alertId} "${name}" query result: ${JSON.stringify(result)}`); + logger.debug(`rule ${ID}:${ruleId} "${name}" query result: ${JSON.stringify(result)}`); + + const unmetGroupValues: Record = {}; + const agg = params.aggField ? `${params.aggType}(${params.aggField})` : `${params.aggType}`; const groupResults = result.results || []; // console.log(`index_threshold: response: ${JSON.stringify(groupResults, null, 4)}`); for (const groupResult of groupResults) { - const instanceId = groupResult.group; + const alertId = groupResult.group; const metric = groupResult.metrics && groupResult.metrics.length > 0 ? groupResult.metrics[0] : null; const value = metric && metric.length === 2 ? metric[1] : null; if (value === null || value === undefined) { logger.debug( - `alert ${ID}:${alertId} "${name}": no metrics found for group ${instanceId}} from groupResult ${JSON.stringify( + `rule ${ID}:${ruleId} "${name}": no metrics found for group ${alertId}} from groupResult ${JSON.stringify( groupResult )}` ); @@ -194,23 +198,41 @@ export function getAlertType( const met = compareFn(value, params.threshold); - if (!met) continue; + if (!met) { + unmetGroupValues[alertId] = value; + continue; + } - const agg = params.aggField ? `${params.aggType}(${params.aggField})` : `${params.aggType}`; const humanFn = `${agg} is ${getHumanReadableComparator( params.thresholdComparator )} ${params.threshold.join(' and ')}`; const baseContext: BaseActionContext = { date, - group: instanceId, + group: alertId, value, conditions: humanFn, }; const actionContext = addMessages(options, baseContext, params); - const alertInstance = alertFactory.create(instanceId); - alertInstance.scheduleActions(ActionGroupId, actionContext); + const alert = alertFactory.create(alertId); + alert.scheduleActions(ActionGroupId, actionContext); logger.debug(`scheduled actionGroup: ${JSON.stringify(actionContext)}`); } + + const { getRecoveredAlerts } = services.alertFactory.done(); + for (const recoveredAlert of getRecoveredAlerts()) { + const alertId = recoveredAlert.getId(); + logger.debug(`setting context for recovered alert ${alertId}`); + const baseContext: BaseActionContext = { + date, + value: unmetGroupValues[alertId] ?? 'unknown', + group: alertId, + conditions: `${agg} is NOT ${getHumanReadableComparator( + params.thresholdComparator + )} ${params.threshold.join(' and ')}`, + }; + const recoveryContext = addMessages(options, baseContext, params, true); + recoveredAlert.setContext(recoveryContext); + } } } diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx index a83cca49c1fd7..18ddda0791c57 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx @@ -8,6 +8,8 @@ import React, { memo } from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { TimelinesStartServices } from '../../../../types'; import { useAddToCase } from '../../../../hooks/use_add_to_case'; import { AddToCaseActionProps } from './add_to_case_action'; import * as i18n from './translations'; @@ -25,7 +27,7 @@ const AddToNewCaseButtonComponent: React.FC = ({ owner, onClose, }) => { - const { addNewCaseClick, isDisabled, userCanCrud } = useAddToCase({ + const { isDisabled, userCanCrud, caseAttachments, onCaseSuccess, onCaseCreated } = useAddToCase({ event, useInsertTimeline, casePermissions, @@ -33,6 +35,20 @@ const AddToNewCaseButtonComponent: React.FC = ({ owner, onClose, }); + const { cases } = useKibana().services; + const createCaseFlyout = cases.hooks.getUseCasesAddToNewCaseFlyout({ + attachments: caseAttachments, + afterCaseCreated: onCaseCreated, + onSuccess: onCaseSuccess, + }); + + const handleClick = () => { + // close the popover + if (onClose) { + onClose(); + } + createCaseFlyout.open(); + }; return ( <> @@ -40,7 +56,7 @@ const AddToNewCaseButtonComponent: React.FC = ({ void; @@ -32,6 +32,7 @@ interface UseAddToCase { closePopover: () => void; isPopoverOpen: boolean; isCreateCaseFlyoutOpen: boolean; + caseAttachments?: CaseAttachments; } export const useAddToCase = ({ @@ -39,6 +40,7 @@ export const useAddToCase = ({ casePermissions, appId, onClose, + owner, }: AddToCaseActionProps): UseAddToCase => { const eventId = event?.ecs._id ?? ''; const dispatch = useDispatch(); @@ -109,6 +111,23 @@ export const useAddToCase = ({ }, [onViewCaseClick, toasts, dispatch, eventId] ); + const caseAttachments: CaseAttachments = useMemo(() => { + const eventIndex = event?.ecs._index ?? ''; + const { ruleId, ruleName } = normalizedEventFields(event); + const attachments = [ + { + alertId: eventId, + index: eventIndex ?? '', + rule: { + id: ruleId, + name: ruleName, + }, + owner, + type: CommentType.alert as const, + }, + ]; + return attachments; + }, [event, eventId, owner]); const onCaseClicked = useCallback( (theCase?: Case) => { @@ -140,6 +159,7 @@ export const useAddToCase = ({ } }, [onClose, closePopover, dispatch, eventId]); return { + caseAttachments, addNewCaseClick, addExistingCaseClick, onCaseClicked, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 481c7e3911753..cf434780c4db1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -654,8 +654,8 @@ "xpack.lens.pieChart.showPercentValuesLabel": "割合を表示", "xpack.lens.pieChart.showTreemapCategoriesLabel": "ラベルを表示", "xpack.lens.pieChart.valuesLabel": "ラベル", - "xpack.lens.referenceLineMarker.positionRequirementTooltip": "位置を変更するには、アイコンを選択するか、表示名を表示する必要があります", - "xpack.lens.referenceLineMarker.textVisibility": "表示名を表示", + "xpack.lens.lineMarker.positionRequirementTooltip": "位置を変更するには、アイコンを選択するか、表示名を表示する必要があります", + "xpack.lens.lineMarker.textVisibility": "表示名を表示", "xpack.lens.resetLayerAriaLabel": "レイヤー {index} をリセット", "xpack.lens.resetVisualizationAriaLabel": "ビジュアライゼーションをリセット", "xpack.lens.searchTitle": "Lens:ビジュアライゼーションを作成", @@ -762,8 +762,6 @@ "xpack.lens.xyChart.bottomAxisDisabledHelpText": "この設定は、下の軸が有効であるときにのみ適用されます。", "xpack.lens.xyChart.bottomAxisLabel": "下の軸", "xpack.lens.xyChart.boundaryError": "下界は上界よりも大きくなければなりません", - "xpack.lens.xyChart.chartTypeLabel": "チャートタイプ", - "xpack.lens.xyChart.chartTypeLegend": "チャートタイプ", "xpack.lens.xyChart.curveStyleLabel": "曲線", "xpack.lens.xyChart.curveType.help": "折れ線グラフで曲線タイプをレンダリングする方法を定義します", "xpack.lens.xyChart.emptyXLabel": "(空)", @@ -804,30 +802,30 @@ "xpack.lens.xyChart.nestUnderRoot": "データセット全体", "xpack.lens.shared.overwriteAxisTitle": "軸タイトルを上書き", "xpack.lens.xyChart.position.help": "凡例の配置を指定します。", - "xpack.lens.xyChart.referenceLine.alertIconLabel": "アラート", - "xpack.lens.xyChart.referenceLine.asteriskIconLabel": "アスタリスク", - "xpack.lens.xyChart.referenceLine.bellIconLabel": "ベル", - "xpack.lens.xyChart.referenceLine.boltIconLabel": "ボルト", - "xpack.lens.xyChart.referenceLine.bugIconLabel": "バグ", - "xpack.lens.xyChart.referenceLine.commentIconLabel": "コメント", - "xpack.lens.xyChart.referenceLine.flagIconLabel": "旗", - "xpack.lens.xyChart.referenceLine.noIconLabel": "なし", - "xpack.lens.xyChart.referenceLine.tagIconLabel": "タグ", - "xpack.lens.xyChart.referenceLineColor.label": "色", - "xpack.lens.xyChart.referenceLineFill.above": "上", - "xpack.lens.xyChart.referenceLineFill.after": "後", - "xpack.lens.xyChart.referenceLineFill.before": "前", - "xpack.lens.xyChart.referenceLineFill.below": "下", - "xpack.lens.xyChart.referenceLineFill.label": "塗りつぶし", - "xpack.lens.xyChart.referenceLineFill.none": "なし", - "xpack.lens.xyChart.referenceLineMarker.auto": "自動", - "xpack.lens.xyChart.referenceLineMarker.icon": "アイコン", - "xpack.lens.xyChart.referenceLineMarker.position": "装飾位置", - "xpack.lens.xyChart.referenceLineStyle.dashed": "鎖線", - "xpack.lens.xyChart.referenceLineStyle.dotted": "点線", - "xpack.lens.xyChart.referenceLineStyle.label": "ラインスタイル", - "xpack.lens.xyChart.referenceLineStyle.solid": "塗りつぶし", - "xpack.lens.xyChart.referenceLineThickness.label": "線の太さです", + "xpack.lens.xyChart.iconSelect.alertIconLabel": "アラート", + "xpack.lens.xyChart.iconSelect.asteriskIconLabel": "アスタリスク", + "xpack.lens.xyChart.iconSelect.bellIconLabel": "ベル", + "xpack.lens.xyChart.iconSelect.boltIconLabel": "ボルト", + "xpack.lens.xyChart.iconSelect.bugIconLabel": "バグ", + "xpack.lens.xyChart.iconSelect.commentIconLabel": "コメント", + "xpack.lens.xyChart.iconSelect.flagIconLabel": "旗", + "xpack.lens.xyChart.iconSelect.noIconLabel": "なし", + "xpack.lens.xyChart.iconSelect.tagIconLabel": "タグ", + "xpack.lens.xyChart.lineColor.label": "色", + "xpack.lens.xyChart.fill.above": "上", + "xpack.lens.xyChart.fill.after": "後", + "xpack.lens.xyChart.fill.before": "前", + "xpack.lens.xyChart.fill.below": "下", + "xpack.lens.xyChart.fill.label": "塗りつぶし", + "xpack.lens.xyChart.fill.none": "なし", + "xpack.lens.xyChart.lineMarker.auto": "自動", + "xpack.lens.xyChart.lineMarker.icon": "アイコン", + "xpack.lens.xyChart.lineMarker.position": "装飾位置", + "xpack.lens.xyChart.lineStyle.dashed": "鎖線", + "xpack.lens.xyChart.lineStyle.dotted": "点線", + "xpack.lens.xyChart.lineStyle.label": "ラインスタイル", + "xpack.lens.xyChart.lineStyle.solid": "塗りつぶし", + "xpack.lens.xyChart.lineThickness.label": "線の太さです", "xpack.lens.xyChart.renderer.help": "X/Y チャートを再レンダリング", "xpack.lens.xyChart.rightAxisDisabledHelpText": "この設定は、右の軸が有効であるときにのみ適用されます。", "xpack.lens.xyChart.rightAxisLabel": "右の軸", @@ -4749,11 +4747,6 @@ "share.contextMenu.permalinkPanelTitle": "パーマリンク", "share.contextMenu.permalinksLabel": "パーマリンク", "share.contextMenuTitle": "この {objectType} を共有", - "share.urlGenerators.error.createUrlFnProvided": "このジェネレーターは非推奨とマークされています。createUrl fn を付けないでください。", - "share.urlGenerators.error.migrationFnGivenNotDeprecated": "移行機能を提供する場合、このジェネレーターに非推奨マークを付ける必要があります", - "share.urlGenerators.error.noCreateUrlFnProvided": "このジェネレーターには非推奨のマークがありません。createUrl fn を付けてください。", - "share.urlGenerators.error.noMigrationFnProvided": "アクセスリンクジェネレーターに非推奨マークが付いている場合、移行機能を提供する必要があります。", - "share.urlGenerators.errors.noGeneratorWithId": "{id} という ID のジェネレーターはありません", "share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。", "share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー", "share.urlPanel.copyLinkButtonLabel": "リンクをコピー", @@ -10666,7 +10659,6 @@ "xpack.enterpriseSearch.workplaceSearch.nav.sources": "ソース", "xpack.enterpriseSearch.workplaceSearch.nav.synchronization": "同期", "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationFrequency": "頻度", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationObjectsAndAssets": "オブジェクトとアセット", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthDescription": "Workplace Search検索APIを安全に使用するために、OAuthアプリケーションを構成します。プラチナライセンスにアップグレードして、検索APIを有効にし、OAuthアプリケーションを作成します。", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthTitle": "カスタム検索アプリケーションのOAuthを構成", "xpack.enterpriseSearch.workplaceSearch.oauth.description": "組織のOAuthクライアントを作成します。", @@ -10920,8 +10912,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePoint": "SharePoint Online", "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.slack": "Slack", "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zendesk": "Zendesk", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceObjectsAndAssetsDescription": "このコンテンツソースからWorkplace Searchに同期されるオブジェクトとアセットを決定するインデックスルールをカスタマイズします。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceObjectsAndAssetsLabel": "検索結果に含めるオブジェクトと詳細", "xpack.enterpriseSearch.workplaceSearch.sources.sourceOverviewTitle": "ソース概要", "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmMessage": "この要求を続行し、他のすべての同期を停止しますか?", "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmTitle": "新しいコンテンツ同期を開始しますか?", @@ -20454,7 +20444,6 @@ "xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage": "レポートジョブデータの解読に失敗しました。{encryptionKey}が設定されていることを確認してこのレポートを再生成してください。{err}", "xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage": "ジョブヘッダーがありません", "xpack.reporting.exportTypes.csv.generateCsv.escapedFormulaValues": "CSVには、値がエスケープされた式が含まれる場合があります", - "xpack.reporting.exportTypes.printablePdf.documentStreamIsNotgeneratedErrorMessage": "ドキュメントストリームが生成されていません。", "xpack.reporting.exportTypes.printablePdf.logoDescription": "Elastic 提供", "xpack.reporting.exportTypes.printablePdf.pagingDescription": "{pageCount} ページ中 {currentPage} ページ目", "xpack.reporting.jobCreatedBy.unknownUserPlaceholderText": "不明", @@ -23354,7 +23343,6 @@ "xpack.securitySolution.eventsViewer.alerts.defaultHeaders.triggeredTitle": "実行済み", "xpack.securitySolution.eventsViewer.alerts.defaultHeaders.versionTitle": "バージョン", "xpack.securitySolution.eventsViewer.alerts.overviewTable.signalStatusTitle": "ステータス", - "xpack.securitySolution.eventsViewer.alerts.overviewTable.targetImportHash": "ハッシュのインポート", "xpack.securitySolution.eventsViewer.errorFetchingEventsData": "イベントデータをクエリできませんでした", "xpack.securitySolution.eventsViewer.eventsLabel": "イベント", "xpack.securitySolution.eventsViewer.showingLabel": "表示中", @@ -28126,4 +28114,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 9fe16bef172a0..d6fd6eda4ff49 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -666,8 +666,8 @@ "xpack.lens.pieChart.showPercentValuesLabel": "显示百分比", "xpack.lens.pieChart.showTreemapCategoriesLabel": "显示标签", "xpack.lens.pieChart.valuesLabel": "标签", - "xpack.lens.referenceLineMarker.positionRequirementTooltip": "必须选择图标或显示显示名称才能更改其位置", - "xpack.lens.referenceLineMarker.textVisibility": "显示显示名称", + "xpack.lens.lineMarker.positionRequirementTooltip": "必须选择图标或显示显示名称才能更改其位置", + "xpack.lens.lineMarker.textVisibility": "显示显示名称", "xpack.lens.resetLayerAriaLabel": "重置图层 {index}", "xpack.lens.resetVisualizationAriaLabel": "重置可视化", "xpack.lens.searchTitle": "Lens:创建可视化", @@ -774,8 +774,6 @@ "xpack.lens.xyChart.bottomAxisDisabledHelpText": "此设置仅在启用底轴时应用。", "xpack.lens.xyChart.bottomAxisLabel": "底轴", "xpack.lens.xyChart.boundaryError": "下边界必须大于上边界", - "xpack.lens.xyChart.chartTypeLabel": "图表类型", - "xpack.lens.xyChart.chartTypeLegend": "图表类型", "xpack.lens.xyChart.curveStyleLabel": "曲线", "xpack.lens.xyChart.curveType.help": "定义为折线图渲染曲线类型的方式", "xpack.lens.xyChart.emptyXLabel": "(空)", @@ -816,30 +814,30 @@ "xpack.lens.xyChart.nestUnderRoot": "整个数据集", "xpack.lens.shared.overwriteAxisTitle": "覆盖轴标题", "xpack.lens.xyChart.position.help": "指定图例位置。", - "xpack.lens.xyChart.referenceLine.alertIconLabel": "告警", - "xpack.lens.xyChart.referenceLine.asteriskIconLabel": "星号", - "xpack.lens.xyChart.referenceLine.bellIconLabel": "钟铃", - "xpack.lens.xyChart.referenceLine.boltIconLabel": "闪电", - "xpack.lens.xyChart.referenceLine.bugIconLabel": "昆虫", - "xpack.lens.xyChart.referenceLine.commentIconLabel": "注释", - "xpack.lens.xyChart.referenceLine.flagIconLabel": "旗帜", - "xpack.lens.xyChart.referenceLine.noIconLabel": "无", - "xpack.lens.xyChart.referenceLine.tagIconLabel": "标签", - "xpack.lens.xyChart.referenceLineColor.label": "颜色", - "xpack.lens.xyChart.referenceLineFill.above": "之上", - "xpack.lens.xyChart.referenceLineFill.after": "之后", - "xpack.lens.xyChart.referenceLineFill.before": "之前", - "xpack.lens.xyChart.referenceLineFill.below": "之下", - "xpack.lens.xyChart.referenceLineFill.label": "填充", - "xpack.lens.xyChart.referenceLineFill.none": "无", - "xpack.lens.xyChart.referenceLineMarker.auto": "自动", - "xpack.lens.xyChart.referenceLineMarker.icon": "图标", - "xpack.lens.xyChart.referenceLineMarker.position": "装饰位置", - "xpack.lens.xyChart.referenceLineStyle.dashed": "虚线", - "xpack.lens.xyChart.referenceLineStyle.dotted": "点线", - "xpack.lens.xyChart.referenceLineStyle.label": "线条样式", - "xpack.lens.xyChart.referenceLineStyle.solid": "纯色", - "xpack.lens.xyChart.referenceLineThickness.label": "线条粗细", + "xpack.lens.xyChart.iconSelect.alertIconLabel": "告警", + "xpack.lens.xyChart.iconSelect.asteriskIconLabel": "星号", + "xpack.lens.xyChart.iconSelect.bellIconLabel": "钟铃", + "xpack.lens.xyChart.iconSelect.boltIconLabel": "闪电", + "xpack.lens.xyChart.iconSelect.bugIconLabel": "昆虫", + "xpack.lens.xyChart.iconSelect.commentIconLabel": "注释", + "xpack.lens.xyChart.iconSelect.flagIconLabel": "旗帜", + "xpack.lens.xyChart.iconSelect.noIconLabel": "无", + "xpack.lens.xyChart.iconSelect.tagIconLabel": "标签", + "xpack.lens.xyChart.lineColor.label": "颜色", + "xpack.lens.xyChart.fill.above": "之上", + "xpack.lens.xyChart.fill.after": "之后", + "xpack.lens.xyChart.fill.before": "之前", + "xpack.lens.xyChart.fill.below": "之下", + "xpack.lens.xyChart.fill.label": "填充", + "xpack.lens.xyChart.fill.none": "无", + "xpack.lens.xyChart.lineMarker.auto": "自动", + "xpack.lens.xyChart.lineMarker.icon": "图标", + "xpack.lens.xyChart.lineMarker.position": "装饰位置", + "xpack.lens.xyChart.lineStyle.dashed": "虚线", + "xpack.lens.xyChart.lineStyle.dotted": "点线", + "xpack.lens.xyChart.lineStyle.label": "线条样式", + "xpack.lens.xyChart.lineStyle.solid": "纯色", + "xpack.lens.xyChart.lineThickness.label": "线条粗细", "xpack.lens.xyChart.renderer.help": "X/Y 图表呈现器", "xpack.lens.xyChart.rightAxisDisabledHelpText": "此设置仅在启用右轴时应用。", "xpack.lens.xyChart.rightAxisLabel": "右轴", @@ -4548,11 +4546,6 @@ "share.contextMenu.permalinkPanelTitle": "固定链接", "share.contextMenu.permalinksLabel": "固定链接", "share.contextMenuTitle": "共享此 {objectType}", - "share.urlGenerators.error.createUrlFnProvided": "此生成器标记为已过时。切勿提供 createUrl 函数。", - "share.urlGenerators.error.migrationFnGivenNotDeprecated": "如果提供了迁移函数,则必须将此生成器标记为已过时", - "share.urlGenerators.error.noCreateUrlFnProvided": "此生成器未标记为已过时。请提供 createUrl 函数。", - "share.urlGenerators.error.noMigrationFnProvided": "如果访问链接生成器标记为已过时,则必须提供迁移函数。", - "share.urlGenerators.errors.noGeneratorWithId": "未找到 ID 为 {id} 的生成器", "share.urlPanel.canNotShareAsSavedObjectHelpText": "只有保存 {objectType} 后,才能共享为已保存对象。", "share.urlPanel.copyIframeCodeButtonLabel": "复制 iFrame 代码", "share.urlPanel.copyLinkButtonLabel": "复制链接", @@ -10524,7 +10517,6 @@ "xpack.enterpriseSearch.workplaceSearch.nav.sources": "源", "xpack.enterpriseSearch.workplaceSearch.nav.synchronization": "同步", "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationFrequency": "频率", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationObjectsAndAssets": "对象和资产", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthDescription": "配置 OAuth 应用程序,以安全使用 Workplace Search 搜索 API。升级到白金级许可证,以启用搜索 API 并创建您的 OAuth 应用程序。", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthTitle": "正在为定制搜索应用程序配置 OAuth", "xpack.enterpriseSearch.workplaceSearch.oauth.description": "为您的组织创建 OAuth 客户端。", @@ -10779,8 +10771,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePoint": "Sharepoint", "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.slack": "Slack", "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zendesk": "Zendesk", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceObjectsAndAssetsDescription": "定制确定将哪些对象和资产从此内容源同步到 Workplace Search 的索引规则。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceObjectsAndAssetsLabel": "要包括在搜索结果中的对象和详情", "xpack.enterpriseSearch.workplaceSearch.sources.sourceOverviewTitle": "源概览", "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmMessage": "是否确定要继续处理此请求并停止所有其他同步?", "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmTitle": "开始新内容同步?", @@ -20510,7 +20500,6 @@ "xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage": "无法解密报告作业数据。请确保已设置 {encryptionKey},然后重新生成此报告。{err}", "xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage": "作业标头缺失", "xpack.reporting.exportTypes.csv.generateCsv.escapedFormulaValues": "CSV 可能包含值已转义的公式", - "xpack.reporting.exportTypes.printablePdf.documentStreamIsNotgeneratedErrorMessage": "尚未生成文档流", "xpack.reporting.exportTypes.printablePdf.logoDescription": "由 Elastic 提供支持", "xpack.reporting.exportTypes.printablePdf.pagingDescription": "第 {currentPage} 页,共 {pageCount} 页", "xpack.reporting.jobCreatedBy.unknownUserPlaceholderText": "未知", @@ -23483,7 +23472,6 @@ "xpack.securitySolution.eventsViewer.alerts.defaultHeaders.triggeredTitle": "已触发", "xpack.securitySolution.eventsViewer.alerts.defaultHeaders.versionTitle": "版本", "xpack.securitySolution.eventsViewer.alerts.overviewTable.signalStatusTitle": "状态", - "xpack.securitySolution.eventsViewer.alerts.overviewTable.targetImportHash": "导入哈希", "xpack.securitySolution.eventsViewer.errorFetchingEventsData": "无法查询事件数据", "xpack.securitySolution.eventsViewer.eventsLabel": "事件", "xpack.securitySolution.eventsViewer.showingLabel": "正在显示", @@ -28355,4 +28343,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts index 30e594f35d1f8..c2ab2936e0cc7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts @@ -191,17 +191,30 @@ describe('transformActionVariables', () => { ]); }); - test('should return only the required action variables when omitOptionalMessageVariables is provided', () => { + test(`should return only the required action variables when omitMessageVariables is "all"`, () => { const alertType = getAlertType({ context: mockContextVariables(), state: mockStateVariables(), params: mockParamsVariables(), }); - expect(transformActionVariables(alertType.actionVariables, true)).toEqual([ + expect(transformActionVariables(alertType.actionVariables, 'all')).toEqual([ ...expectedTransformResult, ...expectedParamsTransformResult(), ]); }); + + test(`should return required and context action variables when omitMessageVariables is "keepContext"`, () => { + const alertType = getAlertType({ + context: mockContextVariables(), + state: mockStateVariables(), + params: mockParamsVariables(), + }); + expect(transformActionVariables(alertType.actionVariables, 'keepContext')).toEqual([ + ...expectedTransformResult, + ...expectedContextTransformResult(), + ...expectedParamsTransformResult(), + ]); + }); }); function getAlertType(actionVariables: ActionVariables): RuleType { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts index 2cf1df85a3447..6aff0781a56c2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts @@ -7,16 +7,20 @@ import { i18n } from '@kbn/i18n'; import { pick } from 'lodash'; -import { ActionVariables, REQUIRED_ACTION_VARIABLES } from '../../types'; +import { ActionVariables, REQUIRED_ACTION_VARIABLES, CONTEXT_ACTION_VARIABLES } from '../../types'; import { ActionVariable } from '../../../../alerting/common'; +export type OmitMessageVariablesType = 'all' | 'keepContext'; + // return a "flattened" list of action variables for an alertType export function transformActionVariables( actionVariables: ActionVariables, - omitOptionalMessageVariables?: boolean + omitMessageVariables?: OmitMessageVariablesType ): ActionVariable[] { - const filteredActionVariables: ActionVariables = omitOptionalMessageVariables - ? pick(actionVariables, ...REQUIRED_ACTION_VARIABLES) + const filteredActionVariables: ActionVariables = omitMessageVariables + ? omitMessageVariables === 'all' + ? pick(actionVariables, REQUIRED_ACTION_VARIABLES) + : pick(actionVariables, [...REQUIRED_ACTION_VARIABLES, ...CONTEXT_ACTION_VARIABLES]) : actionVariables; const alwaysProvidedVars = getAlwaysProvidedActionVariables(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rule_types.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rule_types.ts index 99478f250f6a2..63207dd35dfac 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rule_types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rule_types.ts @@ -22,6 +22,7 @@ const rewriteBodyReq: RewriteRequestCase = ({ action_variables: actionVariables, authorized_consumers: authorizedConsumers, rule_task_timeout: ruleTaskTimeout, + does_set_recovery_context: doesSetRecoveryContext, ...rest }: AsApiContract) => ({ enabledInLicense, @@ -32,6 +33,7 @@ const rewriteBodyReq: RewriteRequestCase = ({ actionVariables, authorizedConsumers, ruleTaskTimeout, + doesSetRecoveryContext, ...rest, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 836e63fa7a68a..f25827fb4ba99 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -40,9 +40,10 @@ import { useKibana } from '../../../common/lib/kibana'; import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params'; import { ConnectorAddModal } from '.'; import { suspendedComponentWithProps } from '../../lib/suspended_component_with_props'; +import { OmitMessageVariablesType } from '../../lib/action_variables'; export interface ActionGroupWithMessageVariables extends ActionGroup { - omitOptionalMessageVariables?: boolean; + omitMessageVariables?: OmitMessageVariablesType; defaultActionMessage?: string; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index 4a4230c233dfa..4a27c4c1e6fef 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -346,7 +346,7 @@ function getAvailableActionVariables( ) { const transformedActionVariables: ActionVariable[] = transformActionVariables( actionVariables, - actionGroup?.omitOptionalMessageVariables + actionGroup?.omitMessageVariables ); // partition deprecated items so they show up last diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 06542cbb3a1a4..fa226c4a74cdd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -556,7 +556,9 @@ export const AlertForm = ({ actionGroup.id === selectedAlertType.recoveryActionGroup.id ? { ...actionGroup, - omitOptionalMessageVariables: true, + omitMessageVariables: selectedAlertType.doesSetRecoveryContext + ? 'keepContext' + : 'all', defaultActionMessage: recoveredActionGroupMessage, } : { ...actionGroup, defaultActionMessage: alertTypeModel?.defaultActionMessage } diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 72fd48d355774..718a637518cbe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -205,7 +205,8 @@ type AsActionVariables = { [Req in Keys]: ActionVariable[]; }; export const REQUIRED_ACTION_VARIABLES = ['params'] as const; -export const OPTIONAL_ACTION_VARIABLES = ['state', 'context'] as const; +export const CONTEXT_ACTION_VARIABLES = ['context'] as const; +export const OPTIONAL_ACTION_VARIABLES = [...CONTEXT_ACTION_VARIABLES, 'state'] as const; export type ActionVariables = AsActionVariables & Partial>; @@ -224,6 +225,7 @@ export interface RuleType< | 'ruleTaskTimeout' | 'defaultScheduleInterval' | 'minimumScheduleInterval' + | 'doesSetRecoveryContext' > { actionVariables: ActionVariables; authorizedConsumers: Record; diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts b/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts index f001cbbc10bbd..595dd1a3e7721 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts @@ -37,6 +37,7 @@ export enum ConfigKey { REQUEST_HEADERS_CHECK = 'check.request.headers', REQUEST_METHOD_CHECK = 'check.request.method', REQUEST_SEND_CHECK = 'check.send', + REVISION = 'revision', SCHEDULE = 'schedule', SCREENSHOTS = 'screenshots', SOURCE_INLINE = 'source.inline.script', diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts b/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts index 26e3d726a10c0..56bc00290eecf 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts @@ -30,27 +30,34 @@ export const ServiceLocationCodec = t.interface({ }); export const ServiceLocationErrors = t.array( - t.intersection([ - t.interface({ - locationId: t.string, - error: t.interface({ + t.interface({ + locationId: t.string, + error: t.intersection([ + t.interface({ reason: t.string, status: t.number, }), - }), - t.partial({ - failed_monitors: t.array( - t.interface({ - id: t.string, - message: t.string, - }) - ), - }), - ]) + t.partial({ + failed_monitors: t.array( + t.interface({ + id: t.string, + message: t.string, + }) + ), + }), + ]), + }) ); export const ServiceLocationsCodec = t.array(ServiceLocationCodec); +export const LocationCodec = t.intersection([ + ServiceLocationCodec, + t.partial({ isServiceManaged: t.boolean }), +]); + +export const LocationsCodec = t.array(LocationCodec); + export const isServiceLocationInvalid = (location: ServiceLocation) => isLeft(ServiceLocationCodec.decode(location)); @@ -63,3 +70,4 @@ export type ServiceLocation = t.TypeOf; export type ServiceLocations = t.TypeOf; export type ServiceLocationsApiResponse = t.TypeOf; export type ServiceLocationErrors = t.TypeOf; +export type Locations = t.TypeOf; diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts index 7117852800811..12791edc676a3 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { ConfigKey } from './config_key'; -import { ServiceLocationsCodec } from './locations'; +import { LocationsCodec } from './locations'; import { DataStreamCodec, ModeCodec, @@ -55,13 +55,13 @@ export const CommonFieldsCodec = t.intersection([ [ConfigKey.MONITOR_TYPE]: DataStreamCodec, [ConfigKey.ENABLED]: t.boolean, [ConfigKey.SCHEDULE]: Schedule, - [ConfigKey.LOCATIONS]: t.array(t.string), [ConfigKey.APM_SERVICE_NAME]: t.string, [ConfigKey.TAGS]: t.array(t.string), - [ConfigKey.LOCATIONS]: ServiceLocationsCodec, + [ConfigKey.LOCATIONS]: LocationsCodec, }), t.partial({ [ConfigKey.TIMEOUT]: t.union([t.string, t.null]), + [ConfigKey.REVISION]: t.number, }), ]); diff --git a/x-pack/plugins/uptime/kibana.json b/x-pack/plugins/uptime/kibana.json index 461358c27fe3b..28a49067b6698 100644 --- a/x-pack/plugins/uptime/kibana.json +++ b/x-pack/plugins/uptime/kibana.json @@ -2,7 +2,7 @@ "configPath": ["xpack", "uptime"], "id": "uptime", "kibanaVersion": "kibana", - "optionalPlugins": ["cloud", "data", "fleet", "home", "ml"], + "optionalPlugins": ["cloud", "data", "fleet", "home", "ml", "telemetry"], "requiredPlugins": [ "alerting", "cases", diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts index 72e9cbaa8a03a..a86894c3d7a48 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts @@ -24,6 +24,7 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.TAGS]: (fields) => arrayToJsonFormatter(fields[ConfigKey.TAGS]), [ConfigKey.TIMEOUT]: (fields) => secondsToCronFormatter(fields[ConfigKey.TIMEOUT] || undefined), [ConfigKey.NAMESPACE]: null, + [ConfigKey.REVISION]: null, }; export const arrayToJsonFormatter = (value: string[] = []) => diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts index a7992b3f935b5..e71a0511cbaf1 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts @@ -82,4 +82,5 @@ export const commonNormalizers: CommonNormalizerMap = { [ConfigKey.TIMEOUT]: getCommonCronToSecondsNormalizer(ConfigKey.TIMEOUT), [ConfigKey.NAMESPACE]: (fields) => fields?.[ConfigKey.NAMESPACE]?.value ?? DEFAULT_NAMESPACE_STRING, + [ConfigKey.REVISION]: getCommonNormalizer(ConfigKey.REVISION), }; diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index b9dfd61e91b70..9efb7e36ebab9 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -7,6 +7,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import type { SavedObjectsClientContract, IScopedClusterClient, Logger } from 'src/core/server'; +import type { TelemetryPluginSetup, TelemetryPluginStart } from 'src/plugins/telemetry/server'; import { ObservabilityPluginSetup } from '../../../../../observability/server'; import { EncryptedSavedObjectsPluginSetup, @@ -21,6 +22,7 @@ import { PluginSetupContract } from '../../../../../features/server'; import { MlPluginSetup as MlSetup } from '../../../../../ml/server'; import { RuleRegistryPluginSetupContract } from '../../../../../rule_registry/server'; import { UptimeESClient } from '../../lib'; +import type { TelemetryEventsSender } from '../../telemetry/sender'; import type { UptimeRouter } from '../../../types'; import { SecurityPluginStart } from '../../../../../security/server'; import { CloudSetup } from '../../../../../cloud/server'; @@ -52,6 +54,7 @@ export interface UptimeServerSetup { syntheticsService: SyntheticsService; kibanaVersion: string; logger: Logger; + telemetry: TelemetryEventsSender; uptimeEsClient: UptimeESClient; } @@ -65,6 +68,7 @@ export interface UptimeCorePluginsSetup { ruleRegistry: RuleRegistryPluginSetupContract; encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; taskManager: TaskManagerSetupContract; + telemetry: TelemetryPluginSetup; } export interface UptimeCorePluginsStart { @@ -72,6 +76,7 @@ export interface UptimeCorePluginsStart { fleet: FleetStartContract; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; taskManager: TaskManagerStartContract; + telemetry: TelemetryPluginStart; } export interface UMBackendFrameworkAdapter { diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/formatters/common.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/formatters/common.ts index 73386578618bb..9aee65e6fa14c 100644 --- a/x-pack/plugins/uptime/server/lib/synthetics_service/formatters/common.ts +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/formatters/common.ts @@ -25,6 +25,7 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.TAGS]: (fields) => arrayFormatter(fields[ConfigKey.TAGS]), [ConfigKey.TIMEOUT]: (fields) => secondsToCronFormatter(fields[ConfigKey.TIMEOUT] || undefined), [ConfigKey.NAMESPACE]: null, + [ConfigKey.REVISION]: null, }; export const arrayFormatter = (value: string[] = []) => (value.length ? value : null); diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts index b2644d53e49d9..1da192ab24058 100644 --- a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts @@ -49,6 +49,7 @@ describe('getServiceLocations', function () { id: 'us_central', label: 'US Central', url: 'https://local.dev', + isServiceManaged: true, }, ], }); diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts index ffec06259f4e7..e746dafdb55d5 100644 --- a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts @@ -8,13 +8,13 @@ import axios from 'axios'; import { ManifestLocation, - ServiceLocations, + Locations, ServiceLocationsApiResponse, } from '../../../common/runtime_types'; import { UptimeServerSetup } from '../adapters/framework'; export async function getServiceLocations(server: UptimeServerSetup) { - const locations: ServiceLocations = []; + const locations: Locations = []; if (!server.config.service?.manifestUrl) { return { locations }; @@ -31,6 +31,7 @@ export async function getServiceLocations(server: UptimeServerSetup) { label: location.geo.name, geo: location.geo.location, url: location.url, + isServiceManaged: true, }); }); diff --git a/x-pack/plugins/uptime/server/lib/telemetry/__mocks__/index.ts b/x-pack/plugins/uptime/server/lib/telemetry/__mocks__/index.ts new file mode 100644 index 0000000000000..2070aeca20861 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/telemetry/__mocks__/index.ts @@ -0,0 +1,26 @@ +/* + * 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 type { TelemetryEventsSender } from '../sender'; + +/** + * Creates a mocked Telemetry Events Sender + */ +export const createMockTelemetryEventsSender = ( + enableTelemetry?: boolean +): jest.Mocked => { + return { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + fetchTelemetryUrl: jest.fn(), + queueTelemetryEvents: jest.fn(), + isTelemetryOptedIn: jest.fn().mockReturnValue(enableTelemetry ?? jest.fn()), + sendIfDue: jest.fn(), + sendEvents: jest.fn(), + } as unknown as jest.Mocked; +}; diff --git a/x-pack/plugins/uptime/server/lib/telemetry/constants.ts b/x-pack/plugins/uptime/server/lib/telemetry/constants.ts new file mode 100644 index 0000000000000..11b5785031149 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/telemetry/constants.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 const MONITOR_UPDATE_CHANNEL = 'synthetics-monitor-update'; +export const MONITOR_CURRENT_CHANNEL = 'synthetics-monitor-current'; diff --git a/x-pack/plugins/uptime/server/lib/telemetry/queue.test.ts b/x-pack/plugins/uptime/server/lib/telemetry/queue.test.ts new file mode 100644 index 0000000000000..510b898387036 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/telemetry/queue.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* eslint-disable dot-notation */ +import { TelemetryQueue } from './queue'; + +describe('TelemetryQueue', () => { + describe('queueTelemetryEvents', () => { + it('queues two events', () => { + const queue = new TelemetryQueue(); + queue.addEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); + expect(queue['queue'].length).toBe(2); + }); + + it('queues more than maxQueueSize events', () => { + const queue = new TelemetryQueue(); + queue.addEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); + queue['maxQueueSize'] = 5; + queue.addEvents([{ 'event.kind': '3' }, { 'event.kind': '4' }]); + queue.addEvents([{ 'event.kind': '5' }, { 'event.kind': '6' }]); + queue.addEvents([{ 'event.kind': '7' }, { 'event.kind': '8' }]); + expect(queue['queue'].length).toBe(5); + }); + + it('get and clear events', async () => { + const queue = new TelemetryQueue(); + queue.addEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); + + expect(queue.getEvents().length).toBe(2); + + queue.clearEvents(); + + expect(queue['queue'].length).toBe(0); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/telemetry/queue.ts b/x-pack/plugins/uptime/server/lib/telemetry/queue.ts new file mode 100644 index 0000000000000..3496cfb94915d --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/telemetry/queue.ts @@ -0,0 +1,40 @@ +/* + * 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 const TELEMETRY_MAX_QUEUE_SIZE = 100; + +export class TelemetryQueue { + private maxQueueSize = TELEMETRY_MAX_QUEUE_SIZE; + private queue: T[] = []; + + public addEvents(events: T[]) { + const qlength = this.queue.length; + + if (events.length === 0) { + return; + } + + if (qlength >= this.maxQueueSize) { + // we're full already + return; + } + + if (events.length > this.maxQueueSize - qlength) { + this.queue.push(...events.slice(0, this.maxQueueSize - qlength)); + } else { + this.queue.push(...events); + } + } + + public clearEvents() { + this.queue = []; + } + + public getEvents(): T[] { + return this.queue; + } +} diff --git a/x-pack/plugins/uptime/server/lib/telemetry/sender.test.ts b/x-pack/plugins/uptime/server/lib/telemetry/sender.test.ts new file mode 100644 index 0000000000000..5a7a69cbcd995 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/telemetry/sender.test.ts @@ -0,0 +1,149 @@ +/* + * 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. + */ + +/* eslint-disable dot-notation */ + +import { URL } from 'url'; + +import axios from 'axios'; + +import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; + +import { loggingSystemMock } from 'src/core/server/mocks'; + +import { MONITOR_UPDATE_CHANNEL } from './constants'; + +import { TelemetryEventsSender } from './sender'; + +jest.mock('axios', () => { + return { + post: jest.fn(), + }; +}); + +describe('TelemetryEventsSender', () => { + let logger: ReturnType; + let sender: TelemetryEventsSender; + const sampleEvent = { + configId: '12345', + stackVersion: '8.1.0', + type: 'http', + locations: ['us_central'], + locationsCount: 1, + monitorNameLength: 8, + monitorInterval: 180000, + revision: 1, + }; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + sender = new TelemetryEventsSender(logger); + sender['fetchClusterInfo'] = jest.fn(async () => { + return { + cluster_uuid: '1', + cluster_name: 'name', + version: { + number: '8.0.0', + }, + } as InfoResponse; + }); + sender.start(undefined, { + elasticsearch: { client: { asInternalUser: { info: jest.fn(async () => ({})) } } }, + } as any); + }); + + describe('queueTelemetryEvents', () => { + it('queues two events', () => { + sender.queueTelemetryEvents(MONITOR_UPDATE_CHANNEL, [sampleEvent]); + expect(sender['queuesPerChannel'][MONITOR_UPDATE_CHANNEL]).toBeDefined(); + }); + + it('should send events when due', async () => { + sender['telemetryStart'] = { + getIsOptedIn: jest.fn(async () => true), + }; + sender['telemetrySetup'] = { + getTelemetryUrl: jest.fn( + async () => new URL('https://telemetry-staging.elastic.co/v3/send/snapshot') + ), + }; + + sender.queueTelemetryEvents(MONITOR_UPDATE_CHANNEL, [sampleEvent]); + sender['sendEvents'] = jest.fn(); + + await sender['sendIfDue'](); + + expect(sender['sendEvents']).toHaveBeenCalledWith( + `https://telemetry-staging.elastic.co/v3-dev/send/${MONITOR_UPDATE_CHANNEL}`, + { cluster_name: 'name', cluster_uuid: '1', version: { number: '8.0.0' } }, + expect.anything() + ); + }); + + it("shouldn't send when telemetry is disabled", async () => { + const telemetryStart = { + getIsOptedIn: jest.fn(async () => false), + }; + sender['telemetryStart'] = telemetryStart; + + sender.queueTelemetryEvents(MONITOR_UPDATE_CHANNEL, [sampleEvent]); + sender['sendEvents'] = jest.fn(); + + await sender['sendIfDue'](); + + expect(sender['sendEvents']).toBeCalledTimes(0); + }); + + it('should send events to separate channels', async () => { + sender['telemetryStart'] = { + getIsOptedIn: jest.fn(async () => true), + }; + sender['telemetrySetup'] = { + getTelemetryUrl: jest.fn( + async () => new URL('https://telemetry.elastic.co/v3/send/snapshot') + ), + }; + + const myChannelEvents = [{ 'event.kind': '1' }, { 'event.kind': '2' }]; + // @ts-ignore + sender.queueTelemetryEvents('my-channel', myChannelEvents); + sender['queuesPerChannel']['my-channel']['getEvents'] = jest.fn(() => myChannelEvents); + + expect(sender['queuesPerChannel']['my-channel']['queue'].length).toBe(2); + + const myChannel2Events = [{ 'event.kind': '3' }]; + // @ts-ignore + sender.queueTelemetryEvents('my-channel2', myChannel2Events); + sender['queuesPerChannel']['my-channel2']['getEvents'] = jest.fn(() => myChannel2Events); + + expect(sender['queuesPerChannel']['my-channel2']['queue'].length).toBe(1); + + await sender['sendIfDue'](); + + expect(sender['queuesPerChannel']['my-channel']['getEvents']).toBeCalledTimes(1); + expect(sender['queuesPerChannel']['my-channel2']['getEvents']).toBeCalledTimes(1); + const headers = { + headers: { + 'Content-Type': 'application/x-ndjson', + 'X-Elastic-Cluster-ID': '1', + 'X-Elastic-Cluster-Name': 'name', + 'X-Elastic-Stack-Version': '8.0.0', + }, + }; + expect(axios.post).toHaveBeenCalledWith( + 'https://telemetry.elastic.co/v3/send/my-channel', + '{"event.kind":"1"}\n{"event.kind":"2"}\n', + headers + ); + expect(axios.post).toHaveBeenCalledWith( + 'https://telemetry.elastic.co/v3/send/my-channel2', + '{"event.kind":"3"}\n', + headers + ); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/telemetry/sender.ts b/x-pack/plugins/uptime/server/lib/telemetry/sender.ts new file mode 100644 index 0000000000000..296bc2af2f71f --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/telemetry/sender.ts @@ -0,0 +1,198 @@ +/* + * 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 type { CoreStart, ElasticsearchClient, Logger } from 'src/core/server'; +import type { TelemetryPluginStart, TelemetryPluginSetup } from 'src/plugins/telemetry/server'; + +import { cloneDeep } from 'lodash'; + +import axios from 'axios'; + +import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; + +import { TelemetryQueue } from './queue'; + +import type { MonitorUpdateTelemetryChannel, MonitorUpdateTelemetryChannelEvents } from './types'; + +/** + * Simplified version of https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts + * Sends batched events to telemetry v3 api + */ +export class TelemetryEventsSender { + private readonly initialCheckDelayMs = 10 * 1000; + private readonly checkIntervalMs = 30 * 1000; + private readonly logger: Logger; + + private telemetryStart?: TelemetryPluginStart; + private telemetrySetup?: TelemetryPluginSetup; + private intervalId?: NodeJS.Timeout; + private isSending = false; + private queuesPerChannel: { [channel: string]: TelemetryQueue } = {}; + private isOptedIn?: boolean = true; // Assume true until the first check + private esClient?: ElasticsearchClient; + private clusterInfo?: InfoResponse; + + constructor(logger: Logger) { + this.logger = logger; + } + + public setup(telemetrySetup?: TelemetryPluginSetup) { + this.telemetrySetup = telemetrySetup; + } + + public async start(telemetryStart?: TelemetryPluginStart, core?: CoreStart) { + this.telemetryStart = telemetryStart; + this.esClient = core?.elasticsearch.client.asInternalUser; + this.clusterInfo = await this.fetchClusterInfo(); + + this.logger.debug(`Starting local task`); + setTimeout(() => { + this.sendIfDue(); + this.intervalId = setInterval(() => this.sendIfDue(), this.checkIntervalMs); + }, this.initialCheckDelayMs); + } + + public stop() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + + public queueTelemetryEvents( + channel: T, + events: Array + ) { + if (!this.queuesPerChannel[channel]) { + this.queuesPerChannel[channel] = new TelemetryQueue(); + } + this.queuesPerChannel[channel].addEvents(cloneDeep(events)); + } + + public async isTelemetryOptedIn() { + this.isOptedIn = await this.telemetryStart?.getIsOptedIn(); + return this.isOptedIn === true; + } + + private async sendIfDue() { + if (this.isSending) { + return; + } + + this.isSending = true; + + this.isOptedIn = await this.isTelemetryOptedIn(); + if (!this.isOptedIn) { + this.logger.debug(`Telemetry is not opted-in.`); + for (const channel of Object.keys(this.queuesPerChannel)) { + this.queuesPerChannel[channel].clearEvents(); + } + this.isSending = false; + return; + } + + for (const channel of Object.keys(this.queuesPerChannel)) { + await this.sendEvents( + await this.fetchTelemetryUrl(channel), + this.clusterInfo, + this.queuesPerChannel[channel] + ); + } + + this.isSending = false; + } + + private async fetchClusterInfo(): Promise { + if (this.esClient === undefined || this.esClient === null) { + throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation'); + } + + return await this.esClient.info(); + } + + public async sendEvents( + telemetryUrl: string, + clusterInfo: InfoResponse | undefined, + queue: TelemetryQueue + ) { + const events = queue.getEvents(); + if (events.length === 0) { + return; + } + + try { + this.logger.debug(`Telemetry URL: ${telemetryUrl}`); + + queue.clearEvents(); + + this.logger.debug(JSON.stringify(events)); + + await this.send( + events, + telemetryUrl, + clusterInfo?.cluster_uuid, + clusterInfo?.version?.number, + clusterInfo?.cluster_name + ); + } catch (err) { + this.logger.debug(`Error sending telemetry events data: ${err}`); + queue.clearEvents(); + } + } + + // Forms URLs like: + // https://telemetry.elastic.co/v3/send/my-channel-name or + // https://telemetry-staging.elastic.co/v3/send/my-channel-name + private async fetchTelemetryUrl(channel: string): Promise { + const telemetryUrl = await this.telemetrySetup?.getTelemetryUrl(); + if (!telemetryUrl) { + throw Error("Couldn't get telemetry URL"); + } + if (!telemetryUrl.hostname.includes('staging')) { + telemetryUrl.pathname = `/v3/send/${channel}`; + } else { + telemetryUrl.pathname = `/v3-dev/send/${channel}`; + } + return telemetryUrl.toString(); + } + + private async send( + events: unknown[], + telemetryUrl: string, + clusterUuid: string | undefined, + clusterVersionNumber: string | undefined, + clusterName: string | undefined + ) { + // using ndjson so that each line will be wrapped in json envelope on server side + // see https://github.com/elastic/infra/blob/master/docs/telemetry/telemetry-next-dataflow.md#json-envelope + const ndjson = this.transformDataToNdjson(events); + + try { + const resp = await axios.post(telemetryUrl, ndjson, { + headers: { + 'Content-Type': 'application/x-ndjson', + 'X-Elastic-Cluster-ID': clusterUuid, + 'X-Elastic-Cluster-Name': clusterName, + 'X-Elastic-Stack-Version': clusterVersionNumber ? clusterVersionNumber : '8.2.0', + }, + }); + this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); + } catch (err) { + this.logger.debug( + `Error sending events: ${err.response.status} ${JSON.stringify(err.response.data)}` + ); + } + } + + private transformDataToNdjson = (data: unknown[]): string => { + if (data.length !== 0) { + const dataString = data.map((dataItem) => JSON.stringify(dataItem)).join('\n'); + return `${dataString}\n`; + } else { + return ''; + } + }; +} diff --git a/x-pack/plugins/uptime/server/lib/telemetry/types.ts b/x-pack/plugins/uptime/server/lib/telemetry/types.ts new file mode 100644 index 0000000000000..59d060c2f3a76 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/telemetry/types.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 { ServiceLocationErrors } from '../../../common/runtime_types/monitor_management'; + +export interface MonitorUpdateEvent { + updatedAt?: string; + lastUpdatedAt?: string; + durationSinceLastUpdated?: number; + deletedAt?: string; + type: string; + stackVersion: string; + monitorNameLength: number; + monitorInterval: number; + locations: string[]; + locationsCount: number; + scriptType?: 'inline' | 'recorder' | 'zip'; + revision?: number; + errors?: ServiceLocationErrors; + configId: string; +} + +export interface MonitorUpdateTelemetryChannelEvents { + // channel name => event type + 'synthetics-monitor-update': MonitorUpdateEvent; + 'synthetics-monitor-current': MonitorUpdateEvent; +} + +export type MonitorUpdateTelemetryChannel = keyof MonitorUpdateTelemetryChannelEvents; diff --git a/x-pack/plugins/uptime/server/plugin.ts b/x-pack/plugins/uptime/server/plugin.ts index 4dc8437cbbcb6..d2afb3f16fb6a 100644 --- a/x-pack/plugins/uptime/server/plugin.ts +++ b/x-pack/plugins/uptime/server/plugin.ts @@ -21,6 +21,7 @@ import { UptimeCorePluginsStart, UptimeServerSetup, } from './lib/adapters'; +import { TelemetryEventsSender } from './lib/telemetry/sender'; import { registerUptimeSavedObjects, savedObjectsAdapter } from './lib/saved_objects/saved_objects'; import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; import { experimentalRuleFieldMap } from '../../rule_registry/common/assets/field_maps/experimental_rule_field_map'; @@ -37,11 +38,13 @@ export class Plugin implements PluginType { private logger: Logger; private server?: UptimeServerSetup; private syntheticService?: SyntheticsService; + private readonly telemetryEventsSender: TelemetryEventsSender; private readonly isServiceEnabled?: boolean; constructor(initializerContext: PluginInitializerContext) { this.initContext = initializerContext; this.logger = initializerContext.logger.get(); + this.telemetryEventsSender = new TelemetryEventsSender(this.logger); const config = this.initContext.config.get(); this.isServiceEnabled = config?.ui?.monitorManagement?.enabled && Boolean(config.service); } @@ -76,6 +79,7 @@ export class Plugin implements PluginType { cloud: plugins.cloud, kibanaVersion: this.initContext.env.packageInfo.version, logger: this.logger, + telemetry: this.telemetryEventsSender, } as UptimeServerSetup; if (this.isServiceEnabled && this.server.config.service) { @@ -86,6 +90,7 @@ export class Plugin implements PluginType { ); this.syntheticService.registerSyncTask(plugins.taskManager); + this.telemetryEventsSender.setup(plugins.telemetry); } initServerWithKibana(this.server, plugins, ruleDataClient, this.logger); @@ -130,6 +135,7 @@ export class Plugin implements PluginType { if (this.server && this.syntheticService) { this.server.syntheticsService = this.syntheticService; } + this.telemetryEventsSender.start(plugins.telemetry, coreStart); } } diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts index 38def648f6e5c..29c2210efd20a 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts @@ -5,11 +5,13 @@ * 2.0. */ import { schema } from '@kbn/config-schema'; +import { SavedObject } from 'kibana/server'; import { MonitorFields, SyntheticsMonitor } from '../../../common/runtime_types'; import { UMRestApiRouteFactory } from '../types'; import { API_URLS } from '../../../common/constants'; import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; import { validateMonitor } from './monitor_validation'; +import { sendTelemetryEvents, formatTelemetryEvent } from './telemetry/monitor_upgrade_sender'; export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ method: 'POST', @@ -27,10 +29,11 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ return response.badRequest({ body: { message, attributes: { details, ...payload } } }); } - const newMonitor = await savedObjectsClient.create( - syntheticsMonitorType, - monitor - ); + const newMonitor: SavedObject = + await savedObjectsClient.create(syntheticsMonitorType, { + ...monitor, + revision: 1, + }); const { syntheticsService } = server; @@ -45,6 +48,12 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ }, ]); + sendTelemetryEvents( + server.logger, + server.telemetry, + formatTelemetryEvent({ monitor: newMonitor, errors, kibanaVersion: server.kibanaVersion }) + ); + if (errors) { return errors; } diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/delete_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/delete_monitor.ts index c1a1ea8945606..bbdb6818b3f6a 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics_service/delete_monitor.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/delete_monitor.ts @@ -11,6 +11,10 @@ import { UMRestApiRouteFactory } from '../types'; import { API_URLS } from '../../../common/constants'; import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; import { getMonitorNotFoundResponse } from './service_errors'; +import { + sendTelemetryEvents, + formatTelemetryDeleteEvent, +} from './telemetry/monitor_upgrade_sender'; export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ method: 'DELETE', @@ -36,6 +40,12 @@ export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ { ...monitor.attributes, id: monitorId }, ]); + sendTelemetryEvents( + server.logger, + server.telemetry, + formatTelemetryDeleteEvent(monitor, server.kibanaVersion, new Date().toISOString(), errors) + ); + if (errors) { return errors; } diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts index 62a542b2b2037..d8a6ec85e770e 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts @@ -6,14 +6,18 @@ */ import { schema } from '@kbn/config-schema'; -import { SavedObjectsUpdateResponse } from 'kibana/server'; +import { SavedObjectsUpdateResponse, SavedObject } from 'kibana/server'; import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; -import { MonitorFields, SyntheticsMonitor } from '../../../common/runtime_types'; +import { MonitorFields, SyntheticsMonitor, ConfigKey } from '../../../common/runtime_types'; import { UMRestApiRouteFactory } from '../types'; import { API_URLS } from '../../../common/constants'; import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; import { validateMonitor } from './monitor_validation'; import { getMonitorNotFoundResponse } from './service_errors'; +import { + sendTelemetryEvents, + formatTelemetryUpdateEvent, +} from './telemetry/monitor_upgrade_sender'; // Simplify return promise type and type it with runtime_types export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ @@ -40,11 +44,20 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ const { syntheticsService } = server; try { + const previousMonitor: SavedObject = await savedObjectsClient.get( + syntheticsMonitorType, + monitorId + ); + const monitorWithRevision = { + ...monitor, + revision: (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, + }; + const editMonitor: SavedObjectsUpdateResponse = await savedObjectsClient.update( syntheticsMonitorType, monitorId, - monitor.type === 'browser' ? { ...monitor, urls: '' } : monitor + monitor.type === 'browser' ? { ...monitorWithRevision, urls: '' } : monitorWithRevision ); const errors = await syntheticsService.pushConfigs(request, [ @@ -58,6 +71,12 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ }, ]); + sendTelemetryEvents( + server.logger, + server.telemetry, + formatTelemetryUpdateEvent(editMonitor, previousMonitor, server.kibanaVersion, errors) + ); + // Return service sync errors in OK response if (errors) { return errors; diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/telemetry/monitor_upgrade_sender.test.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/telemetry/monitor_upgrade_sender.test.ts new file mode 100644 index 0000000000000..24734c88301f1 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/telemetry/monitor_upgrade_sender.test.ts @@ -0,0 +1,215 @@ +/* + * 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 { sha256 } from 'js-sha256'; +import type { Logger } from 'src/core/server'; +import { loggingSystemMock } from 'src/core/server/mocks'; +import { SavedObject } from 'kibana/server'; +import { + SyntheticsMonitor, + ConfigKey, + DataStream, + ScheduleUnit, +} from '../../../../common/runtime_types/monitor_management'; + +import type { TelemetryEventsSender } from '../../../lib/telemetry/sender'; +import { createMockTelemetryEventsSender } from '../../../lib/telemetry/__mocks__'; + +import { MONITOR_UPDATE_CHANNEL, MONITOR_CURRENT_CHANNEL } from '../../../lib/telemetry/constants'; + +import { + formatTelemetryEvent, + formatTelemetryUpdateEvent, + formatTelemetryDeleteEvent, + sendTelemetryEvents, +} from './monitor_upgrade_sender'; + +const kibanaVersion = '8.2.0'; +const id = '123456'; +const errors = [ + { + locationId: 'us_central', + error: { + reason: 'my reason', + status: 400, + }, + }, +]; +const testConfig: SavedObject = { + updated_at: '2011-10-05T14:48:00.000Z', + id, + attributes: { + [ConfigKey.MONITOR_TYPE]: DataStream.HTTP, + [ConfigKey.LOCATIONS]: [ + { + id: 'us_central', + label: 'US Central', + url: 'testurl.com', + geo: { + lat: 0, + lon: 0, + }, + isServiceManaged: true, + }, + { + id: 'custom', + label: 'Custom US Central', + url: 'testurl.com', + geo: { + lat: 0, + lon: 0, + }, + }, + ], + [ConfigKey.SCHEDULE]: { number: '3', unit: ScheduleUnit.MINUTES }, + [ConfigKey.URLS]: 'https://elastic.co', + [ConfigKey.NAME]: 'Test', + [ConfigKey.REVISION]: 1, + } as SyntheticsMonitor, +} as SavedObject; +const createTestConfig = (extraConfigs: Record, updatedAt?: string) => { + return { + ...testConfig, + updated_at: updatedAt || testConfig.updated_at, + attributes: { + ...testConfig.attributes, + ...extraConfigs, + }, + } as SavedObject; +}; + +describe('monitor upgrade telemetry helpers', () => { + it('formats telemetry events', () => { + const actual = formatTelemetryEvent({ monitor: testConfig, kibanaVersion, errors }); + expect(actual).toEqual({ + stackVersion: kibanaVersion, + configId: sha256.create().update(testConfig.id).hex(), + locations: ['us_central', 'other'], + locationsCount: 2, + monitorNameLength: testConfig.attributes[ConfigKey.NAME].length, + updatedAt: testConfig.updated_at, + type: testConfig.attributes[ConfigKey.MONITOR_TYPE], + scriptType: undefined, + monitorInterval: 180000, + lastUpdatedAt: undefined, + deletedAt: undefined, + errors, + durationSinceLastUpdated: undefined, + revision: 1, + }); + }); + + it.each([ + [ConfigKey.SOURCE_INLINE, 'recorder', true], + [ConfigKey.SOURCE_INLINE, 'inline', false], + [ConfigKey.SOURCE_ZIP_URL, 'zip', false], + ])('handles formatting scriptType for browser monitors', (config, scriptType, isRecorder) => { + const actual = formatTelemetryEvent({ + monitor: createTestConfig({ + [config]: 'test', + [ConfigKey.METADATA]: { + script_source: { + is_generated_script: isRecorder, + }, + }, + }), + kibanaVersion, + errors, + }); + expect(actual).toEqual({ + stackVersion: kibanaVersion, + configId: sha256.create().update(testConfig.id).hex(), + locations: ['us_central', 'other'], + locationsCount: 2, + monitorNameLength: testConfig.attributes[ConfigKey.NAME].length, + updatedAt: testConfig.updated_at, + type: testConfig.attributes[ConfigKey.MONITOR_TYPE], + scriptType, + monitorInterval: 180000, + lastUpdatedAt: undefined, + deletedAt: undefined, + errors, + durationSinceLastUpdated: undefined, + revision: 1, + }); + }); + + it('handles formatting update events', () => { + const actual = formatTelemetryUpdateEvent( + createTestConfig({}, '2011-10-05T16:48:00.000Z'), + testConfig, + kibanaVersion, + errors + ); + expect(actual).toEqual({ + stackVersion: kibanaVersion, + configId: sha256.create().update(testConfig.id).hex(), + locations: ['us_central', 'other'], + locationsCount: 2, + monitorNameLength: testConfig.attributes[ConfigKey.NAME].length, + updatedAt: '2011-10-05T16:48:00.000Z', + type: testConfig.attributes[ConfigKey.MONITOR_TYPE], + scriptType: undefined, + monitorInterval: 180000, + lastUpdatedAt: testConfig.updated_at, + deletedAt: undefined, + errors, + durationSinceLastUpdated: 7200000, + revision: 1, + }); + }); + + it('handles formatting delete events', () => { + const actual = formatTelemetryDeleteEvent( + testConfig, + kibanaVersion, + '2011-10-05T16:48:00.000Z', + errors + ); + expect(actual).toEqual({ + stackVersion: kibanaVersion, + configId: sha256.create().update(testConfig.id).hex(), + locations: ['us_central', 'other'], + locationsCount: 2, + monitorNameLength: testConfig.attributes[ConfigKey.NAME].length, + updatedAt: '2011-10-05T16:48:00.000Z', + type: testConfig.attributes[ConfigKey.MONITOR_TYPE], + scriptType: undefined, + monitorInterval: 180000, + lastUpdatedAt: testConfig.updated_at, + deletedAt: '2011-10-05T16:48:00.000Z', + errors, + durationSinceLastUpdated: 7200000, + revision: 1, + }); + }); +}); + +describe('sendTelemetryEvents', () => { + let eventsTelemetryMock: jest.Mocked; + let loggerMock: jest.Mocked; + + beforeEach(() => { + eventsTelemetryMock = createMockTelemetryEventsSender(); + loggerMock = loggingSystemMock.createLogger(); + }); + + it('should queue telemetry events with generic error', () => { + const event = formatTelemetryEvent({ monitor: testConfig, kibanaVersion, errors }); + sendTelemetryEvents( + loggerMock, + eventsTelemetryMock, + formatTelemetryEvent({ monitor: testConfig, kibanaVersion, errors }) + ); + + expect(eventsTelemetryMock.queueTelemetryEvents).toHaveBeenCalledWith(MONITOR_UPDATE_CHANNEL, [ + event, + ]); + expect(eventsTelemetryMock.queueTelemetryEvents).toHaveBeenCalledWith(MONITOR_CURRENT_CHANNEL, [ + event, + ]); + }); +}); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/telemetry/monitor_upgrade_sender.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/telemetry/monitor_upgrade_sender.ts new file mode 100644 index 0000000000000..d4ac7c85c9586 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/telemetry/monitor_upgrade_sender.ts @@ -0,0 +1,147 @@ +/* + * 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 { sha256 } from 'js-sha256'; +import type { Logger } from 'src/core/server'; +import { SavedObjectsUpdateResponse, SavedObject } from 'kibana/server'; +import { + MonitorFields, + SyntheticsMonitor, + ConfigKey, + ServiceLocationErrors, +} from '../../../../common/runtime_types'; +import type { MonitorUpdateEvent } from '../../../lib/telemetry/types'; + +import { TelemetryEventsSender } from '../../../lib/telemetry/sender'; +import { MONITOR_UPDATE_CHANNEL, MONITOR_CURRENT_CHANNEL } from '../../../lib/telemetry/constants'; + +export interface UpgradeError { + key?: string; + message: string | string[]; +} + +export function sendTelemetryEvents( + logger: Logger, + eventsTelemetry: TelemetryEventsSender | undefined, + updateEvent: MonitorUpdateEvent +) { + if (eventsTelemetry === undefined) { + return; + } + + try { + eventsTelemetry.queueTelemetryEvents(MONITOR_UPDATE_CHANNEL, [updateEvent]); + eventsTelemetry.queueTelemetryEvents(MONITOR_CURRENT_CHANNEL, [updateEvent]); + } catch (exc) { + logger.error(`queing telemetry events failed ${exc}`); + } +} + +export function formatTelemetryEvent({ + monitor, + kibanaVersion, + lastUpdatedAt, + durationSinceLastUpdated, + deletedAt, + errors, +}: { + monitor: SavedObject; + kibanaVersion: string; + lastUpdatedAt?: string; + durationSinceLastUpdated?: number; + deletedAt?: string; + errors?: ServiceLocationErrors; +}) { + const { attributes } = monitor; + + return { + updatedAt: deletedAt || monitor.updated_at, + lastUpdatedAt, + durationSinceLastUpdated, + deletedAt, + type: attributes[ConfigKey.MONITOR_TYPE], + locations: attributes[ConfigKey.LOCATIONS].map((location) => + location.isServiceManaged ? location.id : 'other' + ), // mark self-managed locations as other + locationsCount: attributes[ConfigKey.LOCATIONS].length, + monitorNameLength: attributes[ConfigKey.NAME].length, + monitorInterval: parseInt(attributes[ConfigKey.SCHEDULE].number, 10) * 60 * 1000, + stackVersion: kibanaVersion, + scriptType: getScriptType(attributes as MonitorFields), + errors: + errors && errors?.length + ? errors.map((e) => ({ + locationId: e.locationId, + error: { + // don't expose failed_monitors on error object + status: e.error?.status, + reason: e.error?.reason, + }, + })) + : undefined, + configId: sha256.create().update(monitor.id).hex(), + revision: attributes[ConfigKey.REVISION], + }; +} + +export function formatTelemetryUpdateEvent( + currentMonitor: SavedObjectsUpdateResponse, + previousMonitor: SavedObject, + kibanaVersion: string, + errors?: ServiceLocationErrors +) { + let durationSinceLastUpdated: number = 0; + if (currentMonitor.updated_at && previousMonitor.updated_at) { + durationSinceLastUpdated = + new Date(currentMonitor.updated_at).getTime() - + new Date(previousMonitor.updated_at).getTime(); + } + + return formatTelemetryEvent({ + monitor: currentMonitor as SavedObject, + kibanaVersion, + durationSinceLastUpdated, + lastUpdatedAt: previousMonitor.updated_at, + errors, + }); +} + +export function formatTelemetryDeleteEvent( + previousMonitor: SavedObject, + kibanaVersion: string, + deletedAt: string, + errors?: ServiceLocationErrors +) { + let durationSinceLastUpdated: number = 0; + if (deletedAt && previousMonitor.updated_at) { + durationSinceLastUpdated = + new Date(deletedAt).getTime() - new Date(previousMonitor.updated_at).getTime(); + } + + return formatTelemetryEvent({ + monitor: previousMonitor as SavedObject, + kibanaVersion, + durationSinceLastUpdated, + lastUpdatedAt: previousMonitor.updated_at, + deletedAt, + errors, + }); +} + +function getScriptType(attributes: MonitorFields): 'inline' | 'recorder' | 'zip' | undefined { + if (attributes[ConfigKey.SOURCE_ZIP_URL]) { + return 'zip'; + } else if ( + attributes[ConfigKey.SOURCE_INLINE] && + attributes[ConfigKey.METADATA].script_source?.is_generated_script + ) { + return 'recorder'; + } else if (attributes[ConfigKey.SOURCE_INLINE]) { + return 'inline'; + } + + return undefined; +} diff --git a/x-pack/test/accessibility/apps/kibana_overview.ts b/x-pack/test/accessibility/apps/kibana_overview.ts index 6ea51cc0b855c..9d21f08a900cc 100644 --- a/x-pack/test/accessibility/apps/kibana_overview.ts +++ b/x-pack/test/accessibility/apps/kibana_overview.ts @@ -11,7 +11,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'home']); const a11y = getService('a11y'); - // FLAKY: https://github.com/elastic/kibana/issues/98463 describe('Kibana overview', () => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/accessibility/apps/login_page.ts b/x-pack/test/accessibility/apps/login_page.ts index 407c4dbee464c..154517d09502e 100644 --- a/x-pack/test/accessibility/apps/login_page.ts +++ b/x-pack/test/accessibility/apps/login_page.ts @@ -15,7 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'security']); // Failing: See https://github.com/elastic/kibana/issues/96372 - describe.skip('Security', () => { + describe('Security', () => { describe('Login Page', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts index b070219410fd9..0c527ac1449f8 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts @@ -21,6 +21,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { { id: 'recovered', name: 'Recovered' }, ], default_action_group_id: 'default', + does_set_recovery_context: false, id: 'test.noop', name: 'Test: Noop', action_variables: { @@ -49,6 +50,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { name: 'Restricted Recovery', }, default_action_group_id: 'default', + does_set_recovery_context: false, id: 'test.restricted-noop', name: 'Test: Restricted Noop', action_variables: { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index 8c796c1a39d67..ba2a2cb8fdf47 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -17,27 +17,27 @@ import { } from '../../../../../common/lib'; import { createEsDocuments } from './create_test_data'; -const ALERT_TYPE_ID = '.index-threshold'; -const ACTION_TYPE_ID = '.index'; +const RULE_TYPE_ID = '.index-threshold'; +const CONNECTOR_TYPE_ID = '.index'; const ES_TEST_INDEX_SOURCE = 'builtin-alert:index-threshold'; const ES_TEST_INDEX_REFERENCE = '-na-'; const ES_TEST_OUTPUT_INDEX_NAME = `${ES_TEST_INDEX_NAME}-output`; -const ALERT_INTERVALS_TO_WRITE = 5; -const ALERT_INTERVAL_SECONDS = 3; -const ALERT_INTERVAL_MILLIS = ALERT_INTERVAL_SECONDS * 1000; +const RULE_INTERVALS_TO_WRITE = 5; +const RULE_INTERVAL_SECONDS = 3; +const RULE_INTERVAL_MILLIS = RULE_INTERVAL_SECONDS * 1000; // eslint-disable-next-line import/no-default-export -export default function alertTests({ getService }: FtrProviderContext) { +export default function ruleTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const retry = getService('retry'); const es = getService('es'); const esTestIndexTool = new ESTestIndexTool(es, retry); const esTestIndexToolOutput = new ESTestIndexTool(es, retry, ES_TEST_OUTPUT_INDEX_NAME); - describe('alert', async () => { + describe('rule', async () => { let endDate: string; - let actionId: string; + let connectorId: string; const objectRemover = new ObjectRemover(supertest); beforeEach(async () => { @@ -47,10 +47,10 @@ export default function alertTests({ getService }: FtrProviderContext) { await esTestIndexToolOutput.destroy(); await esTestIndexToolOutput.setup(); - actionId = await createAction(supertest, objectRemover); + connectorId = await createConnector(supertest, objectRemover); // write documents in the future, figure out the end date - const endDateMillis = Date.now() + (ALERT_INTERVALS_TO_WRITE - 1) * ALERT_INTERVAL_MILLIS; + const endDateMillis = Date.now() + (RULE_INTERVALS_TO_WRITE - 1) * RULE_INTERVAL_MILLIS; endDate = new Date(endDateMillis).toISOString(); // write documents from now to the future end date in 3 groups @@ -67,7 +67,7 @@ export default function alertTests({ getService }: FtrProviderContext) { // never fire; the tests ensure the ones that should fire, do fire, and // those that shouldn't fire, do not fire. it('runs correctly: count all < >', async () => { - await createAlert({ + await createRule({ name: 'never fire', aggType: 'count', groupBy: 'all', @@ -75,7 +75,7 @@ export default function alertTests({ getService }: FtrProviderContext) { threshold: [0], }); - await createAlert({ + await createRule({ name: 'always fire', aggType: 'count', groupBy: 'all', @@ -104,7 +104,7 @@ export default function alertTests({ getService }: FtrProviderContext) { // create some more documents in the first group createEsDocumentsInGroups(1); - await createAlert({ + await createRule({ name: 'never fire', aggType: 'count', groupBy: 'top', @@ -114,7 +114,7 @@ export default function alertTests({ getService }: FtrProviderContext) { threshold: [-1], }); - await createAlert({ + await createRule({ name: 'always fire', aggType: 'count', groupBy: 'top', @@ -148,7 +148,7 @@ export default function alertTests({ getService }: FtrProviderContext) { // create some more documents in the first group createEsDocumentsInGroups(1); - await createAlert({ + await createRule({ name: 'never fire', aggType: 'sum', aggField: 'testedValue', @@ -157,7 +157,7 @@ export default function alertTests({ getService }: FtrProviderContext) { threshold: [-2, -1], }); - await createAlert({ + await createRule({ name: 'always fire', aggType: 'sum', aggField: 'testedValue', @@ -183,7 +183,7 @@ export default function alertTests({ getService }: FtrProviderContext) { createEsDocumentsInGroups(1); // this never fires because of bad fields error - await createAlert({ + await createRule({ name: 'never fire', timeField: 'source', // bad field for time aggType: 'avg', @@ -193,7 +193,7 @@ export default function alertTests({ getService }: FtrProviderContext) { threshold: [0], }); - await createAlert({ + await createRule({ name: 'always fire', aggType: 'avg', aggField: 'testedValue', @@ -218,7 +218,7 @@ export default function alertTests({ getService }: FtrProviderContext) { // create some more documents in the first group createEsDocumentsInGroups(1); - await createAlert({ + await createRule({ name: 'never fire', aggType: 'max', aggField: 'testedValue', @@ -229,7 +229,7 @@ export default function alertTests({ getService }: FtrProviderContext) { threshold: [0], }); - await createAlert({ + await createRule({ name: 'always fire', aggType: 'max', aggField: 'testedValue', @@ -264,7 +264,7 @@ export default function alertTests({ getService }: FtrProviderContext) { // create some more documents in the first group createEsDocumentsInGroups(1); - await createAlert({ + await createRule({ name: 'never fire', aggType: 'min', aggField: 'testedValue', @@ -275,7 +275,7 @@ export default function alertTests({ getService }: FtrProviderContext) { threshold: [0], }); - await createAlert({ + await createRule({ name: 'always fire', aggType: 'min', aggField: 'testedValue', @@ -306,13 +306,59 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(inGroup0).to.be.greaterThan(0); }); + it('runs correctly and populates recovery context', async () => { + // This rule should be active initially when the number of documents is below the threshold + // and then recover when we add more documents. + await createRule({ + name: 'fire then recovers', + aggType: 'count', + groupBy: 'all', + thresholdComparator: '<', + threshold: [10], + timeWindowSize: 60, + }); + + await createEsDocumentsInGroups(1); + + const docs = await waitForDocs(2); + const activeDoc = docs[0]; + const { group: activeGroup } = activeDoc._source; + const { + name: activeName, + title: activeTitle, + message: activeMessage, + } = activeDoc._source.params; + + expect(activeName).to.be('fire then recovers'); + expect(activeGroup).to.be('all documents'); + expect(activeTitle).to.be('alert fire then recovers group all documents met threshold'); + expect(activeMessage).to.match( + /alert 'fire then recovers' is active for group \'all documents\':\n\n- Value: \d+\n- Conditions Met: count is less than 10 over 60s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/ + ); + + const recoveredDoc = docs[1]; + const { group: recoveredGroup } = recoveredDoc._source; + const { + name: recoveredName, + title: recoveredTitle, + message: recoveredMessage, + } = recoveredDoc._source.params; + + expect(recoveredName).to.be('fire then recovers'); + expect(recoveredGroup).to.be('all documents'); + expect(recoveredTitle).to.be('alert fire then recovers group all documents recovered'); + expect(recoveredMessage).to.match( + /alert 'fire then recovers' is recovered for group \'all documents\':\n\n- Value: \d+\n- Conditions Met: count is NOT less than 10 over 60s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/ + ); + }); + async function createEsDocumentsInGroups(groups: number) { await createEsDocuments( es, esTestIndexTool, endDate, - ALERT_INTERVALS_TO_WRITE, - ALERT_INTERVAL_MILLIS, + RULE_INTERVALS_TO_WRITE, + RULE_INTERVAL_MILLIS, groups ); } @@ -325,11 +371,12 @@ export default function alertTests({ getService }: FtrProviderContext) { ); } - interface CreateAlertParams { + interface CreateRuleParams { name: string; aggType: string; aggField?: string; timeField?: string; + timeWindowSize?: number; groupBy: 'all' | 'top'; termField?: string; termSize?: number; @@ -337,9 +384,9 @@ export default function alertTests({ getService }: FtrProviderContext) { threshold: number[]; } - async function createAlert(params: CreateAlertParams): Promise { + async function createRule(params: CreateRuleParams): Promise { const action = { - id: actionId, + id: connectorId, group: 'threshold met', params: { documents: [ @@ -347,7 +394,7 @@ export default function alertTests({ getService }: FtrProviderContext) { source: ES_TEST_INDEX_SOURCE, reference: ES_TEST_INDEX_REFERENCE, params: { - name: '{{{alertName}}}', + name: '{{{rule.name}}}', value: '{{{context.value}}}', title: '{{{context.title}}}', message: '{{{context.message}}}', @@ -362,16 +409,37 @@ export default function alertTests({ getService }: FtrProviderContext) { }, }; - const { status, body: createdAlert } = await supertest + const recoveryAction = { + id: connectorId, + group: 'recovered', + params: { + documents: [ + { + source: ES_TEST_INDEX_SOURCE, + reference: ES_TEST_INDEX_REFERENCE, + params: { + name: '{{{rule.name}}}', + value: '{{{context.value}}}', + title: '{{{context.title}}}', + message: '{{{context.message}}}', + }, + date: '{{{context.date}}}', + group: '{{{context.group}}}', + }, + ], + }, + }; + + const { status, body: createdRule } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send({ name: params.name, consumer: 'alerts', enabled: true, - rule_type_id: ALERT_TYPE_ID, - schedule: { interval: `${ALERT_INTERVAL_SECONDS}s` }, - actions: [action], + rule_type_id: RULE_TYPE_ID, + schedule: { interval: `${RULE_INTERVAL_SECONDS}s` }, + actions: [action, recoveryAction], notify_when: 'onActiveAlert', params: { index: ES_TEST_INDEX_NAME, @@ -381,7 +449,7 @@ export default function alertTests({ getService }: FtrProviderContext) { groupBy: params.groupBy, termField: params.termField, termSize: params.termSize, - timeWindowSize: ALERT_INTERVAL_SECONDS * 5, + timeWindowSize: params.timeWindowSize ?? RULE_INTERVAL_SECONDS * 5, timeWindowUnit: 's', thresholdComparator: params.thresholdComparator, threshold: params.threshold, @@ -389,25 +457,25 @@ export default function alertTests({ getService }: FtrProviderContext) { }); // will print the error body, if an error occurred - // if (statusCode !== 200) console.log(createdAlert); + // if (statusCode !== 200) console.log(createdRule); expect(status).to.be(200); - const alertId = createdAlert.id; - objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); + const ruleId = createdRule.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); - return alertId; + return ruleId; } }); } -async function createAction(supertest: any, objectRemover: ObjectRemover): Promise { - const { statusCode, body: createdAction } = await supertest +async function createConnector(supertest: any, objectRemover: ObjectRemover): Promise { + const { statusCode, body: createdConnector } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ - name: 'index action for index threshold FT', - connector_type_id: ACTION_TYPE_ID, + name: 'index connector for index threshold FT', + connector_type_id: CONNECTOR_TYPE_ID, config: { index: ES_TEST_OUTPUT_INDEX_NAME, }, @@ -415,12 +483,12 @@ async function createAction(supertest: any, objectRemover: ObjectRemover): Promi }); // will print the error body, if an error occurred - // if (statusCode !== 200) console.log(createdAction); + // if (statusCode !== 200) console.log(createdConnector); expect(statusCode).to.be(200); - const actionId = createdAction.id; - objectRemover.add(Spaces.space1.id, actionId, 'connector', 'actions'); + const connectorId = createdConnector.id; + objectRemover.add(Spaces.space1.id, connectorId, 'connector', 'actions'); - return actionId; + return connectorId; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index baa9eeb3ce036..5077c8d720c24 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -181,7 +181,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); it('7.15.0 migrates security_solution alerts with exceptionLists to be saved object references', async () => { - // NOTE: We hae to use elastic search directly against the ".kibana" index because alerts do not expose the references which we want to test exists + // NOTE: We have to use elasticsearch directly against the ".kibana" index because alerts do not expose the references which we want to test exists const response = await es.get<{ references: [{}] }>( { index: '.kibana', @@ -373,5 +373,39 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(response.body._source?.alert?.alertTypeId).to.be('siem.queryRule'); expect(response.body._source?.alert?.enabled).to.be(false); }); + + it('8.0.1 migrates and adds tags to disabled rules in 8.0', async () => { + const responseEnabledBeforeMigration = await es.get<{ alert: RawRule }>( + { + index: '.kibana', + id: 'alert:1efdfa40-8ec7-11ec-a700-5524407a7653', + }, + { meta: true } + ); + expect(responseEnabledBeforeMigration.statusCode).to.eql(200); + const responseDisabledBeforeMigration = await es.get<{ alert: RawRule }>( + { + index: '.kibana', + id: 'alert:13fdfa40-8ec7-11ec-a700-5524407a7667', + }, + { meta: true } + ); + expect(responseDisabledBeforeMigration.statusCode).to.eql(200); + + // Both should be disabled + expect(responseEnabledBeforeMigration.body._source?.alert?.enabled).to.be(false); + expect(responseDisabledBeforeMigration.body._source?.alert?.enabled).to.be(false); + + // Only the rule that was enabled should be tagged + expect(responseEnabledBeforeMigration.body._source?.alert?.tags).to.eql([ + '__internal_rule_id:064e3fed-6328-416b-bb85-c08265088f41', + '__internal_immutable:false', + 'auto_disabled_8.0', + ]); + expect(responseDisabledBeforeMigration.body._source?.alert?.tags).to.eql([ + '__internal_rule_id:364e3fed-6328-416b-bb85-c08265088f41', + '__internal_immutable:false', + ]); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts index 77638ed90fbe4..d9b3035ac05dd 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts @@ -29,6 +29,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { { id: 'recovered', name: 'Recovered' }, ], default_action_group_id: 'default', + does_set_recovery_context: false, id: 'test.noop', name: 'Test: Noop', action_variables: { @@ -115,6 +116,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { { id: 'recovered', name: 'Recovered' }, ], defaultActionGroupId: 'default', + doesSetRecoveryContext: false, id: 'test.noop', name: 'Test: Noop', actionVariables: { diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts index db5dbc9735e66..debfb683cd883 100644 --- a/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts +++ b/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Snapshot and Restore', () => { loadTestFile(require.resolve('./policies')); loadTestFile(require.resolve('./snapshots')); + loadTestFile(require.resolve('./repositories')); }); } diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/repositories.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/repositories.ts new file mode 100644 index 0000000000000..982f32faf73ce --- /dev/null +++ b/x-pack/test/api_integration/apis/management/snapshot_restore/repositories.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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'; + +const API_BASE_PATH = '/api/snapshot_restore'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const deployment = getService('deployment'); + + describe('snapshot repositories', function () { + describe('repository types', () => { + it('returns a list of default repository types', async () => { + const { body } = await supertest.get(`${API_BASE_PATH}/repository_types`).expect(200); + + const isCloud = await deployment.isCloud(); + if (isCloud) { + // on Cloud there are only module repo types + expect(body).to.eql(['azure', 'gcs', 's3']); + } else { + // on prem there are module repo types and file system and url repo types + expect(body).to.eql(['azure', 'gcs', 's3', 'fs', 'url']); + } + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts index 13581bcfc3e7f..c453d28259a2c 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts @@ -53,7 +53,7 @@ export default function ({ getService }: FtrProviderContext) { [ConfigKey.NAME]: 'Modified name', }; - const modifiedMonitor = { ...savedMonitor, ...updates }; + const modifiedMonitor = { ...savedMonitor, ...updates, revision: 2 }; const editResponse = await supertest .put(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json index 01a8ecd409fb3..cd75c25a07798 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json @@ -58,5 +58,6 @@ }, "url": "https://example-url.com" }], - "namespace": "testnamespace" + "namespace": "testnamespace", + "revision": 1 } diff --git a/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts b/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts index b9e913524cb1f..a6ff54ccc7eb2 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts @@ -19,7 +19,7 @@ interface CheckProps { mogrify?: (doc: any) => any; refresh?: boolean; tls?: boolean | TlsProps; - isFleetManaged?: boolean; + customIndex?: string; } const getRandomMonitorId = () => { @@ -33,11 +33,11 @@ export const makeCheck = async ({ mogrify = (d) => d, refresh = true, tls = false, - isFleetManaged = false, + customIndex, }: CheckProps): Promise<{ monitorId: string; docs: any }> => { const cgFields = { monitor: { - check_group: uuid.v4(), + check_group: fields.monitor?.check_group || uuid.v4(), }, }; @@ -52,18 +52,10 @@ export const makeCheck = async ({ ip: `127.0.0.${i}`, }, }); - if (i === numIps - 1) { + if (i === numIps - 1 && fields.summary !== null) { pingFields.summary = summary; } - const doc = await makePing( - es, - monitorId, - pingFields, - mogrify, - false, - tls as any, - isFleetManaged - ); + const doc = await makePing(es, monitorId, pingFields, mogrify, false, tls as any, customIndex); docs.push(doc); // @ts-ignore summary[doc.monitor.status]++; @@ -85,7 +77,7 @@ export const makeChecks = async ( fields: { [key: string]: any } = {}, mogrify: (doc: any) => any = (d) => d, refresh: boolean = true, - isFleetManaged: boolean = false + customIndex?: string ) => { const checks = []; const oldestTime = new Date().getTime() - numChecks * every; @@ -109,7 +101,7 @@ export const makeChecks = async ( fields, mogrify, refresh: false, - isFleetManaged, + customIndex, }); checks.push(docs); } @@ -131,7 +123,7 @@ export const makeChecksWithStatus = async ( status: 'up' | 'down', mogrify: (doc: any) => any = (d) => d, refresh: boolean = true, - isFleetManaged: boolean = false + customIndex?: string ) => { const oppositeStatus = status === 'up' ? 'down' : 'up'; @@ -152,7 +144,7 @@ export const makeChecksWithStatus = async ( return mogrify(d); }, refresh, - isFleetManaged + customIndex ); }; diff --git a/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts b/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts index 29421345393a8..e663aa36cadc5 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts @@ -11,7 +11,6 @@ import type { Client } from '@elastic/elasticsearch'; import { makeTls, TlsProps } from './make_tls'; const DEFAULT_INDEX_NAME = 'heartbeat-8-generated-test'; -const DATA_STREAM_INDEX_NAME = 'synthetics-http-default'; export const makePing = async ( es: Client, @@ -20,7 +19,7 @@ export const makePing = async ( mogrify: (doc: any) => any, refresh: boolean = true, tls: boolean | TlsProps = false, - isFleetManaged: boolean | undefined = false + customIndex?: string ) => { const timestamp = new Date(); const baseDoc: any = { @@ -118,7 +117,7 @@ export const makePing = async ( const doc = mogrify(merge(baseDoc, fields)); await es.index({ - index: isFleetManaged ? DATA_STREAM_INDEX_NAME : DEFAULT_INDEX_NAME, + index: customIndex || DEFAULT_INDEX_NAME, refresh, body: doc, }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors_fleet.ts b/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors_fleet.ts index 84d6d71a4ab6d..ff257c152daa9 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors_fleet.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors_fleet.ts @@ -81,7 +81,7 @@ export default function ({ getService }: FtrProviderContext) { 'up', undefined, undefined, - true + testDataStreamName ); await makeChecksWithStatus( @@ -100,7 +100,7 @@ export default function ({ getService }: FtrProviderContext) { 'down', undefined, undefined, - true + testDataStreamName ); await makeChecksWithStatus( @@ -118,7 +118,7 @@ export default function ({ getService }: FtrProviderContext) { 'down', undefined, undefined, - true + testDataStreamName ); await makeChecksWithStatus( @@ -137,7 +137,7 @@ export default function ({ getService }: FtrProviderContext) { 'down', undefined, undefined, - true + testDataStreamName ); await makeChecksWithStatus( @@ -150,7 +150,7 @@ export default function ({ getService }: FtrProviderContext) { 'down', undefined, undefined, - true + testDataStreamName ); await client.indices.refresh(); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts index 0dfc753be402c..818ba3b366e40 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts @@ -63,7 +63,8 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const es = getService('es'); - describe('create_rules_with_exceptions', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('create_rules_with_exceptions', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts index 343db03c2ae27..e2ce3922f2d3f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts @@ -92,7 +92,8 @@ export default ({ getService }: FtrProviderContext) => { return body; } - describe('Generating signals from ml anomalies', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('Generating signals from ml anomalies', () => { before(async () => { // Order is critical here: auditbeat data must be loaded before attempting to start the ML job, // as the job looks for certain indices on start diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index 0fcb98b51fa06..3bc547ccb6a9a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -44,7 +44,8 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); - describe('create_rules', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('create_rules', () => { describe('creating rules', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 346998e7af261..b5b232f70ec89 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -69,7 +69,8 @@ export default ({ getService }: FtrProviderContext) => { /** * Specific api integration tests for threat matching rule type */ - describe('create_threat_matching', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('create_threat_matching', () => { describe('creating threat match rule', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index f9c4a1bac9d24..761792e29ea1d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -71,7 +71,8 @@ export default ({ getService }: FtrProviderContext) => { const es = getService('es'); const log = getService('log'); - describe('Generating signals from source indexes', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('Generating signals from source indexes', () => { beforeEach(async () => { await deleteSignalsIndex(supertest, log); await createSignalsIndex(supertest, log); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_export_rules.ts new file mode 100644 index 0000000000000..21d545aef0f04 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_export_rules.ts @@ -0,0 +1,126 @@ +/* + * 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 { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; + +import { deleteAllExceptions } from '../../../lists_api_integration/utils'; +import { getCreateExceptionListItemMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_item_schema.mock'; +import { getCreateExceptionListMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_schema.mock'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + binaryToString, + createRule, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getSimpleRule, +} from '../../utils'; +import { ROLES } from '../../../../plugins/security_solution/common/test'; +import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; + +// This test was meant to be more full flow, ensuring that +// exported rules are able to be reimported as opposed to +// testing the import/export functionality separately +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); + + describe('import_export_rules_flow', () => { + beforeEach(async () => { + await createSignalsIndex(supertest, log); + await createUserAndRole(getService, ROLES.soc_manager); + }); + + afterEach(async () => { + await deleteUserAndRole(getService, ROLES.soc_manager); + await deleteAllExceptions(supertest, log); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + it('should be able to reimport a rule referencing an exception list with existing comments', async () => { + // create an exception list + const { body: exceptionBody } = await supertestWithoutAuth + .post(EXCEPTION_LIST_URL) + .auth(ROLES.soc_manager, 'changeme') + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + const { body: exceptionItemBody } = await supertestWithoutAuth + .post(EXCEPTION_LIST_ITEM_URL) + .auth(ROLES.soc_manager, 'changeme') + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + comments: [{ comment: 'this exception item rocks' }], + }) + .expect(200); + + const { body: exceptionItem } = await supertest + .get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(exceptionItem.comments).to.eql([ + { + comment: 'this exception item rocks', + created_at: `${exceptionItem.comments[0].created_at}`, + created_by: 'soc_manager', + id: `${exceptionItem.comments[0].id}`, + }, + ]); + + await createRule(supertest, log, { + ...getSimpleRule(), + exceptions_list: [ + { + id: exceptionBody.id, + list_id: exceptionBody.list_id, + type: exceptionBody.type, + namespace_type: exceptionBody.namespace_type, + }, + ], + }); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .send() + .expect(200) + .parse(binaryToString); + + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`) + .set('kbn-xsrf', 'true') + .attach('file', Buffer.from(body), 'rules.ndjson') + .expect(200); + + const { body: exceptionItemFind2 } = await supertest + .get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + + // NOTE: Existing comment is uploaded successfully + // however, the meta now reflects who imported it, + // not who created the initial comment + expect(exceptionItemFind2.comments).to.eql([ + { + comment: 'this exception item rocks', + created_at: `${exceptionItemFind2.comments[0].created_at}`, + created_by: 'elastic', + id: `${exceptionItemFind2.comments[0].id}`, + }, + ]); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts index 81e0374b96738..ab509a7cba825 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; import { getCreateExceptionListMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_schema.mock'; import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -831,7 +831,33 @@ export default ({ getService }: FtrProviderContext): void => { type: 'detection', namespace_type: 'single', }, - getImportExceptionsListItemSchemaMock('test_item_id', 'i_exist'), + { + description: 'some description', + entries: [ + { + entries: [ + { + field: 'nested.field', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + field: 'some.parentField', + type: 'nested', + }, + { + field: 'some.not.nested.field', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + item_id: 'item_id_1', + list_id: 'i_exist', + name: 'Query with a rule id', + type: 'simple', + }, ]) ), 'rules.ndjson' @@ -871,6 +897,135 @@ export default ({ getService }: FtrProviderContext): void => { exceptions_success_count: 2, }); }); + + it('should resolve exception references that include comments', async () => { + // So importing a rule that references an exception list + // Keep in mind, no exception lists or rules exist yet + const simpleRule: ReturnType = { + ...getSimpleRule('rule-1'), + exceptions_list: [ + { + id: 'abc', + list_id: 'i_exist', + type: 'detection', + namespace_type: 'single', + }, + ], + }; + + // Importing the "simpleRule", along with the exception list + // it's referencing and the list's item + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + simpleRule, + { + ...getImportExceptionsListSchemaMock('i_exist'), + id: 'abc', + type: 'detection', + namespace_type: 'single', + }, + { + comments: [ + { + comment: 'This is an exception to the rule', + created_at: '2022-02-04T02:27:40.938Z', + created_by: 'elastic', + id: '845fc456-91ff-4530-bcc1-5b7ebd2f75b5', + }, + { + comment: 'I decided to add a new comment', + }, + ], + description: 'some description', + entries: [ + { + entries: [ + { + field: 'nested.field', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + field: 'some.parentField', + type: 'nested', + }, + { + field: 'some.not.nested.field', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + item_id: 'item_id_1', + list_id: 'i_exist', + name: 'Query with a rule id', + type: 'simple', + }, + ]) + ), + 'rules.ndjson' + ) + .expect(200); + + const { body: ruleResponse } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) + .send() + .expect(200); + const bodyToCompare = removeServerGeneratedProperties(ruleResponse); + const referencedExceptionList = ruleResponse.exceptions_list[0]; + + // create an exception list + const { body: exceptionBody } = await supertest + .get( + `${EXCEPTION_LIST_URL}?list_id=${referencedExceptionList.list_id}&id=${referencedExceptionList.id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(bodyToCompare.exceptions_list).to.eql([ + { + id: exceptionBody.id, + list_id: 'i_exist', + namespace_type: 'single', + type: 'detection', + }, + ]); + + const { body: exceptionItemBody } = await supertest + .get(`${EXCEPTION_LIST_ITEM_URL}?item_id="item_id_1"`) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(exceptionItemBody.comments).to.eql([ + { + comment: 'This is an exception to the rule', + created_at: `${exceptionItemBody.comments[0].created_at}`, + created_by: 'elastic', + id: `${exceptionItemBody.comments[0].id}`, + }, + { + comment: 'I decided to add a new comment', + created_at: `${exceptionItemBody.comments[1].created_at}`, + created_by: 'elastic', + id: `${exceptionItemBody.comments[1].id}`, + }, + ]); + + expect(body).to.eql({ + success: true, + success_count: 1, + errors: [], + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 2, + }); + }); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 55b7327670631..d234c70de5240 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -9,7 +9,8 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('detection engine api security and spaces enabled', function () { describe('', function () { this.tags('ciGroup11'); @@ -33,6 +34,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./generating_signals')); loadTestFile(require.resolve('./get_prepackaged_rules_status')); loadTestFile(require.resolve('./import_rules')); + loadTestFile(require.resolve('./import_export_rules')); loadTestFile(require.resolve('./read_rules')); loadTestFile(require.resolve('./resolve_read_rules')); loadTestFile(require.resolve('./update_rules')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/perform_bulk_action.ts index 06223fbca7070..1bbfafd8f0b14 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/perform_bulk_action.ts @@ -79,15 +79,20 @@ export default ({ getService }: FtrProviderContext): void => { it('should delete rules', async () => { const ruleId = 'ruleId'; - await createRule(supertest, log, getSimpleRule(ruleId)); + const testRule = getSimpleRule(ruleId); + await createRule(supertest, log, testRule); const { body } = await postBulkAction() .send({ query: '', action: BulkAction.delete }) .expect(200); - expect(body).to.eql({ success: true, rules_count: 1 }); + expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + + // Check that the deleted rule is returned with the response + expect(body.attributes.results.deleted[0].name).to.eql(testRule.name); - await await fetchRule(ruleId).expect(404); + // Check that the updates have been persisted + await fetchRule(ruleId).expect(404); }); it('should enable rules', async () => { @@ -98,16 +103,14 @@ export default ({ getService }: FtrProviderContext): void => { .send({ query: '', action: BulkAction.enable }) .expect(200); - expect(body).to.eql({ success: true, rules_count: 1 }); - - const { body: ruleBody } = await fetchRule(ruleId).expect(200); - - const referenceRule = getSimpleRuleOutput(ruleId); - referenceRule.enabled = true; + expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); - const storedRule = removeServerGeneratedProperties(ruleBody); + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].enabled).to.eql(true); - expect(storedRule).to.eql(referenceRule); + // Check that the updates have been persisted + const { body: ruleBody } = await fetchRule(ruleId).expect(200); + expect(ruleBody.enabled).to.eql(true); }); it('should disable rules', async () => { @@ -118,26 +121,31 @@ export default ({ getService }: FtrProviderContext): void => { .send({ query: '', action: BulkAction.disable }) .expect(200); - expect(body).to.eql({ success: true, rules_count: 1 }); - - const { body: ruleBody } = await fetchRule(ruleId).expect(200); + expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); - const referenceRule = getSimpleRuleOutput(ruleId); - const storedRule = removeServerGeneratedProperties(ruleBody); + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].enabled).to.eql(false); - expect(storedRule).to.eql(referenceRule); + // Check that the updates have been persisted + const { body: ruleBody } = await fetchRule(ruleId).expect(200); + expect(ruleBody.enabled).to.eql(false); }); it('should duplicate rules', async () => { const ruleId = 'ruleId'; - await createRule(supertest, log, getSimpleRule(ruleId)); + const ruleToDuplicate = getSimpleRule(ruleId); + await createRule(supertest, log, ruleToDuplicate); const { body } = await postBulkAction() .send({ query: '', action: BulkAction.duplicate }) .expect(200); - expect(body).to.eql({ success: true, rules_count: 1 }); + expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + + // Check that the duplicated rule is returned with the response + expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); + // Check that the updates have been persisted const { body: rulesResponse } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}/_find`) .set('kbn-xsrf', 'true') @@ -165,8 +173,12 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(setTagsBody).to.eql({ success: true, rules_count: 1 }); + expect(setTagsBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + // Check that the updated rule is returned with the response + expect(setTagsBody.attributes.results.updated[0].tags).to.eql(['reset-tag']); + + // Check that the updates have been persisted const { body: setTagsRule } = await fetchRule(ruleId).expect(200); expect(setTagsRule.tags).to.eql(['reset-tag']); @@ -184,8 +196,12 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(addTagsBody).to.eql({ success: true, rules_count: 1 }); + expect(addTagsBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + + // Check that the updated rule is returned with the response + expect(addTagsBody.attributes.results.updated[0].tags).to.eql(['reset-tag', ...tags]); + // Check that the updates have been persisted const { body: addedTagsRule } = await fetchRule(ruleId).expect(200); expect(addedTagsRule.tags).to.eql(['reset-tag', ...tags]); @@ -226,8 +242,12 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(setIndexBody).to.eql({ success: true, rules_count: 1 }); + expect(setIndexBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + // Check that the updated rule is returned with the response + expect(setIndexBody.attributes.results.updated[0].index).to.eql(['initial-index-*']); + + // Check that the updates have been persisted const { body: setIndexRule } = await fetchRule(ruleId).expect(200); expect(setIndexRule.index).to.eql(['initial-index-*']); @@ -245,8 +265,15 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(addIndexBody).to.eql({ success: true, rules_count: 1 }); + expect(addIndexBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + + // Check that the updated rule is returned with the response + expect(addIndexBody.attributes.results.updated[0].index).to.eql([ + 'initial-index-*', + ...indices, + ]); + // Check that the updates have been persisted const { body: addIndexRule } = await fetchRule(ruleId).expect(200); expect(addIndexRule.index).to.eql(['initial-index-*', ...indices]); @@ -291,8 +318,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body).to.eql({ success: true, rules_count: 1 }); + expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].timeline_id).to.eql(timelineId); + expect(body.attributes.results.updated[0].timeline_title).to.eql(timelineTitle); + + // Check that the updates have been persisted const { body: rule } = await fetchRule(ruleId).expect(200); expect(rule.timeline_id).to.eql(timelineId); @@ -352,8 +384,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body).to.eql({ success: true, rules_count: 1 }); + expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].timeline_id).to.eql(timelineId); + expect(body.attributes.results.updated[0].timeline_title).to.eql(timelineTitle); + // Check that the updates have been persisted const { body: rule } = await fetchRule(ruleId).expect(200); expect(rule.timeline_id).to.eql(timelineId); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 9cbaef3ad0fe2..496faa3c5e421 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -1043,8 +1043,8 @@ export const countDownTest = async ( * and error about the race condition. * rule a second attempt. It only re-tries adding the rule if it encounters a conflict once. * @param supertest The supertest deps - * @param rule The rule to create * @param log The tooling logger + * @param rule The rule to create */ export const createRule = async ( supertest: SuperTest.SuperTest, diff --git a/x-pack/test/fleet_api_integration/apis/epm/setup.ts b/x-pack/test/fleet_api_integration/apis/epm/setup.ts index eb29920b83036..253e19c2db1b1 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/setup.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/setup.ts @@ -27,7 +27,7 @@ export default function (providerContext: FtrProviderContext) { const url = '/api/fleet/epm/packages/endpoint'; await supertest.delete(url).set('kbn-xsrf', 'xxxx').send({ force: true }).expect(200); await supertest - .post(`${url}-${oldEndpointVersion}`) + .post(`${url}/${oldEndpointVersion}`) .set('kbn-xsrf', 'xxxx') .send({ force: true }) .expect(200); @@ -52,6 +52,75 @@ export default function (providerContext: FtrProviderContext) { }); }); + describe('package policy upgrade on setup', () => { + let agentPolicyId: string; + before(async function () { + const { body: agentPolicyResponse } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Test policy', + namespace: 'default', + }); + agentPolicyId = agentPolicyResponse.item.id; + }); + + after(async function () { + await supertest + .post(`/api/fleet/agent_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentPolicyId }); + }); + + it('should upgrade package policy on setup if keep policies up to date set to true', async () => { + const oldVersion = '1.9.0'; + await supertest + .post(`/api/fleet/epm/packages/system/${oldVersion}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + await supertest + .put(`/api/fleet/epm/packages/system/${oldVersion}`) + .set('kbn-xsrf', 'xxxx') + .send({ keepPoliciesUpToDate: true }) + .expect(200); + await supertest + .post('/api/fleet/package_policies') + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'system-1', + namespace: 'default', + policy_id: agentPolicyId, + package: { name: 'system', version: oldVersion }, + inputs: [], + }) + .expect(200); + + let { body } = await supertest + .get(`/api/fleet/epm/packages/system/${oldVersion}`) + .expect(200); + const latestVersion = body.item.latestVersion; + log.info(`System package latest version: ${latestVersion}`); + // make sure we're actually doing an upgrade + expect(latestVersion).not.eql(oldVersion); + + ({ body } = await supertest + .post(`/api/fleet/epm/packages/system/${latestVersion}`) + .set('kbn-xsrf', 'xxxx') + .expect(200)); + + await supertest.post(`/api/fleet/setup`).set('kbn-xsrf', 'xxxx').expect(200); + + ({ body } = await supertest + .get('/api/fleet/package_policies') + .set('kbn-xsrf', 'xxxx') + .expect(200)); + expect(body.items.find((pkg: any) => pkg.name === 'system-1').package.version).to.equal( + latestVersion + ); + }); + }); + it('does not fail when package is no longer compatible in registry', async () => { await supertest .post(`/api/fleet/epm/packages/deprecated/0.1.0`) diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts index a803b7224d0b4..da8efafe8b637 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts @@ -218,7 +218,7 @@ export default function (providerContext: FtrProviderContext) { package: { name: 'endpoint', title: 'Endpoint', - version: '1.3.0-dev.0', + version: '1.4.1', }, }) .expect(200); @@ -236,7 +236,7 @@ export default function (providerContext: FtrProviderContext) { package: { name: 'endpoint', title: 'Endpoint', - version: '1.3.0-dev.0', + version: '1.3.0', }, }) .expect(400); diff --git a/x-pack/test/fleet_api_integration/config.ts b/x-pack/test/fleet_api_integration/config.ts index fb9dc7b6b4ce6..28af25c20181a 100644 --- a/x-pack/test/fleet_api_integration/config.ts +++ b/x-pack/test/fleet_api_integration/config.ts @@ -15,7 +15,7 @@ import { defineDockerServersConfig } from '@kbn/test'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - 'docker.elastic.co/package-registry/distribution@sha256:de952debe048d903fc73e8a4472bb48bb95028d440cba852f21b863d47020c61'; + 'docker.elastic.co/package-registry/distribution@sha256:c5bf8e058727de72e561b228f4b254a14a6f880e582190d01bd5ff74318e1d0b'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); diff --git a/x-pack/test/functional/apps/discover/error_handling.ts b/x-pack/test/functional/apps/discover/error_handling.ts index 19cedc8ecb555..194a9a592c1c2 100644 --- a/x-pack/test/functional/apps/discover/error_handling.ts +++ b/x-pack/test/functional/apps/discover/error_handling.ts @@ -10,19 +10,22 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); const toasts = getService('toasts'); const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); describe('errors', function describeIndexTests() { before(async function () { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.load('x-pack/test/functional/es_archives/invalid_scripted_field'); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/invalid_scripted_field' + ); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); after(async function () { - await esArchiver.unload('x-pack/test/functional/es_archives/invalid_scripted_field'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); // this is the same test as in OSS but it catches different error message issue in different licences diff --git a/x-pack/test/functional/apps/discover/saved_searches.ts b/x-pack/test/functional/apps/discover/saved_searches.ts index f2abc6b350e5b..5d8c2aff3ed5f 100644 --- a/x-pack/test/functional/apps/discover/saved_searches.ts +++ b/x-pack/test/functional/apps/discover/saved_searches.ts @@ -30,9 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); }; - // Failing: See https://github.com/elastic/kibana/issues/104578 - // FLAKY: https://github.com/elastic/kibana/issues/114002 - describe.skip('Discover Saved Searches', () => { + describe('Discover Saved Searches', () => { before('initialize tests', async () => { await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce'); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); diff --git a/x-pack/test/functional/apps/discover/value_suggestions.ts b/x-pack/test/functional/apps/discover/value_suggestions.ts index 6cef272279548..e67d42bf27896 100644 --- a/x-pack/test/functional/apps/discover/value_suggestions.ts +++ b/x-pack/test/functional/apps/discover/value_suggestions.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; export default function ({ getService, getPageObjects }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const queryBar = getService('queryBar'); const filterBar = getService('filterBar'); @@ -28,10 +29,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.load('x-pack/test/functional/es_archives/dashboard/drilldowns'); + await kibanaServer.uiSettings.update({ + 'doc_table:legacy': true, + }); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/dashboard/drilldowns'); + await kibanaServer.uiSettings.unset('doc_table:legacy'); }); describe('useTimeRange enabled', () => { diff --git a/x-pack/test/functional/apps/discover/value_suggestions_non_timebased.ts b/x-pack/test/functional/apps/discover/value_suggestions_non_timebased.ts index b95cbea20cf82..8d95d85a88e1e 100644 --- a/x-pack/test/functional/apps/discover/value_suggestions_non_timebased.ts +++ b/x-pack/test/functional/apps/discover/value_suggestions_non_timebased.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'settings', 'context', 'header']); @@ -17,12 +18,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.loadIfNeeded( 'test/functional/fixtures/es_archiver/index_pattern_without_timefield' ); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/index_pattern_without_timefield' + ); + await kibanaServer.uiSettings.replace({ defaultIndex: 'without-timefield' }); + await kibanaServer.uiSettings.update({ + 'doc_table:legacy': true, + }); }); after(async () => { await esArchiver.unload( 'test/functional/fixtures/es_archiver/index_pattern_without_timefield' ); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.uiSettings.unset('defaultIndex'); + await kibanaServer.uiSettings.unset('doc_table:legacy'); }); it('shows all autosuggest options for a filter in discover context app', async () => { diff --git a/x-pack/test/functional/apps/lens/reference_lines.ts b/x-pack/test/functional/apps/lens/reference_lines.ts index 8ea66cb29f5a8..97c6ebf5b138f 100644 --- a/x-pack/test/functional/apps/lens/reference_lines.ts +++ b/x-pack/test/functional/apps/lens/reference_lines.ts @@ -82,7 +82,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should carry the style when moving a reference line to another group', async () => { // style it enabling the fill await testSubjects.click('lnsXY_yReferenceLineLeftPanel > lns-dimensionTrigger'); - await testSubjects.click('lnsXY_referenceLine_fill_below'); + await testSubjects.click('lnsXY_fill_below'); await PageObjects.lens.closeDimensionEditor(); // drag and drop it to the left axis @@ -93,9 +93,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('lnsXY_yReferenceLineRightPanel > lns-dimensionTrigger'); expect( - await find.existsByCssSelector( - '[data-test-subj="lnsXY_referenceLine_fill_below"][class$="isSelected"]' - ) + await find.existsByCssSelector('[data-test-subj="lnsXY_fill_below"][class$="isSelected"]') ).to.be(true); await PageObjects.lens.closeDimensionEditor(); }); @@ -113,9 +111,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ) ).click(); expect( - await find.existsByCssSelector( - '[data-test-subj="lnsXY_referenceLine_fill_below"][class$="isSelected"]' - ) + await find.existsByCssSelector('[data-test-subj="lnsXY_fill_below"][class$="isSelected"]') ).to.be(true); await PageObjects.lens.closeDimensionEditor(); }); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts index 908e45daf7105..bc11a44148546 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts @@ -15,7 +15,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./regression_creation')); loadTestFile(require.resolve('./classification_creation')); loadTestFile(require.resolve('./cloning')); - loadTestFile(require.resolve('./feature_importance')); + loadTestFile(require.resolve('./results_view_content')); loadTestFile(require.resolve('./regression_creation_saved_search')); loadTestFile(require.resolve('./classification_creation_saved_search')); loadTestFile(require.resolve('./outlier_detection_creation_saved_search')); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/results_view_content.ts similarity index 61% rename from x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts rename to x-pack/test/functional/apps/ml/data_frame_analytics/results_view_content.ts index 9d816e083e496..e2db6ca28a6e6 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/results_view_content.ts @@ -14,12 +14,23 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('total feature importance panel and decision path popover', function () { + describe('results view content and total feature importance', function () { const testDataList: Array<{ suiteTitle: string; archive: string; indexPattern: { name: string; timeField: string }; job: DeepPartial; + sortBy: { + column: string; + sortDirection: 'asc' | 'desc'; + }; + expected: { + histogramCharts: Array<{ chartAvailable: boolean; id: string; legend?: string }>; + sortBy: { + columnIndex: number; + expectedValues: string[]; + }; + }; }> = (() => { const timestamp = Date.now(); @@ -69,6 +80,29 @@ export default function ({ getService }: FtrProviderContext) { model_memory_limit: '60mb', allow_lazy_start: false, }, + sortBy: { + column: 'ml_central_air.is_training', + // asc is `False-True` + sortDirection: 'asc', + }, + expected: { + histogramCharts: [ + // We are not checking for chart's legend content here + // because results can always change + { chartAvailable: true, id: 'ml_central_air.is_training' }, + { chartAvailable: true, id: 'ml_central_air.CentralAir_prediction' }, + { chartAvailable: true, id: 'CentralAir' }, + { chartAvailable: true, id: 'ml_central_air.prediction_probability' }, + { chartAvailable: false, id: 'ml_central_air.feature_importance' }, + { chartAvailable: true, id: 'ml_central_air.prediction_score' }, + { chartAvailable: false, id: 'ml_central_air.top_classes' }, + { chartAvailable: true, id: '1stFlrSF' }, + ], + sortBy: { + columnIndex: 0, + expectedValues: ['false'], + }, + }, }, { suiteTitle: 'multi class classification job', @@ -115,6 +149,31 @@ export default function ({ getService }: FtrProviderContext) { model_memory_limit: '60mb', allow_lazy_start: false, }, + sortBy: { + column: 'ml_heating_qc.is_training', + // desc is `True-False` + sortDirection: 'desc', + }, + expected: { + histogramCharts: [ + { chartAvailable: true, id: 'ml_heating_qc.is_training' }, + { chartAvailable: true, id: 'ml_heating_qc.heatingqc' }, + { chartAvailable: true, id: 'HeatingQC' }, + { chartAvailable: true, id: 'ml_heating_qc.prediction_probability' }, + { chartAvailable: false, id: 'ml_heating_qc.feature_importance' }, + { chartAvailable: true, id: 'ml_heating_qc.prediction_score' }, + { + chartAvailable: false, + id: 'ml_heating_qc.top_classes', + legend: 'Chart not supported.', + }, + { chartAvailable: true, id: '1stFlrSF' }, + ], + sortBy: { + columnIndex: 0, + expectedValues: ['true'], + }, + }, }, { suiteTitle: 'regression job', @@ -164,6 +223,26 @@ export default function ({ getService }: FtrProviderContext) { }, model_memory_limit: '20mb', }, + sortBy: { + column: 'ml.is_training', + // desc is `True-False` + sortDirection: 'desc', + }, + expected: { + histogramCharts: [ + { chartAvailable: true, id: 'ml.is_training' }, + { chartAvailable: true, id: 'ml.test' }, + { chartAvailable: true, id: 'stab', legend: '-0.06 - 0.11' }, + { chartAvailable: true, id: 'g1', legend: '0.05 - 1' }, + { chartAvailable: true, id: 'g2', legend: '0.05 - 1' }, + { chartAvailable: true, id: 'g3', legend: '0.05 - 1' }, + { chartAvailable: true, id: 'g4', legend: '0.05 - 1' }, + ], + sortBy: { + columnIndex: 0, + expectedValues: ['true'], + }, + }, }, ]; })(); @@ -207,12 +286,41 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsResults.assertTotalFeatureImportanceEvaluatePanelExists(); }); - it('should display the feature importance decision path in the data grid', async () => { + it('should display the feature importance decision path', async () => { await ml.dataFrameAnalyticsResults.assertResultsTableExists(); await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty(); await ml.dataFrameAnalyticsResults.openFeatureImportancePopover(); await ml.dataFrameAnalyticsResults.assertFeatureImportancePopoverContent(); }); + + it('should display the histogram charts', async () => { + await ml.testExecution.logTestStep( + 'displays the histogram charts when option is enabled' + ); + await ml.dataFrameAnalyticsResults.enableResultsTablePreviewHistogramCharts(true); + await ml.dataFrameAnalyticsResults.assertResultsTablePreviewHistogramCharts( + testData.expected.histogramCharts + ); + + await ml.testExecution.logTestStep('hides the histogram charts when option is disabled'); + await ml.dataFrameAnalyticsResults.enableResultsTablePreviewHistogramCharts(false); + await ml.dataFrameAnalyticsResults.assertResultsTablePreviewHistogramChartsMissing( + testData.expected.histogramCharts + ); + }); + + it('should sort and hide/show columns correctly', async () => { + await ml.testExecution.logTestStep('sorts table'); + await ml.dataFrameAnalyticsResults.toggleColumnSortPopoverState(true); + await ml.dataFrameAnalyticsResults.setColumnToSortBy( + testData.sortBy.column, + testData.sortBy.sortDirection + ); + + await ml.testExecution.logTestStep('shows all and hides all columns'); + await ml.dataFrameAnalyticsResults.showAllResultsTableColumns(); + await ml.dataFrameAnalyticsResults.hideAllResultsTableColumns(); + }); }); } }); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 67c2f9b386425..7e1ac8e5481bc 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -15,7 +15,7 @@ import { pageObjects } from './page_objects'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - 'docker.elastic.co/package-registry/distribution:ffcbe0ba25b9bae09a671249cbb1b25af0aa1994'; + 'docker.elastic.co/package-registry/distribution@sha256:c5bf8e058727de72e561b228f4b254a14a6f880e582190d01bd5ff74318e1d0b'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/functional/es_archives/alerts/data.json b/x-pack/test/functional/es_archives/alerts/data.json index afa54208512f4..96dad21732d0d 100644 --- a/x-pack/test/functional/es_archives/alerts/data.json +++ b/x-pack/test/functional/es_archives/alerts/data.json @@ -663,3 +663,194 @@ } } +{ + "type":"doc", + "value":{ + "id":"alert:1efdfa40-8ec7-11ec-a700-5524407a7653", + "index":".kibana_1", + "source":{ + "alert":{ + "name":"enabled 7.16.1 query rule", + "tags":[ + "__internal_rule_id:064e3fed-6328-416b-bb85-c08265088f41", + "__internal_immutable:false" + ], + "alertTypeId":"siem.signals", + "consumer":"siem", + "params":{ + "author":[ + + ], + "description":"enabled 7.16.1 query rule", + "ruleId":"064e3fed-6328-416b-bb85-c08265088f41", + "falsePositives":[ + + ], + "from":"now-36000060s", + "immutable":false, + "license":"", + "outputIndex":".siem-signals-default", + "meta":{ + "from":"10000h" + }, + "maxSignals":100, + "riskScore":21, + "riskScoreMapping":[ + + ], + "severity":"low", + "severityMapping":[ + + ], + "threat":[ + + ], + "to":"now", + "references":[ + + ], + "version":4, + "exceptionsList":[ + ], + "type":"query", + "language":"kuery", + "index":[ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + "test-index-3" + ], + "query":"*:*", + "filters":[ + + ] + }, + "schedule":{ + "interval":"1m" + }, + "enabled":true, + "actions":[ + + ], + "throttle":null, + "apiKeyOwner":"3270256467", + "apiKey":"UnFCyd4CthfIw6Mv5SRRbhYC7NPD2Jn8L+aCT/JfPQ5/poIsrwkh0plDAllpBYjFYhGXTQPtSPgu9yAwsuuaUhXxtFcnFHPleQeLvmu8VICueSxgMnK25Oqku8hfSQw9ETf9WZ3yXQOwvD9i8fkIUx84zt5q2LMuZv826fY9433/seVoCWspTj5qm2DchMDgTWzMKQaR2zbvRA==", + "createdBy":"3270256467", + "updatedBy":"3270256467", + "createdAt":"2022-02-16T01:24:02.121Z", + "muteAll":true, + "mutedInstanceIds":[ + + ], + "scheduledTaskId":"2030ade0-8ec7-11ec-a700-5524407a7653" + }, + "type":"alert", + "references":[], + "migrationVersion":{ + "alert":"7.16.0" + }, + "updated_at":"2022-02-16T16:20:19.375Z" + } + } +} + +{ + "type":"doc", + "value":{ + "id":"alert:13fdfa40-8ec7-11ec-a700-5524407a7667", + "index":".kibana_1", + "source":{ + "alert":{ + "name":"disabled 7.16.1 query rule", + "tags":[ + "__internal_rule_id:364e3fed-6328-416b-bb85-c08265088f41", + "__internal_immutable:false" + ], + "alertTypeId":"siem.signals", + "consumer":"siem", + "params":{ + "author":[ + + ], + "description":"disabled 7.16.1 query rule", + "ruleId":"364e3fed-6328-416b-bb85-c08265088f41", + "falsePositives":[ + + ], + "from":"now-36000060s", + "immutable":false, + "license":"", + "outputIndex":".siem-signals-default", + "meta":{ + "from":"10000h" + }, + "maxSignals":100, + "riskScore":21, + "riskScoreMapping":[ + + ], + "severity":"low", + "severityMapping":[ + + ], + "threat":[ + + ], + "to":"now", + "references":[ + + ], + "version":4, + "exceptionsList":[ + ], + "type":"query", + "language":"kuery", + "index":[ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + "test-index-3" + ], + "query":"*:*", + "filters":[ + + ] + }, + "schedule":{ + "interval":"1m" + }, + "enabled":true, + "actions":[ + + ], + "throttle":null, + "apiKeyOwner":"3270256467", + "apiKey":"UnFCyd4CthfIw6Mv5SRRbhYC7NPD2Jn8L+aCT/JfPQ5/poIsrwkh0plDAllpBYjFYhGXTQPtSPgu9yAwsuuaUhXxtFcnFHPleQeLvmu8VICueSxgMnK25Oqku8hfSQw9ETf9WZ3yXQOwvD9i8fkIUx84zt5q2LMuZv826fY9433/seVoCWspTj5qm2DchMDgTWzMKQaR2zbvRA==", + "createdBy":"3270256467", + "updatedBy":"3270256467", + "createdAt":"2022-02-16T01:25:02.121Z", + "muteAll":true, + "mutedInstanceIds":[ + + ], + "scheduledTaskId":null + }, + "type":"alert", + "references":[], + "migrationVersion":{ + "alert":"7.16.0" + }, + "updated_at":"2022-02-16T16:21:19.375Z" + } + } +} diff --git a/x-pack/test/functional/es_archives/alerts/mappings.json b/x-pack/test/functional/es_archives/alerts/mappings.json index ecaf138a0cdc4..babc00babc838 100644 --- a/x-pack/test/functional/es_archives/alerts/mappings.json +++ b/x-pack/test/functional/es_archives/alerts/mappings.json @@ -341,4 +341,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/test/functional/es_archives/invalid_scripted_field/data.json.gz b/x-pack/test/functional/es_archives/invalid_scripted_field/data.json.gz deleted file mode 100644 index 380dd6049179a..0000000000000 Binary files a/x-pack/test/functional/es_archives/invalid_scripted_field/data.json.gz and /dev/null differ 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 deleted file mode 100644 index 0d41e0ce86c14..0000000000000 --- a/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "dynamic": "strict", - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/x-pack/test/functional/services/ml/common_data_grid.ts b/x-pack/test/functional/services/ml/common_data_grid.ts new file mode 100644 index 0000000000000..c48cf92107dab --- /dev/null +++ b/x-pack/test/functional/services/ml/common_data_grid.ts @@ -0,0 +1,223 @@ +/* + * 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 { chunk } from 'lodash'; +import type { ProvidedType } from '@kbn/test'; +import type { FtrProviderContext } from '../../ftr_provider_context'; +import { asyncForEach } from '../../apps/ml/settings/common'; + +export interface SetValueOptions { + clearWithKeyboard?: boolean; + enforceDataTestSubj?: boolean; + typeCharByChar?: boolean; +} + +export type MlCommonDataGrid = ProvidedType; + +export function MachineLearningCommonDataGridProvider({ getService }: FtrProviderContext) { + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + const find = getService('find'); + + return { + dataGridSelector(tableSubj: string, subSelector?: string) { + return `~${tableSubj} > ${subSelector}`; + }, + + async parseEuiDataGrid(tableSubj: string, maxColumnsToParse: number) { + const table = await testSubjects.find(`~${tableSubj}`); + const $ = await table.parseDomContent(); + + // Get the content of each cell and divide them up into rows. + // Virtualized cells outside the view area are not present in the DOM until they + // are scroilled into view, so we're limiting the number of parsed columns. + // 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') + .toArray() + .map((cell) => { + const cellText = $(cell).text(); + const pattern = /^(.*)Row: (\d+); Column: (\d+)$/; + const matches = cellText.match(pattern); + expect(matches).to.not.eql(null, `Cell text should match pattern '${pattern}'`); + return { text: matches![1], row: Number(matches![2]), column: Number(matches![3]) }; + }) + .filter((cell) => + maxColumnsToParse !== undefined ? cell?.column <= maxColumnsToParse : false + ) + .sort(function (a, b) { + return a.row - b.row || a.column - b.column; + }) + .map((cell) => cell.text); + + const rows = maxColumnsToParse !== undefined ? chunk(cells, maxColumnsToParse) : cells; + return rows; + }, + + async getDataGridCells(tableSubj: string) { + return (await testSubjects.find(tableSubj)).findAllByTestSubject('dataGridRowCell'); + }, + + async assertEuiDataGridNotEmpty(tableSubj: string) { + const dataGridCells = await this.getDataGridCells(tableSubj); + expect(dataGridCells.length).to.be.greaterThan( + 0, + `EuiDataGrid '${tableSubj}' should have at least one cell (got '${dataGridCells.length}')` + ); + }, + + async assertEuiDataGridEmpty(tableSubj: string) { + const dataGridCells = await this.getDataGridCells(tableSubj); + expect(dataGridCells.length).to.eql( + 0, + `EuiDataGrid '${tableSubj}' should be empty (got '${dataGridCells.length} cells')` + ); + }, + + async getEuiDataGridColumnUniqueValues(tableSubj: string, column: number) { + // get a 2D array of rows and cell values + // only parse columns up to the one we want to assert + const rows = await this.parseEuiDataGrid(tableSubj, column + 1); + + // reduce the rows data to an array of unique values in the specified column + const uniqueColumnValues = rows + .map((row) => row[column]) + .flat() + .filter((v, i, a) => a.indexOf(v) === i); + return uniqueColumnValues; + }, + + async assertEuiDataGridColumnValues( + tableSubj: string, + column: number, + expectedColumnValues: string[] + ) { + await retry.tryForTime(20 * 1000, async () => { + const uniqueColumnValues = await this.getEuiDataGridColumnUniqueValues(tableSubj, column); + + uniqueColumnValues.sort(); + // check if the returned unique value matches the supplied filter value + expect(uniqueColumnValues).to.eql( + expectedColumnValues, + `Unique EuiDataGrid '${tableSubj}' column values should be '${expectedColumnValues.join()}' (got ${uniqueColumnValues.join()})` + ); + }); + }, + + async assertEuiDataGridColumnValuesNotEmpty(tableSubj: string, column: number) { + await retry.tryForTime(20 * 1000, async () => { + const uniqueColumnValues = await this.getEuiDataGridColumnUniqueValues(tableSubj, column); + uniqueColumnValues.forEach((value) => { + // check if the returned unique value is not empty + expect(value).to.not.eql(''); + }); + }); + }, + + async assertColumnSelectPopoverOpenState(tableSubj: string, expectedState: boolean) { + const popoverSelector = this.dataGridSelector(tableSubj, 'dataGridColumnSelectorPopover'); + + if (expectedState === true) { + await testSubjects.existOrFail(popoverSelector, { + timeout: 5 * 1000, + }); + } else { + await testSubjects.missingOrFail(popoverSelector, { + timeout: 5 * 1000, + }); + } + }, + + async toggleColumnSelectPopoverState(tableSubj: string, state: boolean) { + await retry.tryForTime(15 * 1000, async () => { + const popoverIsOpen = await testSubjects.exists( + this.dataGridSelector(tableSubj, '~dataGridColumnSelectorPopoverButton') + ); + if (popoverIsOpen !== state) { + await testSubjects.click( + this.dataGridSelector(tableSubj, '~dataGridColumnSelectorButton') + ); + } + await this.assertColumnSelectPopoverOpenState(tableSubj, state); + }); + }, + + async assertColumnSelectorsSwitchState(expectedState: boolean) { + await retry.tryForTime(5 * 1000, async () => { + const visibilityToggles = await ( + await find.byClassName('euiDataGrid__controlScroll') + ).findAllByCssSelector('[role="switch"]'); + + await asyncForEach(visibilityToggles, async (toggle) => { + const checked = (await toggle.getAttribute('aria-checked')) === 'true'; + expect(checked).to.eql( + expectedState, + `Expected column select switch button's checked state to be ${expectedState} (got ${checked})` + ); + }); + }); + }, + + async hideAllColumns(tableSubj: string) { + await this.toggleColumnSelectPopoverState(tableSubj, true); + await testSubjects.click('dataGridColumnSelectorHideAllButton'); + await this.assertColumnSelectorsSwitchState(false); + await browser.pressKeys(browser.keys.ESCAPE); + }, + + async showAllColumns(tableSubj: string) { + await this.toggleColumnSelectPopoverState(tableSubj, true); + await testSubjects.click('dataGridColumnSelectorShowAllButton'); + await this.assertColumnSelectorsSwitchState(true); + await browser.pressKeys(browser.keys.ESCAPE); + }, + + async assertColumnSortPopoverOpenState(tableSubj: string, expectedOpenState: boolean) { + const popoverSelector = this.dataGridSelector(tableSubj, 'dataGridColumnSortingPopover'); + + if (expectedOpenState === true) { + await testSubjects.existOrFail(popoverSelector, { + timeout: 5 * 1000, + }); + } else { + await testSubjects.missingOrFail(popoverSelector, { + timeout: 5 * 1000, + }); + } + }, + + async toggleColumnSortPopoverState(tableSubj: string, expectedState: boolean) { + await retry.tryForTime(15 * 1000, async () => { + const popoverIsOpen = await testSubjects.exists('dataGridColumnSortingSelectionButton'); + if (popoverIsOpen !== expectedState) { + await testSubjects.click(this.dataGridSelector(tableSubj, 'dataGridColumnSortingButton')); + } + await this.assertColumnSortPopoverOpenState(tableSubj, expectedState); + }); + }, + + async setColumnToSortBy(tableSubj: string, columnId: string, sortDirection: 'asc' | 'desc') { + await this.toggleColumnSortPopoverState(tableSubj, true); + await retry.tryForTime(15 * 1000, async () => { + // Pick fields to sort by + await testSubjects.existOrFail('dataGridColumnSortingSelectionButton'); + await testSubjects.click('dataGridColumnSortingSelectionButton'); + await testSubjects.existOrFail(`dataGridColumnSortingPopoverColumnSelection-${columnId}`); + await testSubjects.click(`dataGridColumnSortingPopoverColumnSelection-${columnId}`); + await testSubjects.existOrFail(`euiDataGridColumnSorting-sortColumn-${columnId}`); + // Click sorting direction + await testSubjects.click( + `euiDataGridColumnSorting-sortColumn-${columnId}-${sortDirection}` + ); + // Close popover + await browser.pressKeys(browser.keys.ESCAPE); + }); + }, + }; +} diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index a97c25b2fcbbf..6432b1675dc9d 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -7,9 +7,7 @@ import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test'; - import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; - import { FtrProviderContext } from '../../ftr_provider_context'; import type { CanvasElementColorStats } from '../canvas_element'; diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index 3bb0f6d8169ee..89f5ff5ef17a7 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -152,7 +152,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( ) { // 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 () => { + await retry.tryForTime(10000, async () => { for (const expected of expectedHistogramCharts.values()) { const id = expected.id; await testSubjects.existOrFail(`mlDataGridChart-${id}`); @@ -186,16 +186,6 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( }); }, - // async assertIncludedFieldsSelection(expectedSelection: string[]) { - // const includesTable = await testSubjects.find('mlAnalyticsCreateJobWizardIncludesSelect'); - // const actualSelection = await includesTable.findByClassName('euiTableRow-isSelected'); - - // expect(actualSelection).to.eql( - // expectedSelection, - // `Included fields should be '${expectedSelection}' (got '${actualSelection}')` - // ); - // }, - async assertDestIndexInputExists() { await retry.tryForTime(4000, async () => { await testSubjects.existOrFail('mlAnalyticsCreateJobFlyoutDestinationIndexInput'); diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_results.ts b/x-pack/test/functional/services/ml/data_frame_analytics_results.ts index dc7ccc9130fab..1c64a514de5be 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_results.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_results.ts @@ -7,15 +7,15 @@ import expect from '@kbn/expect'; import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; - import { FtrProviderContext } from '../../ftr_provider_context'; - import type { CanvasElementColorStats } from '../canvas_element'; import type { MlCommonUI } from './common_ui'; +import type { MlCommonDataGrid } from './common_data_grid'; export function MachineLearningDataFrameAnalyticsResultsProvider( { getService }: FtrProviderContext, - mlCommonUI: MlCommonUI + mlCommonUI: MlCommonUI, + commonDataGrid: MlCommonDataGrid ) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -61,20 +61,132 @@ export function MachineLearningDataFrameAnalyticsResultsProvider( }); }, - async getResultTableRows() { - return (await testSubjects.find('mlExplorationDataGrid loaded')).findAllByTestSubject( - 'dataGridRowCell' + async assertResultsTablePreviewHistogramChartButtonCheckState(expectedCheckState: boolean) { + const actualCheckState = + (await testSubjects.getAttribute( + 'mlExplorationDataGridHistogramButton', + 'aria-pressed' + )) === 'true'; + expect(actualCheckState).to.eql( + expectedCheckState, + `Chart histogram button check state should be '${expectedCheckState}' (got '${actualCheckState}')` ); }, - async assertResultsTableNotEmpty() { - const resultTableRows = await this.getResultTableRows(); - expect(resultTableRows.length).to.be.greaterThan( - 0, - `DFA results table should have at least one row (got '${resultTableRows.length}')` + async enableResultsTablePreviewHistogramCharts(expectedButtonState: boolean) { + await retry.tryForTime(5000, async () => { + const actualState = + (await testSubjects.getAttribute( + 'mlExplorationDataGridHistogramButton', + 'aria-pressed' + )) === 'true'; + + if (actualState !== expectedButtonState) { + await testSubjects.click('mlExplorationDataGridHistogramButton'); + await this.assertResultsTablePreviewHistogramChartButtonCheckState(expectedButtonState); + } + }); + }, + + async assertResultsTablePreviewHistogramChartsMissing( + expectedHistogramCharts: Array<{ + chartAvailable: boolean; + id: string; + legend?: string; + }> + ) { + for (const expected of expectedHistogramCharts.values()) { + const id = expected.id; + await testSubjects.missingOrFail(`mlDataGridChart-${id}`); + } + }, + + async assertResultsTablePreviewHistogramCharts( + expectedHistogramCharts: Array<{ + chartAvailable: boolean; + id: string; + legend?: string; + }> + ) { + // 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 () => { + for (const expected of expectedHistogramCharts.values()) { + const id = expected.id; + await testSubjects.existOrFail(`mlDataGridChart-${id}`); + + if (expected.chartAvailable) { + await testSubjects.existOrFail(`mlDataGridChart-${id}-histogram`); + } + + const actualLegend = await testSubjects.getVisibleText(`mlDataGridChart-${id}-legend`); + if (expected.legend) { + expect(actualLegend).to.eql( + expected.legend, + `Legend text for column '${id}' should be '${expected.legend}' (got '${actualLegend}')` + ); + } + const actualId = await testSubjects.getVisibleText(`mlDataGridChart-${id}-id`); + expect(actualId).to.eql( + expected.id, + `Id text for column '${id}' should be '${expected.id}' (got '${actualId}')` + ); + } + }); + }, + + async assertColumnSelectPopoverOpenState(expectedState: boolean) { + await commonDataGrid.assertColumnSelectPopoverOpenState( + 'mlExplorationDataGrid', + expectedState + ); + }, + + async toggleColumnSelectPopoverState(state: boolean) { + await commonDataGrid.toggleColumnSelectPopoverState('mlExplorationDataGrid', state); + }, + + async hideAllResultsTableColumns() { + await commonDataGrid.hideAllColumns('mlExplorationDataGrid'); + await this.assertResultsTableEmpty(); + }, + + async showAllResultsTableColumns() { + await commonDataGrid.showAllColumns('mlExplorationDataGrid'); + await this.assertResultsTableNotEmpty(); + }, + + async assertColumnSortPopoverOpenState(expectedOpenState: boolean) { + await commonDataGrid.assertColumnSortPopoverOpenState( + 'mlExplorationDataGrid', + expectedOpenState + ); + }, + + async toggleColumnSortPopoverState(expectedState: boolean) { + await commonDataGrid.toggleColumnSortPopoverState('mlExplorationDataGrid', expectedState); + }, + + async setColumnToSortBy(columnId: string, sortDirection: 'asc' | 'desc') { + await commonDataGrid.setColumnToSortBy('mlExplorationDataGrid', columnId, sortDirection); + }, + + async assertResultsTableColumnValues(column: number, expectedColumnValues: string[]) { + await commonDataGrid.assertEuiDataGridColumnValues( + 'mlExplorationDataGrid', + column, + expectedColumnValues ); }, + async assertResultsTableNotEmpty() { + await commonDataGrid.assertEuiDataGridNotEmpty('mlExplorationDataGrid loaded'); + }, + + async assertResultsTableEmpty() { + await commonDataGrid.assertEuiDataGridEmpty('mlExplorationDataGrid loaded'); + }, + async assertTotalFeatureImportanceEvaluatePanelExists() { await testSubjects.existOrFail('mlDFExpandableSection-FeatureImportanceSummary'); await this.scrollFeatureImportanceIntoView(); diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index f0cb2da9efdde..ae596abb562d6 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -13,6 +13,7 @@ import { MachineLearningAPIProvider } from './api'; import { MachineLearningCommonAPIProvider } from './common_api'; import { MachineLearningCommonConfigsProvider } from './common_config'; import { MachineLearningCommonUIProvider } from './common_ui'; +import { MachineLearningCommonDataGridProvider } from './common_data_grid'; import { MachineLearningCustomUrlsProvider } from './custom_urls'; import { MachineLearningDataFrameAnalyticsProvider } from './data_frame_analytics'; import { MachineLearningDataFrameAnalyticsCreationProvider } from './data_frame_analytics_creation'; @@ -59,6 +60,7 @@ import { MlNodesPanelProvider } from './ml_nodes_list'; export function MachineLearningProvider(context: FtrProviderContext) { const commonAPI = MachineLearningCommonAPIProvider(context); const commonUI = MachineLearningCommonUIProvider(context); + const commonDataGrid = MachineLearningCommonDataGridProvider(context); const anomaliesTable = MachineLearningAnomaliesTableProvider(context); const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context); @@ -81,7 +83,8 @@ export function MachineLearningProvider(context: FtrProviderContext) { const dataFrameAnalyticsEdit = MachineLearningDataFrameAnalyticsEditProvider(context, commonUI); const dataFrameAnalyticsResults = MachineLearningDataFrameAnalyticsResultsProvider( context, - commonUI + commonUI, + commonDataGrid ); const dataFrameAnalyticsMap = MachineLearningDataFrameAnalyticsMapProvider(context); const dataFrameAnalyticsTable = MachineLearningDataFrameAnalyticsTableProvider(context); @@ -134,6 +137,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { api, commonAPI, commonConfig, + commonDataGrid, commonUI, customUrls, dashboardJobSelectionTable, diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts index bd413eb2893c2..785d7e33d5042 100644 --- a/x-pack/test/functional/services/transform/transform_table.ts +++ b/x-pack/test/functional/services/transform/transform_table.ts @@ -16,6 +16,7 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); + const ml = getService('ml'); return new (class TransformTable { public async parseTransformTable() { @@ -62,50 +63,6 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { return rows; } - async parseEuiDataGrid(tableSubj: string) { - const table = await testSubjects.find(`~${tableSubj}`); - const $ = await table.parseDomContent(); - const rows = []; - - // For each row, get the content of each cell and - // add its values as an array to each row. - for (const tr of $.findTestSubjects(`~dataGridRow`).toArray()) { - rows.push( - $(tr) - .find('.euiDataGridRowCell__truncate') - .toArray() - .map((cell) => $(cell).text().trim()) - ); - } - - return rows; - } - - async assertEuiDataGridColumnValues( - tableSubj: string, - column: number, - expectedColumnValues: string[] - ) { - await retry.tryForTime(2000, async () => { - // get a 2D array of rows and cell values - const rows = await this.parseEuiDataGrid(tableSubj); - - // reduce the rows data to an array of unique values in the specified column - const uniqueColumnValues = rows - .map((row) => row[column]) - .flat() - .filter((v, i, a) => a.indexOf(v) === i); - - uniqueColumnValues.sort(); - - // check if the returned unique value matches the supplied filter value - expect(uniqueColumnValues).to.eql( - expectedColumnValues, - `Expected '${tableSubj}' column values to be '${expectedColumnValues}' (got '${uniqueColumnValues}')` - ); - }); - } - public async waitForRefreshButtonLoaded() { await testSubjects.existOrFail('~transformRefreshTransformListButton', { timeout: 10 * 1000, @@ -384,7 +341,11 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { public async assertTransformsExpandedRowPreviewColumnValues(column: number, values: string[]) { await this.waitForTransformsExpandedRowPreviewTabToLoad(); - await this.assertEuiDataGridColumnValues('transformPivotPreview', column, values); + await ml.commonDataGrid.assertEuiDataGridColumnValues( + 'transformPivotPreview', + column, + values + ); } public async assertTransformDeleteModalExists() { diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index d96b77ef4998a..714ae52a6641b 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -4,8 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { chunk } from 'lodash'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -98,84 +96,11 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi await testSubjects.missingOrFail('transformPivotPreviewHistogramButton'); }, - async parseEuiDataGrid(tableSubj: string, maxColumnsToParse: number) { - const table = await testSubjects.find(`~${tableSubj}`); - const $ = await table.parseDomContent(); - - // Get the content of each cell and divide them up into rows. - // Virtualized cells outside the view area are not present in the DOM until they - // are scroilled into view, so we're limiting the number of parsed columns. - // 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') - .toArray() - .map((cell) => { - const cellText = $(cell).text(); - const pattern = /^(.*)Row: (\d+); Column: (\d+)$/; - const matches = cellText.match(pattern); - expect(matches).to.not.eql(null, `Cell text should match pattern '${pattern}'`); - return { text: matches![1], row: Number(matches![2]), column: Number(matches![3]) }; - }) - .filter((cell) => cell?.column <= maxColumnsToParse) - .sort(function (a, b) { - return a.row - b.row || a.column - b.column; - }) - .map((cell) => cell.text); - - const rows = chunk(cells, maxColumnsToParse); - return rows; - }, - - async assertEuiDataGridColumnValues( - tableSubj: string, - column: number, - expectedColumnValues: string[] - ) { - await retry.tryForTime(20 * 1000, async () => { - // get a 2D array of rows and cell values - // only parse columns up to the one we want to assert - const rows = await this.parseEuiDataGrid(tableSubj, column + 1); - - // reduce the rows data to an array of unique values in the specified column - const uniqueColumnValues = rows - .map((row) => row[column]) - .flat() - .filter((v, i, a) => a.indexOf(v) === i); - - uniqueColumnValues.sort(); - - // check if the returned unique value matches the supplied filter value - expect(uniqueColumnValues).to.eql( - expectedColumnValues, - `Unique EuiDataGrid column values should be '${expectedColumnValues.join()}' (got ${uniqueColumnValues.join()})` - ); - }); - }, - - async assertEuiDataGridColumnValuesNotEmpty(tableSubj: string, column: number) { - await retry.tryForTime(20 * 1000, async () => { - // get a 2D array of rows and cell values - // only parse columns up to the one we want to assert - const rows = await this.parseEuiDataGrid(tableSubj, column + 1); - - // reduce the rows data to an array of unique values in the specified column - const uniqueColumnValues = rows - .map((row) => row[column]) - .flat() - .filter((v, i, a) => a.indexOf(v) === i); - - uniqueColumnValues.forEach((value) => { - // check if the returned unique value is not empty - expect(value).to.not.eql(''); - }); - }); - }, - async assertIndexPreview(columns: number, expectedNumberOfRows: number) { await retry.tryForTime(20 * 1000, async () => { // get a 2D array of rows and cell values // only parse the first column as this is sufficient to get assert the row count - const rowsData = await this.parseEuiDataGrid('transformIndexPreview', 1); + const rowsData = await ml.commonDataGrid.parseEuiDataGrid('transformIndexPreview', 1); expect(rowsData).to.length( expectedNumberOfRows, @@ -194,19 +119,33 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi }, async assertIndexPreviewColumnValues(column: number, values: string[]) { - await this.assertEuiDataGridColumnValues('transformIndexPreview', column, values); + await ml.commonDataGrid.assertEuiDataGridColumnValues( + 'transformIndexPreview', + column, + values + ); }, async assertIndexPreviewColumnValuesNotEmpty(column: number) { - await this.assertEuiDataGridColumnValuesNotEmpty('transformIndexPreview', column); + await ml.commonDataGrid.assertEuiDataGridColumnValuesNotEmpty( + 'transformIndexPreview', + column + ); }, async assertPivotPreviewColumnValues(column: number, values: string[]) { - await this.assertEuiDataGridColumnValues('transformPivotPreview', column, values); + await ml.commonDataGrid.assertEuiDataGridColumnValues( + 'transformPivotPreview', + column, + values + ); }, async assertPivotPreviewColumnValuesNotEmpty(column: number) { - await this.assertEuiDataGridColumnValuesNotEmpty('transformPivotPreview', column); + await ml.commonDataGrid.assertEuiDataGridColumnValuesNotEmpty( + 'transformPivotPreview', + column + ); }, async assertPivotPreviewLoaded() { diff --git a/x-pack/test/functional_execution_context/tests/browser.ts b/x-pack/test/functional_execution_context/tests/browser.ts index ca777e1d4acf3..aa990dccda86c 100644 --- a/x-pack/test/functional_execution_context/tests/browser.ts +++ b/x-pack/test/functional_execution_context/tests/browser.ts @@ -12,7 +12,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home']); const retry = getService('retry'); - describe('Browser apps', () => { + // FLAKY: https://github.com/elastic/kibana/issues/125743 + describe.skip('Browser apps', () => { before(async () => { await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { useActualUrl: true, @@ -251,7 +252,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: (record) => Boolean( record.http?.request?.id?.includes( - 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:TSVB:bcb63b50-4c89-11e8-b3d7-01146121b73d' + 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:metrics:bcb63b50-4c89-11e8-b3d7-01146121b73d' ) ), retry, @@ -268,7 +269,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d', child: { type: 'visualization', - name: 'TSVB', + name: 'metrics', id: 'bcb63b50-4c89-11e8-b3d7-01146121b73d', description: '[Flights] Delays & Cancellations', url: '/app/visualize#/edit/bcb63b50-4c89-11e8-b3d7-01146121b73d', @@ -278,13 +279,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); + // application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:vega:ed78a660-53a0-11e8-acbd-0be0ad9d822b it('propagates context for Vega visualizations', async () => { await assertLogContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: (record) => Boolean( record.http?.request?.id?.includes( - 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Vega:ed78a660-53a0-11e8-acbd-0be0ad9d822b' + 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:vega:ed78a660-53a0-11e8-acbd-0be0ad9d822b' ) ), retry, @@ -301,7 +303,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d', child: { type: 'visualization', - name: 'Vega', + name: 'vega', id: 'ed78a660-53a0-11e8-acbd-0be0ad9d822b', description: '[Flights] Airport Connections (Hover Over Airport)', url: '/app/visualize#/edit/ed78a660-53a0-11e8-acbd-0be0ad9d822b', @@ -317,7 +319,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: (record) => Boolean( record.http?.request?.id?.includes( - 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Tag cloud:293b5a30-4c8f-11e8-b3d7-01146121b73d' + 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:tagcloud:293b5a30-4c8f-11e8-b3d7-01146121b73d' ) ), retry, @@ -334,7 +336,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d', child: { type: 'visualization', - name: 'Tag cloud', + name: 'tagcloud', id: '293b5a30-4c8f-11e8-b3d7-01146121b73d', description: '[Flights] Destination Weather', url: '/app/visualize#/edit/293b5a30-4c8f-11e8-b3d7-01146121b73d', @@ -350,7 +352,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: (record) => Boolean( record.http?.request?.id?.includes( - 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Vertical bar:9886b410-4c8b-11e8-b3d7-01146121b73d' + 'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:histogram:9886b410-4c8b-11e8-b3d7-01146121b73d' ) ), retry, @@ -367,7 +369,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d', child: { type: 'visualization', - name: 'Vertical bar', + name: 'histogram', id: '9886b410-4c8b-11e8-b3d7-01146121b73d', description: '[Flights] Delay Buckets', url: '/app/visualize#/edit/9886b410-4c8b-11e8-b3d7-01146121b73d', diff --git a/x-pack/test/functional_execution_context/tests/server.ts b/x-pack/test/functional_execution_context/tests/server.ts index fd10118a03627..adb92fc6a2283 100644 --- a/x-pack/test/functional_execution_context/tests/server.ts +++ b/x-pack/test/functional_execution_context/tests/server.ts @@ -73,7 +73,7 @@ export default function ({ getService }: FtrProviderContext) { Boolean( // exclude part with taskId record.http?.request?.id?.includes( - `kibana:task manager:run alerting:test.executionContext:` + `kibana:task%20manager:run%20alerting%3Atest.executionContext:` ) ), retry, @@ -84,7 +84,7 @@ export default function ({ getService }: FtrProviderContext) { 'alerting execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: (record) => Boolean( - record.http?.request?.id?.includes(`alert:execute test.executionContext:${alertId}`) + record.http?.request?.id?.includes(`alert:execute%20test.executionContext:${alertId}`) ), retry, }); diff --git a/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts b/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts index dd8afd4d414a6..1cf32b4abf040 100644 --- a/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts +++ b/x-pack/test/functional_synthetics/apps/uptime/synthetics_integration.ts @@ -131,7 +131,8 @@ export default function (providerContext: FtrProviderContext) { type: `synthetics/${monitorType}`, use_output: 'default', }); - describe('When on the Synthetics Integration Policy Create Page', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125881 + describe.skip('When on the Synthetics Integration Policy Create Page', function () { skipIfNoDockerRegistry(providerContext); const basicConfig = { name: monitorName, diff --git a/x-pack/test/functional_synthetics/config.js b/x-pack/test/functional_synthetics/config.js index 28cd7e3b099dc..f9074812a7e22 100644 --- a/x-pack/test/functional_synthetics/config.js +++ b/x-pack/test/functional_synthetics/config.js @@ -17,7 +17,7 @@ import { pageObjects } from './page_objects'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry that updates Synthetics. export const dockerImage = - 'docker.elastic.co/package-registry/distribution:48202133e7506873aff3cc7c3b1d284158727779'; + 'docker.elastic.co/package-registry/distribution@sha256:c5bf8e058727de72e561b228f4b254a14a6f880e582190d01bd5ff74318e1d0b'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/functional_synthetics/services/uptime/synthetics_package.ts b/x-pack/test/functional_synthetics/services/uptime/synthetics_package.ts index b0d935c408e4d..898d527245b16 100644 --- a/x-pack/test/functional_synthetics/services/uptime/synthetics_package.ts +++ b/x-pack/test/functional_synthetics/services/uptime/synthetics_package.ts @@ -50,6 +50,7 @@ export function SyntheticsPackageProvider({ getService }: FtrProviderContext) { apiRequest = retry.try(() => { return supertest .get(INGEST_API_EPM_PACKAGES) + .query({ experimental: true }) .set('kbn-xsrf', 'xxx') .expect(200) .catch((error) => { diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts b/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts index 842cfbcf7c1e1..68899e65528ed 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts @@ -35,7 +35,9 @@ export default function ({ getService }: FtrProviderContext) { // Retry the download URL until a "failed" response status is returned await retry.tryForTime(120000, async () => { const { body } = await supertest.get(downloadPath).expect(500); - expect(body.message).to.match(/Reporting generation failed: Error:/); + expect(body.message).to.match( + /Reporting generation failed: ReportingError\(code: unknown_error\) "/ + ); }); }); }); diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts index be3146f34c30e..4df3ff6b20649 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts @@ -39,7 +39,8 @@ const SPACE_ID = 'space1'; export default function registryRulesApiTest({ getService }: FtrProviderContext) { const es = getService('es'); - describe('Rule Registry API', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('Rule Registry API', () => { describe('with write permissions', () => { it('does not bootstrap indices on plugin startup', async () => { const { body: targetIndices } = await getAlertsTargetIndices(getService, obsOnly, SPACE_ID); diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts index 15ef6a7673d08..8fabaf9151d53 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts @@ -59,7 +59,8 @@ export default function createLifecycleExecutorApiTest({ getService }: FtrProvid return Promise.resolve(client); }; - describe('createLifecycleExecutor', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125851 + describe.skip('createLifecycleExecutor', () => { let ruleDataClient: IRuleDataClient; before(async () => { // First we need to setup the data service. This happens within the 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 72b64f78319ef..3f057d198a25c 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 @@ -493,7 +493,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { version: policyInfo.packageInfo.version, }, }, - artifact_manifest: agentFullPolicy.inputs[0].artifact_manifest, + artifact_manifest: agentFullPolicyUpdated.inputs[0].artifact_manifest, }), ]); }); diff --git a/x-pack/test/security_solution_endpoint/services/endpoint.ts b/x-pack/test/security_solution_endpoint/services/endpoint.ts index dbc2c165577c7..e2d3a77e46f61 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint.ts @@ -11,6 +11,7 @@ import { FtrService } from '../../functional/ftr_provider_context'; import { metadataCurrentIndexPattern, metadataTransformPrefix, + METADATA_UNITED_INDEX, } from '../../../plugins/security_solution/common/endpoint/constants'; import { deleteIndexedHostsAndAlerts, @@ -28,6 +29,7 @@ export class EndpointTestResources extends FtrService { private readonly retry = this.ctx.getService('retry'); private readonly kbnClient = this.ctx.getService('kibanaServer'); private readonly transform = this.ctx.getService('transform'); + private readonly config = this.ctx.getService('config'); private generateTransformId(endpointPackageVersion?: string): string { return `${metadataTransformPrefix}-${endpointPackageVersion ?? ''}`; @@ -137,13 +139,46 @@ export class EndpointTestResources extends FtrService { return deleteIndexedHostsAndAlerts(this.esClient as Client, this.kbnClient, indexedData); } + private async waitForIndex( + ids: string[], + index: string, + body: any = {}, + timeout: number = this.config.get('timeouts.waitFor') + ) { + // If we have a specific number of endpoint hosts to check for, then use that number, + // else we just want to make sure the index has data, thus just having one in the index will do + const size = ids.length || 1; + + await this.retry.waitForWithTimeout(`endpoint hosts in ${index}`, timeout, async () => { + try { + const searchResponse = await this.esClient.search({ + index, + size, + body, + rest_total_hits_as_int: true, + }); + + return searchResponse.hits.total === size; + } catch (error) { + // We ignore 404's (index might not exist) + if (error instanceof errors.ResponseError && error.statusCode === 404) { + return false; + } + + // Wrap the ES error so that we get a good stack trace + throw new EndpointError(error.message, error); + } + }); + } + /** * Waits for endpoints to show up on the `metadata-current` index. * Optionally, specific endpoint IDs (agent.id) can be provided to ensure those specific ones show up. * * @param [ids] optional list of ids to check for. If empty, it will just check if data exists in the index + * @param [timeout] optional max timeout to waitFor in ms. default is 20000. */ - async waitForEndpoints(ids: string[] = []) { + async waitForEndpoints(ids: string[] = [], timeout = this.config.get('timeouts.waitFor')) { const body = ids.length ? { query: { @@ -159,35 +194,49 @@ export class EndpointTestResources extends FtrService { }, } : { + size: 1, query: { match_all: {}, }, }; - // If we have a specific number of endpoint hosts to check for, then use that number, - // else we just want to make sure the index has data, thus just having one in the index will do - const size = ids.length || 1; - - await this.retry.waitFor('endpoint hosts', async () => { - try { - const searchResponse = await this.esClient.search({ - index: metadataCurrentIndexPattern, - size, - body, - rest_total_hits_as_int: true, - }); + await this.waitForIndex(ids, metadataCurrentIndexPattern, body, timeout); + } - return searchResponse.hits.total === size; - } catch (error) { - // We ignore 404's (index might not exist) - if (error instanceof errors.ResponseError && error.statusCode === 404) { - return false; + /** + * Waits for endpoints to show up on the `metadata_united` index. + * Optionally, specific endpoint IDs (agent.id) can be provided to ensure those specific ones show up. + * + * @param [ids] optional list of ids to check for. If empty, it will just check if data exists in the index + * @param [timeout] optional max timeout to waitFor in ms. default is 20000. + */ + async waitForUnitedEndpoints(ids: string[] = [], timeout = this.config.get('timeouts.waitFor')) { + const body = ids.length + ? { + query: { + bool: { + filter: [ + { + terms: { + 'agent.id': ids, + }, + }, + // make sure that both endpoint and agent portions are populated + // since agent is likely to be populated first + { exists: { field: 'united.endpoint.agent.id' } }, + { exists: { field: 'united.agent.agent.id' } }, + ], + }, + }, } + : { + size: 1, + query: { + match_all: {}, + }, + }; - // Wrap the ES error so that we get a good stack trace - throw new EndpointError(error.message, error); - } - }); + await this.waitForIndex(ids, METADATA_UNITED_INDEX, body, timeout); } /** diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index e6fd28d279fe7..86c8e32f3bdf9 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -35,8 +35,13 @@ import { TRANSFORM_STATES } from '../../../plugins/security_solution/common/cons export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); + const endpointTestResources = getService('endpointTestResources'); describe('test metadata apis', () => { + before(async () => { + await endpointTestResources.setMetadataTransformFrequency('1s'); + }); + describe('list endpoints GET route', () => { describe('with .metrics-endpoint.metadata_united_default index', () => { const numberOfHostsInFixture = 2; @@ -58,17 +63,22 @@ export default function ({ getService }: FtrProviderContext) { const policyId = policy.integrationPolicies[0].policy_id; const currentTime = new Date().getTime(); + const agentDocs = generateAgentDocs(currentTime, policyId); + await Promise.all([ - bulkIndex(getService, AGENTS_INDEX, generateAgentDocs(currentTime, policyId)), + bulkIndex(getService, AGENTS_INDEX, agentDocs), bulkIndex(getService, METADATA_DATASTREAM, generateMetadataDocs(currentTime)), ]); - // wait for latest metadata transform to run - await new Promise((r) => setTimeout(r, 60000)); + await endpointTestResources.waitForEndpoints( + agentDocs.map((doc) => doc.agent.id), + 60000 + ); await startTransform(getService, METADATA_UNITED_TRANSFORM); - - // wait for united metadata transform to run - await new Promise((r) => setTimeout(r, 30000)); + await endpointTestResources.waitForUnitedEndpoints( + agentDocs.map((doc) => doc.agent.id), + 60000 + ); }); after(async () => { @@ -295,9 +305,14 @@ export default function ({ getService }: FtrProviderContext) { // otherwise it won't hit metrics-endpoint.metadata_current_default index await stopTransform(getService, `${METADATA_UNITED_TRANSFORM}*`); await deleteIndex(getService, METADATA_UNITED_INDEX); - await bulkIndex(getService, METADATA_DATASTREAM, generateMetadataDocs(timestamp)); - // wait for transform - await new Promise((r) => setTimeout(r, 60000)); + + const metadataDocs = generateMetadataDocs(timestamp); + await bulkIndex(getService, METADATA_DATASTREAM, metadataDocs); + + await endpointTestResources.waitForEndpoints( + Array.from(new Set(metadataDocs.map((doc) => doc.agent.id))), + 60000 + ); }); // the endpoint uses data streams and es archiver does not support deleting them at the moment so we need // to do it manually diff --git a/yarn.lock b/yarn.lock index 252b2a8cc6775..694f7fd4e8647 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4643,15 +4643,6 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request-error@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" - integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== - dependencies: - "@octokit/types" "^5.0.1" - deprecation "^2.0.0" - once "^1.4.0" - "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" @@ -4673,21 +4664,7 @@ once "^1.4.0" universal-user-agent "^2.0.1" -"@octokit/request@^5.2.0", "@octokit/request@^5.3.0": - version "5.4.12" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.12.tgz#b04826fa934670c56b135a81447be2c1723a2ffc" - integrity sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - is-plain-object "^5.0.0" - node-fetch "^2.6.1" - once "^1.4.0" - universal-user-agent "^6.0.0" - -"@octokit/request@^5.6.0": +"@octokit/request@^5.2.0", "@octokit/request@^5.3.0", "@octokit/request@^5.6.0": version "5.6.2" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.2.tgz#1aa74d5da7b9e04ac60ef232edd9a7438dcf32d8" integrity sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA== @@ -4756,7 +4733,7 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^5.0.0", "@octokit/types@^5.0.1": +"@octokit/types@^5.0.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== @@ -6769,7 +6746,7 @@ "@types/parse5" "*" "@types/tough-cookie" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== @@ -7074,12 +7051,13 @@ resolved "https://registry.yarnpkg.com/@types/lz-string/-/lz-string-1.3.34.tgz#69bfadde419314b4a374bf2c8e58659c035ed0a5" integrity sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow== -"@types/markdown-it@^0.0.7": - version "0.0.7" - resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.7.tgz#75070485a3d8ad11e7deb8287f4430be15bf4d39" - integrity sha512-WyL6pa76ollQFQNEaLVa41ZUUvDvPY+qAUmlsphnrpL6I9p1m868b26FyeoOmo7X3/Ta/S9WKXcEYXUSHnxoVQ== +"@types/markdown-it@^12.2.3": + version "12.2.3" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" + integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== dependencies: "@types/linkify-it" "*" + "@types/mdurl" "*" "@types/markdown-to-jsx@^6.11.3": version "6.11.3" @@ -7102,6 +7080,11 @@ dependencies: "@types/unist" "*" +"@types/mdurl@*": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + "@types/micromatch@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7" @@ -7196,13 +7179,13 @@ dependencies: "@types/node" "*" -"@types/node-fetch@^2.5.7": - version "2.5.7" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" - integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== +"@types/node-fetch@^2.5.7", "@types/node-fetch@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.0.tgz#bf854a36a0d0d99436fd199e06612ef4e73591b6" + integrity sha512-HT+uU6V27wJFXgEqTk/+rVE1MWcp5bg7Yuz//43TZ2PjpQbQ8vDLwVmB+fSpgs83j/+p+rMIlDRo9TL3IexWMA== dependencies: "@types/node" "*" - form-data "^3.0.0" + form-data "^2.3.3" "@types/node-forge@^1.0.0": version "1.0.0" @@ -9178,7 +9161,7 @@ async@^2.1.4, async@^2.6.2: dependencies: lodash "^4.17.14" -async@^3.1.0, async@^3.2.0: +async@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== @@ -10159,29 +10142,7 @@ browserslist@4.14.2: escalade "^3.0.2" node-releases "^1.1.61" -browserslist@^4.0.0, browserslist@^4.12.0: - version "4.17.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9" - integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ== - dependencies: - caniuse-lite "^1.0.30001259" - electron-to-chromium "^1.3.846" - escalade "^3.1.1" - nanocolors "^0.1.5" - node-releases "^1.1.76" - -browserslist@^4.17.5, browserslist@^4.17.6: - version "4.18.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" - integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ== - dependencies: - caniuse-lite "^1.0.30001280" - electron-to-chromium "^1.3.896" - escalade "^3.1.1" - node-releases "^2.0.1" - picocolors "^1.0.0" - -browserslist@^4.19.1: +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.17.5, browserslist@^4.17.6, browserslist@^4.19.1: version "4.19.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== @@ -10524,15 +10485,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001097, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001259, caniuse-lite@^1.0.30001280: - version "1.0.30001280" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz#066a506046ba4be34cde5f74a08db7a396718fb7" - integrity sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA== - -caniuse-lite@^1.0.30001286: - version "1.0.30001303" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz#9b168e4f43ccfc372b86f4bc5a551d9b909c95c9" - integrity sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001097, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001286: + version "1.0.30001309" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001309.tgz#e0ee78b9bec0704f67304b00ff3c5c0c768a9f62" + integrity sha512-Pl8vfigmBXXq+/yUz1jUwULeq9xhMJznzdc/xwl4WclDAuebcTHVefpz8lE/bMI+UN7TOkSSe7B7RnZd6+dzjA== canvg@^3.0.9: version "3.0.9" @@ -10741,7 +10697,7 @@ cheerio@^1.0.0-rc.10, cheerio@^1.0.0-rc.3: parse5-htmlparser2-tree-adapter "^6.0.1" tslib "^2.2.0" -chokidar@3.4.3, chokidar@^2.0.0, chokidar@^2.0.4, chokidar@^2.1.2, chokidar@^2.1.8, chokidar@^3.2.2, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: +chokidar@3.4.3, chokidar@^2.0.0, chokidar@^2.0.4, chokidar@^2.1.2, chokidar@^2.1.8, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3, chokidar@^3.5.2: version "3.5.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== @@ -11223,12 +11179,7 @@ color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" -colorette@^1.2.0, colorette@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" - integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== - -colorette@^1.2.2: +colorette@^1.2.0, colorette@^1.2.1, colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -11238,7 +11189,7 @@ colors@1.0.3: resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= -colors@1.4.0, colors@^1.1.2, colors@^1.2.1, colors@^1.3.2: +colors@1.4.0, colors@^1.1.2, colors@^1.3.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -11394,7 +11345,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.2, concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: +concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -12722,17 +12673,17 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@3.X, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6, debug@^3.2.7: +debug@3.X, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -debug@4, debug@4.3.1, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@4, debug@4.3.3, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -12757,17 +12708,17 @@ debug@4.2.0: dependencies: ms "2.1.2" -debug@4.3.2, debug@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== +debug@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" -debug@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" @@ -13392,13 +13343,20 @@ domhandler@2.1: dependencies: domelementtype "1" -domhandler@^2.3.0, domhandler@^2.4.2: +domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== dependencies: domelementtype "1" +domhandler@^4.0, domhandler@^4.2.2: + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + dependencies: + domelementtype "^2.2.0" + domhandler@^4.0.0, domhandler@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" @@ -13429,7 +13387,7 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0: +domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -13690,25 +13648,10 @@ elasticsearch@^16.4.0: chalk "^1.0.0" lodash "^4.17.10" -electron-to-chromium@^1.3.564: - version "1.3.642" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz#8b884f50296c2ae2a9997f024d0e3e57facc2b94" - integrity sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ== - -electron-to-chromium@^1.3.846: - version "1.3.853" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82" - integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ== - -electron-to-chromium@^1.3.896: - version "1.3.899" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.899.tgz#4d7d040e73def3d5f5bd6b8a21049025dce6fce0" - integrity sha512-w16Dtd2zl7VZ4N4Db+FIa7n36sgPGCKjrKvUUmp5ialsikvcQLjcJR9RWnlYNxIyEHLdHaoIZEqKsPxU9MdyBg== - -electron-to-chromium@^1.4.17: - version "1.4.57" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.57.tgz#2b2766df76ac8dbc0a1d41249bc5684a31849892" - integrity sha512-FNC+P5K1n6pF+M0zIK+gFCoXcJhhzDViL3DRIGy2Fv5PohuSES1JHR7T+GlwxSxlzx4yYbsuzCZvHxcBSRCIOw== +electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.17: + version "1.4.66" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.66.tgz#d7453d363dcd7b06ed1757adcde34d724e27b367" + integrity sha512-f1RXFMsvwufWLwYUxTiP7HmjprKXrqEWHiQkjAYa9DJeVIlZk5v8gBGcaV+FhtXLly6C1OTVzQY+2UQrACiLlg== elegant-spinner@^1.0.1: version "1.0.1" @@ -13855,11 +13798,21 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + entities@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== +entities@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" @@ -14611,9 +14564,9 @@ events@^2.0.0: integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg== events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== eventsource@^1.0.7: version "1.0.7" @@ -14981,7 +14934,7 @@ fast-redact@^3.0.0: resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.0.0.tgz#ac2f9e36c9f4976f5db9fb18c6ffbaf308cf316d" integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== -fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0.7: +fast-safe-stringify@^2.0.7: version "2.0.8" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== @@ -15395,20 +15348,10 @@ follow-redirects@1.12.1: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6" integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== -follow-redirects@^1.0.0, follow-redirects@^1.10.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" - integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== - -follow-redirects@^1.14.4: - version "1.14.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd" - integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A== - -follow-redirects@^1.14.7: - version "1.14.8" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" - integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== +follow-redirects@^1.0.0, follow-redirects@^1.10.0, follow-redirects@^1.14.4, follow-redirects@^1.14.7: + version "1.14.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== font-awesome@4.7.0: version "4.7.0" @@ -15489,7 +15432,7 @@ fork-ts-checker-webpack-plugin@^6.0.4: semver "^7.3.2" tapable "^1.0.0" -form-data@^2.3.1: +form-data@^2.3.1, form-data@^2.3.3: version "2.5.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.0.tgz#094ec359dc4b55e7d62e0db4acd76e89fe874d37" integrity sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA== @@ -15498,15 +15441,6 @@ form-data@^2.3.1: combined-stream "^1.0.6" mime-types "^2.1.12" -form-data@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" - integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -15614,17 +15548,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - -fs-extra@^9.1.0: +fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -16005,9 +15929,9 @@ glob-to-regexp@^0.3.0: integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= glob-to-regexp@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.0.tgz#49bd677b1671022bd10921c3788f23cdebf9c7e6" - integrity sha512-fyPCII4vn9Gvjq2U/oDAfP433aiE64cyP/CJjRJcpVGjqqNdioUYn9+r0cSzT1XPwmGAHuTT7iv+rQT8u/YHKQ== + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== glob-watcher@5.0.3, glob-watcher@^5.0.3: version "5.0.3" @@ -16021,7 +15945,7 @@ glob-watcher@5.0.3, glob-watcher@^5.0.3: just-debounce "^1.0.0" object.defaults "^1.1.0" -glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.4: +glob@7.1.6, glob@~7.1.1, glob@~7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -16044,6 +15968,18 @@ glob@^6.0.1, glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" @@ -16940,15 +16876,14 @@ html-tags@^3.1.0: integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== html-to-react@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.3.4.tgz#647b3a54fdec73a6461864b129fb0d1eec7d4589" - integrity sha512-/tWDdb/8Koi/QEP5YUY1653PcDpBnnMblXRhotnTuhFDjI1Fc6Wzox5d4sw73Xk5rM2OdM5np4AYjT/US/Wj7Q== + version "1.4.8" + resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.4.8.tgz#1a78fe0ad50fe30b7e62f4e90fdff2f2c043eb1a" + integrity sha512-BDOPUYTej5eiOco0V0wxJ5FS+Zckp2O0kb13X1SxQFzyusIeUmnxAK0Cl5M6692bmGBs1WBjh9qF3oQ2AJUZmg== dependencies: - domhandler "^2.4.2" - escape-string-regexp "^1.0.5" - htmlparser2 "^3.10.0" + domhandler "^4.0" + htmlparser2 "^7.0" lodash.camelcase "^4.3.0" - ramda "^0.26" + ramda "^0.28.0" html-void-elements@^1.0.0: version "1.0.5" @@ -17004,6 +16939,16 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +htmlparser2@^7.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5" + integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.2" + domutils "^2.8.0" + entities "^3.0.1" + htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -17181,14 +17126,7 @@ iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" - integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -iconv-lite@^0.6.3: +iconv-lite@^0.6.2, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -19053,9 +18991,9 @@ jpeg-js@^0.4.2: integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== jquery@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9" - integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ== + version "3.6.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" + integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== js-base64@^2.1.8: version "2.4.5" @@ -19718,13 +19656,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -linkify-it@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" - integrity sha1-2UpGSPmxwXnWT6lykSaL22zpQ08= - dependencies: - uc.micro "^1.0.1" - linkify-it@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" @@ -20137,17 +20068,6 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -logform@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" - integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== - dependencies: - colors "^1.2.1" - fast-safe-stringify "^2.0.4" - fecha "^4.2.0" - ms "^2.1.1" - triple-beam "^1.3.0" - logform@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/logform/-/logform-2.3.2.tgz#68babe6a74ab09a1fd15a9b1e6cbc7713d41cb5b" @@ -20417,17 +20337,6 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.1.tgz#1994df2d3af4811de59a6714934c2b2292734518" integrity sha1-GZTfLTr0gR3lmmcUk0wrIpJzRRg= -markdown-it@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" - integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== - dependencies: - argparse "^1.0.7" - entities "~2.0.0" - linkify-it "^2.0.0" - mdurl "^1.0.1" - uc.micro "^1.0.5" - markdown-it@^11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-11.0.1.tgz#b54f15ec2a2193efa66dda1eb4173baea08993d6" @@ -20439,6 +20348,17 @@ markdown-it@^11.0.0: mdurl "^1.0.1" uc.micro "^1.0.5" +markdown-it@^12.3.2: + version "12.3.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" + integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== + dependencies: + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + markdown-table@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" @@ -21384,11 +21304,6 @@ nano-time@1.0.0: dependencies: big-integer "^1.6.16" -nanocolors@^0.1.5: - version "0.1.12" - resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6" - integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ== - nanoid@3.1.12: version "3.1.12" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" @@ -21577,10 +21492,12 @@ node-emoji@^1.10.0: dependencies: lodash.toarray "^4.4.0" -node-fetch@2.6.1, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.1, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" node-forge@^0.10.0, node-forge@^1.2.1: version "1.2.1" @@ -21702,11 +21619,6 @@ node-releases@^1.1.61: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.61.tgz#707b0fca9ce4e11783612ba4a2fcba09047af16e" integrity sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g== -node-releases@^1.1.76: - version "1.1.76" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e" - integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA== - node-releases@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" @@ -21739,20 +21651,20 @@ nodemailer@^6.6.2: integrity sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q== nodemon@^2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.6.tgz#1abe1937b463aaf62f0d52e2b7eaadf28cc2240d" - integrity sha512-4I3YDSKXg6ltYpcnZeHompqac4E6JeAMpGm8tJnB9Y3T0ehasLa4139dJOcCrB93HHrUMsCrKtoAlXTqT5n4AQ== + version "2.0.15" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.15.tgz#504516ce3b43d9dc9a955ccd9ec57550a31a8d4e" + integrity sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA== dependencies: - chokidar "^3.2.2" - debug "^3.2.6" + chokidar "^3.5.2" + debug "^3.2.7" ignore-by-default "^1.0.1" minimatch "^3.0.4" - pstree.remy "^1.1.7" + pstree.remy "^1.1.8" semver "^5.7.1" supports-color "^5.5.0" touch "^3.1.0" - undefsafe "^2.0.3" - update-notifier "^4.1.0" + undefsafe "^2.0.5" + update-notifier "^5.1.0" nopt@^2.2.0: version "2.2.1" @@ -22177,9 +22089,9 @@ onetime@^2.0.0: mimic-fn "^1.0.0" onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" @@ -24022,10 +23934,10 @@ psl@^1.1.28, psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== -pstree.remy@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" - integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== public-encrypt@^4.0.0: version "4.0.0" @@ -24247,16 +24159,16 @@ ramda@^0.21.0: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU= -ramda@^0.26: - version "0.26.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" - integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== - ramda@^0.27.1: version "0.27.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== +ramda@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.28.0.tgz#acd785690100337e8b063cab3470019be427cc97" + integrity sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA== + randexp@0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" @@ -25162,7 +25074,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -"readable-stream@1 || 2", "readable-stream@2 || 3", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.7, readable-stream@~2.3.3, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.3, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -25185,7 +25097,7 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0 isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -26343,11 +26255,11 @@ schema-utils@^1.0.0: ajv-keywords "^3.1.0" schema-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" - integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== dependencies: - "@types/json-schema" "^7.0.6" + "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" @@ -26454,12 +26366,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semve resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@~7.3.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== - -semver@^7.3.4, semver@^7.3.5, semver@~7.3.0: +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0, semver@~7.3.2: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -26731,9 +26638,9 @@ sigmund@^1.0.1: integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== simple-concat@^1.0.0: version "1.0.1" @@ -27020,18 +26927,10 @@ source-map-support@^0.3.2: dependencies: source-map "0.1.32" -source-map-support@^0.5.16, source-map-support@^0.5.19, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@^0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== +source-map-support@^0.5.16, source-map-support@^0.5.19, source-map-support@^0.5.20, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -28391,13 +28290,13 @@ terser@^4.1.2, terser@^4.6.3: source-map-support "~0.5.12" terser@^5.3.4, terser@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784" - integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg== + version "5.10.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" + integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== dependencies: commander "^2.20.0" source-map "~0.7.2" - source-map-support "~0.5.19" + source-map-support "~0.5.20" test-exclude@^6.0.0: version "6.0.0" @@ -28778,6 +28677,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + traceparent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/traceparent/-/traceparent-1.0.0.tgz#9b14445cdfe5c19f023f1c04d249c3d8e003a5ce" @@ -28815,7 +28719,7 @@ trim@0.0.1, trim@1.0.1: resolved "https://registry.yarnpkg.com/trim/-/trim-1.0.1.tgz#68e78f6178ccab9687a610752f4f5e5a7022ee8c" integrity sha512-3JVP2YVqITUisXblCDq/Bi4P9457G/sdEamInkyvCsjbTcXLXIiG7XCb4kGMFWh6JGXesS3TKxOPtrncN/xe8w== -triple-beam@^1.2.0, triple-beam@^1.3.0: +triple-beam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== @@ -29184,12 +29088,10 @@ undeclared-identifiers@^1.1.2: simple-concat "^1.0.0" xtend "^4.0.1" -undefsafe@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" - integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== - dependencies: - debug "^2.2.0" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== underscore@^1.13.1, underscore@^1.8.3: version "1.13.1" @@ -29655,9 +29557,9 @@ url-parse-lax@^3.0.0: prepend-http "^2.0.0" url-parse@^1.4.3, url-parse@^1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== + version "1.5.9" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.9.tgz#05ff26484a0b5e4040ac64dcee4177223d74675e" + integrity sha512-HpOvhKBvre8wYez+QhHcYiVvVmeF6DVnuSOOPhe3cTum3BnqHhvKaZm8FU5yTiOu/Jut2ZpB2rA/SbBA1JIGlQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" @@ -30534,6 +30436,11 @@ web-streams-polyfill@^3.0.0: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.0.1.tgz#1f836eea307e8f4af15758ee473c7af755eb879e" integrity sha512-M+EmTdszMWINywOZaqpZ6VIEDUmNpRaTOuizF0ZKPjSDC8paMRe/jBBwFv0Yeyn5WYnM5pMqMQa82vpaE+IJRw== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -30739,6 +30646,14 @@ whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" @@ -30868,14 +30783,6 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" -winston-transport@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" - integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== - dependencies: - readable-stream "^2.3.7" - triple-beam "^1.2.0" - winston-transport@^4.4.2: version "4.5.0" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" @@ -30885,22 +30792,7 @@ winston-transport@^4.4.2: readable-stream "^3.6.0" triple-beam "^1.3.0" -winston@^3.0.0, winston@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" - integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== - dependencies: - "@dabh/diagnostics" "^2.0.2" - async "^3.1.0" - is-stream "^2.0.0" - logform "^2.2.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.4.0" - -winston@^3.5.1: +winston@^3.0.0, winston@^3.3.3, winston@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/winston/-/winston-3.5.1.tgz#b25cc899d015836dbf8c583dec8c4c4483a0da2e" integrity sha512-tbRtVy+vsSSCLcZq/8nXZaOie/S2tPXPFt4be/Q3vI/WtYwm7rrwidxVw2GRa38FIXcJ1kUM6MOZ9Jmnk3F3UA== @@ -31301,20 +31193,7 @@ yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.0.1: - version "17.1.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" - integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.3.1: +yargs@^17.0.1, yargs@^17.3.1: version "17.3.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==