diff --git a/.backportrc.json b/.backportrc.json index 24e08d4768d9af..f89e758afe8a60 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -1,7 +1,7 @@ { "upstream": "elastic/kibana", "targetBranchChoices": [ - { "name": "master", "checked": true }, + { "name": "main", "checked": true }, "8.0", "7.16", "7.15", @@ -33,7 +33,7 @@ ], "targetPRLabels": ["backport"], "branchLabelMapping": { - "^v8.1.0$": "master", + "^v8.1.0$": "main", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, diff --git a/.bazelrc.common b/.bazelrc.common index c401a905079822..0ad0c95fdcbbdf 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -13,10 +13,10 @@ test --experimental_guard_against_concurrent_changes query --experimental_guard_against_concurrent_changes ## Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) -common --disk_cache=~/.bazel-cache/disk-cache +build --disk_cache=~/.bazel-cache/disk-cache ## Bazel repo cache settings -common --repository_cache=~/.bazel-cache/repository-cache +build --repository_cache=~/.bazel-cache/repository-cache # Bazel will create symlinks from the workspace directory to output artifacts. # Build results will be placed in a directory called "bazel-bin" diff --git a/.buildkite/pipelines/hourly.yml b/.buildkite/pipelines/hourly.yml index b03a46b5b5c667..4b2b17d272d17e 100644 --- a/.buildkite/pipelines/hourly.yml +++ b/.buildkite/pipelines/hourly.yml @@ -147,6 +147,13 @@ steps: key: linting timeout_in_minutes: 90 + - command: .buildkite/scripts/steps/lint_with_types.sh + label: 'Linting (with types)' + agents: + queue: c2-16 + key: linting_with_types + timeout_in_minutes: 90 + - command: .buildkite/scripts/steps/checks.sh label: 'Checks' agents: diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 2aba49bfa64606..efe522f592ecd6 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -10,8 +10,6 @@ steps: - command: .buildkite/scripts/steps/on_merge_build_and_metrics.sh label: Default Build and Metrics - env: - BAZEL_CACHE_MODE: read-write agents: queue: c2-8 timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/performance/nightly.yml b/.buildkite/pipelines/performance/nightly.yml index aa52fb7a9335c1..dfee1061815c37 100644 --- a/.buildkite/pipelines/performance/nightly.yml +++ b/.buildkite/pipelines/performance/nightly.yml @@ -24,8 +24,6 @@ steps: agents: queue: ci-group-6 depends_on: build - concurrency: 50 - concurrency_group: 'performance-test-group' - wait: ~ continue_on_failure: true diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index 1013a841dfd271..0f2a4a1026af87 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -145,6 +145,13 @@ steps: key: linting timeout_in_minutes: 90 + - command: .buildkite/scripts/steps/lint_with_types.sh + label: 'Linting (with types)' + agents: + queue: c2-16 + key: linting_with_types + timeout_in_minutes: 90 + - command: .buildkite/scripts/steps/checks.sh label: 'Checks' agents: diff --git a/.buildkite/pipelines/pull_request/fleet_cypress.yml b/.buildkite/pipelines/pull_request/fleet_cypress.yml new file mode 100644 index 00000000000000..bfaa3faae7783b --- /dev/null +++ b/.buildkite/pipelines/pull_request/fleet_cypress.yml @@ -0,0 +1,11 @@ +steps: + - command: .buildkite/scripts/steps/functional/fleet_cypress.sh + label: 'Fleet Cypress Tests' + agents: + queue: ci-group-6 + depends_on: build + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '*' + limit: 1 diff --git a/.buildkite/scripts/bootstrap.sh b/.buildkite/scripts/bootstrap.sh index df38c105d2fd30..272cd0a0861702 100755 --- a/.buildkite/scripts/bootstrap.sh +++ b/.buildkite/scripts/bootstrap.sh @@ -6,7 +6,17 @@ source .buildkite/scripts/common/util.sh source .buildkite/scripts/common/setup_bazel.sh echo "--- yarn install and bootstrap" -retry 2 15 yarn kbn bootstrap +if ! yarn kbn bootstrap; then + echo "bootstrap failed, trying again in 15 seconds" + sleep 15 + + # Most bootstrap failures will result in a problem inside node_modules that does not get fixed on the next bootstrap + # So, we should just delete node_modules in between attempts + rm -rf node_modules + + echo "--- yarn install and bootstrap, attempt 2" + yarn kbn bootstrap +fi ### ### upload ts-refs-cache artifacts as quickly as possible so they are available for download diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.js b/.buildkite/scripts/pipelines/pull_request/pipeline.js index c5ed216042b680..d0f38dc773357e 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.js +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.js @@ -76,6 +76,16 @@ const uploadPipeline = (pipelineContent) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/apm_cypress.yml')); } + if ( + (await doAnyChangesMatch([ + /^x-pack\/plugins\/fleet/, + /^x-pack\/test\/fleet_cypress/, + ])) || + process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + ) { + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/fleet_cypress.yml')); + } + if (await doAnyChangesMatch([/^x-pack\/plugins\/uptime/])) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/uptime.yml')); } diff --git a/.buildkite/scripts/steps/es_snapshots/build.sh b/.buildkite/scripts/steps/es_snapshots/build.sh index 975d5944da8839..c11f0418364139 100755 --- a/.buildkite/scripts/steps/es_snapshots/build.sh +++ b/.buildkite/scripts/steps/es_snapshots/build.sh @@ -14,6 +14,10 @@ mkdir -p "$destination" mkdir -p elasticsearch && cd elasticsearch export ELASTICSEARCH_BRANCH="${ELASTICSEARCH_BRANCH:-$BUILDKITE_BRANCH}" +# Until ES renames their master branch to main... +if [[ "$ELASTICSEARCH_BRANCH" == "main" ]]; then + export ELASTICSEARCH_BRANCH="master" +fi if [[ ! -d .git ]]; then git init diff --git a/.buildkite/scripts/steps/functional/fleet_cypress.sh b/.buildkite/scripts/steps/functional/fleet_cypress.sh new file mode 100755 index 00000000000000..3847ffda088229 --- /dev/null +++ b/.buildkite/scripts/steps/functional/fleet_cypress.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +.buildkite/scripts/bootstrap.sh +.buildkite/scripts/download_build_artifacts.sh + +export JOB=kibana-fleet-cypress + +echo "--- Fleet Cypress tests" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "Fleet Cypress Tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --config test/fleet_cypress/cli_config.ts diff --git a/.buildkite/scripts/steps/functional/performance.sh b/.buildkite/scripts/steps/functional/performance.sh index 2f1a77690d1538..8e3793733a6e86 100644 --- a/.buildkite/scripts/steps/functional/performance.sh +++ b/.buildkite/scripts/steps/functional/performance.sh @@ -14,6 +14,8 @@ cat << EOF | buildkite-agent pipeline upload steps: - command: .buildkite/scripts/steps/functional/performance_sub.sh parallelism: "$ITERATION_COUNT" + concurrency: 20 + concurrency_group: 'performance-test-group' EOF diff --git a/.buildkite/scripts/steps/lint_with_types.sh b/.buildkite/scripts/steps/lint_with_types.sh new file mode 100755 index 00000000000000..81d5ef03f4989d --- /dev/null +++ b/.buildkite/scripts/steps/lint_with_types.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +export BUILD_TS_REFS_DISABLE=false +.buildkite/scripts/bootstrap.sh + +echo '--- Lint: eslint (with types)' +checks-reporter-with-killswitch "Lint: eslint (with types)" \ + node scripts/eslint_with_types diff --git a/.ci/Dockerfile b/.ci/Dockerfile index 29ed08c84b23ec..8e0d2d4351965e 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -1,7 +1,7 @@ # NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable. # If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts -ARG NODE_VERSION=16.11.1 +ARG NODE_VERSION=16.13.0 FROM node:${NODE_VERSION} AS base diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index 723008d8618d78..fc5a52f1ac9919 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -7,5 +7,5 @@ if [[ "$(which docker)" != "" && "$(command uname -m)" != "aarch64" ]]; then docker pull "maven:3.6.3-openjdk-8-slim" fi -./.ci/packer_cache_for_branch.sh master +./.ci/packer_cache_for_branch.sh main ./.ci/packer_cache_for_branch.sh 7.16 diff --git a/.ci/packer_cache_for_branch.sh b/.ci/packer_cache_for_branch.sh index ee220537de340a..78548f4667a9f0 100755 --- a/.ci/packer_cache_for_branch.sh +++ b/.ci/packer_cache_for_branch.sh @@ -7,7 +7,7 @@ checkoutDir="$(pwd)" function cleanup() { - if [[ "$branch" != "master" ]]; then + if [[ "$branch" != "main" ]]; then rm --preserve-root -rf "$checkoutDir" fi @@ -16,7 +16,7 @@ function cleanup() trap 'cleanup' 0 -if [[ "$branch" != "master" ]]; then +if [[ "$branch" != "main" ]]; then checkoutDir="/tmp/kibana-$branch" git clone https://github.com/elastic/kibana.git --branch "$branch" --depth 1 "$checkoutDir" cd "$checkoutDir" diff --git a/.eslintrc.js b/.eslintrc.js index 60f3ae1528fbcc..00c96e5cf0491f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -902,17 +902,6 @@ module.exports = { }, }, - /** - * Cases overrides - */ - { - files: ['x-pack/plugins/cases/**/*.{js,mjs,ts,tsx}'], - rules: { - 'no-duplicate-imports': 'off', - '@typescript-eslint/no-duplicate-imports': ['error'], - }, - }, - /** * Security Solution overrides. These rules below are maintained and owned by * the people within the security-solution-platform team. Please see ping them @@ -928,6 +917,8 @@ module.exports = { 'x-pack/plugins/security_solution/common/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/timelines/public/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/timelines/common/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/cases/public/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/cases/common/**/*.{js,mjs,ts,tsx}', ], rules: { 'import/no-nodejs-modules': 'error', @@ -949,10 +940,12 @@ module.exports = { files: [ 'x-pack/plugins/security_solution/**/*.{ts,tsx}', 'x-pack/plugins/timelines/**/*.{ts,tsx}', + 'x-pack/plugins/cases/**/*.{ts,tsx}', ], excludedFiles: [ 'x-pack/plugins/security_solution/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/plugins/timelines/**/*.{test,mock,test_helper}.{ts,tsx}', + 'x-pack/plugins/cases/**/*.{test,mock,test_helper}.{ts,tsx}', ], rules: { '@typescript-eslint/no-non-null-assertion': 'error', @@ -963,6 +956,7 @@ module.exports = { files: [ 'x-pack/plugins/security_solution/**/*.{ts,tsx}', 'x-pack/plugins/timelines/**/*.{ts,tsx}', + 'x-pack/plugins/cases/**/*.{ts,tsx}', ], rules: { '@typescript-eslint/no-this-alias': 'error', @@ -985,6 +979,7 @@ module.exports = { files: [ 'x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/timelines/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/cases/**/*.{js,mjs,ts,tsx}', ], plugins: ['eslint-plugin-node', 'react'], env: { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 227041522ac782..e807885e17294f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -232,6 +232,7 @@ /src/core/ @elastic/kibana-core /src/plugins/saved_objects_tagging_oss @elastic/kibana-core /config/kibana.yml @elastic/kibana-core +/typings/ @elastic/kibana-core /x-pack/plugins/banners/ @elastic/kibana-core /x-pack/plugins/features/ @elastic/kibana-core /x-pack/plugins/licensing/ @elastic/kibana-core diff --git a/.github/ISSUE_TEMPLATE/v8_breaking_change.md b/.github/ISSUE_TEMPLATE/v8_breaking_change.md index 9361cf7a52b654..8ef570947dd417 100644 --- a/.github/ISSUE_TEMPLATE/v8_breaking_change.md +++ b/.github/ISSUE_TEMPLATE/v8_breaking_change.md @@ -37,7 +37,7 @@ changes to Upgrade Assistant itself, please tag Team:Elasticsearch UI. **Are there any edge cases?** -**[For Kibana deprecations] Can the change be registered with the [Kibana deprecation service](https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md)?** +**[For Kibana deprecations] Can the change be registered with the [Kibana deprecation service](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md)?** | [Plugin](./kibana-plugin-core-public.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginInitializerContext](./kibana-plugin-core-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | | [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) | This interface is a very simple wrapper for SavedObjects resolved from the server with the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md). | +| [ResponseErrorBody](./kibana-plugin-core-public.responseerrorbody.md) | | | [SavedObject](./kibana-plugin-core-public.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-core-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectError](./kibana-plugin-core-public.savedobjecterror.md) | | diff --git a/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.attributes.md b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.attributes.md new file mode 100644 index 00000000000000..bf64b25c04f924 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.attributes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResponseErrorBody](./kibana-plugin-core-public.responseerrorbody.md) > [attributes](./kibana-plugin-core-public.responseerrorbody.attributes.md) + +## ResponseErrorBody.attributes property + +Signature: + +```typescript +attributes?: Record; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.md b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.md new file mode 100644 index 00000000000000..8a990909fac3ec --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResponseErrorBody](./kibana-plugin-core-public.responseerrorbody.md) + +## ResponseErrorBody interface + + +Signature: + +```typescript +export interface ResponseErrorBody +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [attributes](./kibana-plugin-core-public.responseerrorbody.attributes.md) | Record<string, unknown> | | +| [message](./kibana-plugin-core-public.responseerrorbody.message.md) | string | | +| [statusCode](./kibana-plugin-core-public.responseerrorbody.statuscode.md) | number | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.message.md b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.message.md new file mode 100644 index 00000000000000..a3355ddafde1ee --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResponseErrorBody](./kibana-plugin-core-public.responseerrorbody.md) > [message](./kibana-plugin-core-public.responseerrorbody.message.md) + +## ResponseErrorBody.message property + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.statuscode.md b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.statuscode.md new file mode 100644 index 00000000000000..a342bb0187d72f --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.responseerrorbody.statuscode.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResponseErrorBody](./kibana-plugin-core-public.responseerrorbody.md) > [statusCode](./kibana-plugin-core-public.responseerrorbody.statuscode.md) + +## ResponseErrorBody.statusCode property + +Signature: + +```typescript +statusCode: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md index 59d98bf4d607b5..007b453817c8dd 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md @@ -31,7 +31,7 @@ The saved object after the specified fields were incremented When supplying a field name like `stats.api.counter` the field name will be used as-is to create a document like: `{attributes: {'stats.api.counter': 1}}` It will not create a nested structure like: `{attributes: {stats: {api: {counter: 1}}}}` -When using incrementCounter for collecting usage data, you need to ensure that usage collection happens on a best-effort basis and doesn't negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/master/src/plugins/usage\_collection/README.mdx\#tracking-interactions-with-incrementcounter) +When using incrementCounter for collecting usage data, you need to ensure that usage collection happens on a best-effort basis and doesn't negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/main/src/plugins/usage\_collection/README.mdx\#tracking-interactions-with-incrementcounter) ## Example diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 0306be3eb670d7..4f4f8f5b48d102 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -3,7 +3,7 @@ You can search your data in any app that has a query bar, or by clicking on elements in a visualization. A search matches indices in the current -<> and in the current <>. +<> and in the current <>. [float] diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index f2da704760f3b3..56b7eb09252ed7 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -189,7 +189,7 @@ Set to `true` to enable a dark mode for the {kib} UI. You must refresh the page to apply the setting. [[theme-version]]`theme:version`:: -Specifies the {kib} theme. If you change the setting, refresh the page to apply the setting. +Kibana only ships with the v8 theme now, so this setting can no longer be edited. [[timepicker-quickranges]]`timepicker:quickRanges`:: The list of ranges to show in the Quick section of the time filter. This should diff --git a/docs/management/connectors/action-types/servicenow-sir.asciidoc b/docs/management/connectors/action-types/servicenow-sir.asciidoc index b924837c97bc3b..8847a99fe3af03 100644 --- a/docs/management/connectors/action-types/servicenow-sir.asciidoc +++ b/docs/management/connectors/action-types/servicenow-sir.asciidoc @@ -37,7 +37,7 @@ Use the <> to customize connecto actionTypeId: .servicenow-sir config: apiUrl: https://example.service-now.com/ - isLegacy: false + usesTableApi: false secrets: username: testuser password: passwordkeystorevalue @@ -46,9 +46,9 @@ Use the <> to customize connecto Config defines information for the connector type. `apiUrl`:: An address that corresponds to *URL*. -`isLegacy`:: A boolean that indicates if the connector should use the Table API (legacy) or the Import Set API. +`usesTableApi`:: A boolean that indicates if the connector uses the Table API or the Import Set API. -Note: If `isLegacy` is set to false the Elastic application should be installed in ServiceNow. +Note: If `usesTableApi` is set to false the Elastic application should be installed in ServiceNow. Secrets defines sensitive information for the connector type. diff --git a/docs/management/connectors/action-types/servicenow.asciidoc b/docs/management/connectors/action-types/servicenow.asciidoc index 73da93e57dae90..bfa8c7db657d03 100644 --- a/docs/management/connectors/action-types/servicenow.asciidoc +++ b/docs/management/connectors/action-types/servicenow.asciidoc @@ -37,7 +37,7 @@ Use the <> to customize connecto actionTypeId: .servicenow config: apiUrl: https://example.service-now.com/ - isLegacy: false + usesTableApi: false secrets: username: testuser password: passwordkeystorevalue @@ -46,9 +46,9 @@ Use the <> to customize connecto Config defines information for the connector type. `apiUrl`:: An address that corresponds to *URL*. -`isLegacy`:: A boolean that indicates if the connector should use the Table API (legacy) or the Import Set API. +`usesTableApi`:: A boolean that indicates if the connector uses the Table API or the Import Set API. -Note: If `isLegacy` is set to false the Elastic application should be installed in ServiceNow. +Note: If `usesTableApi` is set to false the Elastic application should be installed in ServiceNow. Secrets defines sensitive information for the connector type. diff --git a/docs/maps/asset-tracking-tutorial.asciidoc b/docs/maps/asset-tracking-tutorial.asciidoc index 4ba045681e1486..ff62f5c019b74f 100644 --- a/docs/maps/asset-tracking-tutorial.asciidoc +++ b/docs/maps/asset-tracking-tutorial.asciidoc @@ -156,16 +156,16 @@ image::maps/images/asset-tracking-tutorial/logstash_output.png[] . Leave the terminal window open and Logstash running throughout this tutorial. [float] -==== Step 3: Create a {kib} index pattern for the tri_met_tracks {es} index +==== Step 3: Create a data view for the tri_met_tracks {es} index -. In Kibana, open the main menu, and click *Stack Management > Index Patterns*. -. Click *Create index pattern*. -. Give the index pattern a name: *tri_met_tracks**. +. In {kib}, open the main menu, and click *Stack Management > Data Views*. +. Click *Create data view*. +. Give the data view a name: *tri_met_tracks**. . Click *Next step*. . Set the *Time field* to *time*. -. Click *Create index pattern*. +. Click *Create data view*. -{kib} shows the fields in your index pattern. +{kib} shows the fields in your data view. [role="screenshot"] image::maps/images/asset-tracking-tutorial/index_pattern.png[] @@ -174,7 +174,7 @@ image::maps/images/asset-tracking-tutorial/index_pattern.png[] ==== Step 4: Explore the Portland bus data . Open the main menu, and click *Discover*. -. Set the index pattern to *tri_met_tracks**. +. Set the data view to *tri_met_tracks**. . Open the <>, and set the time range to the last 15 minutes. . Expand a document and explore some of the fields that you will use later in this tutorial: `bearing`, `in_congestion`, `location`, and `vehicle_id`. @@ -202,7 +202,7 @@ Add a layer to show the bus routes for the last 15 minutes. . Click *Add layer*. . Click *Tracks*. -. Select the *tri_met_tracks** index pattern. +. Select the *tri_met_tracks** data view. . Define the tracks: .. Set *Entity* to *vehicle_id*. .. Set *Sort* to *time*. @@ -225,7 +225,7 @@ image::maps/images/asset-tracking-tutorial/tracks_layer.png[] Add a layer that uses attributes in the data to set the style and orientation of the buses. You’ll see the direction buses are headed and what traffic is like. . Click *Add layer*, and then select *Top Hits per entity*. -. Select the *tri_met_tracks** index pattern. +. Select the *tri_met_tracks** data view. . To display the most recent location per bus: .. Set *Entity* to *vehicle_id*. .. Set *Documents per entity* to 1. diff --git a/docs/maps/geojson-upload.asciidoc b/docs/maps/geojson-upload.asciidoc index 3c9bea11176cc8..15ef3471e58d7d 100644 --- a/docs/maps/geojson-upload.asciidoc +++ b/docs/maps/geojson-upload.asciidoc @@ -30,11 +30,11 @@ a preview of the data on the map. . Use the default *Index type* of {ref}/geo-point.html[geo_point] for point data, or override it and select {ref}/geo-shape.html[geo_shape]. All other shapes will default to a type of `geo_shape`. -. Leave the default *Index name* and *Index pattern* names (the name of the uploaded +. Leave the default *Index name* and *Data view* names (the name of the uploaded file minus its extension). You might need to change the index name if it is invalid. . Click *Import file*. + -Upon completing the indexing process and creating the associated index pattern, +Upon completing the indexing process and creating the associated data view, the Elasticsearch responses are shown on the *Layer add panel* and the indexed data appears on the map. The geospatial data on the map should be identical to the locally-previewed data, but now it's indexed data from Elasticsearch. diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index 434c9ab369a5bd..50f2e9aed9248d 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -58,8 +58,8 @@ auto-populate *Index type* with either {ref}/geo-point.html[geo_point] or . Click *Import file*. + You'll see activity as the GeoJSON Upload utility creates a new index -and index pattern for the data set. When the process is complete, you should -receive messages that the creation of the new index and index pattern +and data view for the data set. When the process is complete, you should +receive messages that the creation of the new index and data view were successful. . Click *Add layer*. diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index 7f4af952653e7c..fced15771c3864 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -62,7 +62,7 @@ To enable a grid aggregation layer: To enable a blended layer that dynamically shows clusters or documents: . Click *Add layer*, then select the *Documents* layer. -. Configure *Index pattern* and the *Geospatial field*. +. Configure *Data view* and the *Geospatial field*. . In *Scaling*, select *Show clusters when results exceed 10000*. @@ -77,7 +77,7 @@ then accumulates the most relevant documents based on sort order for each entry To enable top hits: . Click *Add layer*, then select the *Top hits per entity* layer. -. Configure *Index pattern* and *Geospatial field*. +. Configure *Data view* and *Geospatial field*. . Set *Entity* to the field that identifies entities in your documents. This field will be used in the terms aggregation to group your documents into entity buckets. . Set *Documents per entity* to configure the maximum number of documents accumulated per entity. diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index 014be570253bb7..89d06fce60183d 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -49,7 +49,7 @@ and lighter shades will symbolize countries with less traffic. . From the **Layer** dropdown menu, select **World Countries**. . In **Statistics source**, set: -** **Index pattern** to **kibana_sample_data_logs** +** **Data view** to **kibana_sample_data_logs** ** **Join field** to **geo.dest** . Click **Add layer**. @@ -95,7 +95,7 @@ The layer is only visible when users zoom in. . Click **Add layer**, and then click **Documents**. -. Set **Index pattern** to **kibana_sample_data_logs**. +. Set **Data view** to **kibana_sample_data_logs**. . Set **Scaling** to *Limits results to 10000.* @@ -129,7 +129,7 @@ more total bytes transferred, and smaller circles will symbolize grids with less bytes transferred. . Click **Add layer**, and select **Clusters and grids**. -. Set **Index pattern** to **kibana_sample_data_logs**. +. Set **Data view** to **kibana_sample_data_logs**. . Click **Add layer**. . In **Layer settings**, set: ** **Name** to `Total Requests and Bytes` diff --git a/docs/maps/reverse-geocoding-tutorial.asciidoc b/docs/maps/reverse-geocoding-tutorial.asciidoc index 0c942f120a4da6..8760d3ab4df8b5 100644 --- a/docs/maps/reverse-geocoding-tutorial.asciidoc +++ b/docs/maps/reverse-geocoding-tutorial.asciidoc @@ -141,7 +141,7 @@ PUT kibana_sample_data_logs/_settings ---------------------------------- . Open the main menu, and click *Discover*. -. Set the index pattern to *kibana_sample_data_logs*. +. Set the data view to *kibana_sample_data_logs*. . Open the <>, and set the time range to the last 30 days. . Scan through the list of *Available fields* until you find the `csa.GEOID` field. You can also search for the field by name. . Click image:images/reverse-geocoding-tutorial/add-icon.png[Add icon] to toggle the field into the document table. @@ -162,10 +162,10 @@ Now that our web traffic contains CSA region identifiers, you'll visualize CSA r . Click *Choropleth*. . For *Boundaries source*: .. Select *Points, lines, and polygons from Elasticsearch*. -.. Set *Index pattern* to *csa*. +.. Set *Data view* to *csa*. .. Set *Join field* to *GEOID*. . For *Statistics source*: -.. Set *Index pattern* to *kibana_sample_data_logs*. +.. Set *Data view* to *kibana_sample_data_logs*. .. Set *Join field* to *csa.GEOID.keyword*. . Click *Add layer*. . Scroll to *Layer Style* and Set *Label* to *Fixed*. diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 08624e4ddff57f..a170bcc414d3b0 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -43,7 +43,7 @@ To prevent the global search from applying to a layer, configure the following: [[maps-narrow-layer-by-global-time]] ==== Narrow layers by global time -Layers that request data from {es} using an <> with a configured time field are narrowed by the <>. +Layers that request data from {es} using a <> with a configured time field are narrowed by the <>. These layers contain the clock icon image:maps/images/clock_icon.png[clock icon] next to the layer name in the legend. Use the time slider to quickly select time slices within the global time range: diff --git a/docs/maps/trouble-shooting.asciidoc b/docs/maps/trouble-shooting.asciidoc index 60bcabad3a6b4c..13c8b97c30b3d0 100644 --- a/docs/maps/trouble-shooting.asciidoc +++ b/docs/maps/trouble-shooting.asciidoc @@ -21,18 +21,18 @@ image::maps/images/inspector.png[] === Solutions to common problems [float] -==== Index not listed when adding layer +==== Data view not listed when adding layer * Verify your geospatial data is correctly mapped as {ref}/geo-point.html[geo_point] or {ref}/geo-shape.html[geo_shape]. - ** Run `GET myIndexPatternTitle/_field_caps?fields=myGeoFieldName` in <>, replacing `myIndexPatternTitle` and `myGeoFieldName` with your index pattern title and geospatial field name. + ** Run `GET myIndexName/_field_caps?fields=myGeoFieldName` in <>, replacing `myIndexName` and `myGeoFieldName` with your index and geospatial field name. ** Ensure response specifies `type` as `geo_point` or `geo_shape`. -* Verify your geospatial data is correctly mapped in your <>. - ** Open your index pattern in <>. +* Verify your geospatial data is correctly mapped in your <>. + ** Open your data view in <>. ** Ensure your geospatial field type is `geo_point` or `geo_shape`. ** Ensure your geospatial field is searchable and aggregatable. ** If your geospatial field type does not match your Elasticsearch mapping, click the *Refresh* button to refresh the field list from Elasticsearch. -* Index patterns with thousands of fields can exceed the default maximum payload size. -Increase <> for large index patterns. +* Data views with thousands of fields can exceed the default maximum payload size. +Increase <> for large data views. [float] ==== Features are not displayed diff --git a/docs/maps/vector-tooltips.asciidoc b/docs/maps/vector-tooltips.asciidoc index 2dda35aa28f768..2e4ee99d5b84ff 100644 --- a/docs/maps/vector-tooltips.asciidoc +++ b/docs/maps/vector-tooltips.asciidoc @@ -18,7 +18,7 @@ image::maps/images/multifeature_tooltip.png[] ==== Format tooltips You can format the attributes in a tooltip by adding <> to your -index pattern. You can use field formatters to round numbers, provide units, +data view. You can use field formatters to round numbers, provide units, and even display images in your tooltip. [float] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index d5bc2ccd8ef7d4..4010083d601b56 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -358,3 +358,8 @@ This content has moved. Refer to <>. == Rendering pre-captured profiler JSON This content has moved. Refer to <>. + +[role="exclude",id="index-patterns"] +== Index patterns has been renamed to data views. + +This content has moved. Refer to <>. diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 7737745c7cfa85..2ed3c21c482d59 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -324,52 +324,32 @@ For more details and a reference of audit events, refer to < type: rolling-file - fileName: ./audit.log + fileName: ./data/audit.log policy: type: time-interval - interval: 24h <1> + interval: 24h <2> strategy: type: numeric - max: 10 <2> + max: 10 <3> layout: type: json ---------------------------------------- -<1> Rotates log files every 24 hours. -<2> Keeps maximum of 10 log files before deleting older ones. +<1> This appender is the default and will be used if no `appender.*` config options are specified. +<2> Rotates log files every 24 hours. +<3> Keeps maximum of 10 log files before deleting older ones. -[NOTE] -============ -{ess} does not support custom log file policies. To enable audit logging on {ess} only specify: - -[source,yaml] ----------------------------------------- -xpack.security.audit.enabled: true -xpack.security.audit.appender.type: rolling-file ----------------------------------------- -============ - -[NOTE] -============ -deprecated:[7.15.0,"In 8.0 and later, the legacy audit logger will be removed, and this setting will enable the ECS audit logger with a default appender."] To enable the legacy audit logger only specify: - -[source,yaml] ----------------------------------------- -xpack.security.audit.enabled: true ----------------------------------------- -============ - -| `xpack.security.audit.appender` {ess-icon} -| Optional. Specifies where audit logs should be written to and how they should be formatted. +| `xpack.security.audit.appender` +| Optional. Specifies where audit logs should be written to and how they should be formatted. If no appender is specified, a default appender will be used (see above). -| `xpack.security.audit.appender.type` {ess-icon} +| `xpack.security.audit.appender.type` | Required. Specifies where audit logs should be written to. Allowed values are `console`, `file`, or `rolling-file`. Refer to <> and <> for appender specific settings. diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index ad38ac1710fd56..b1d9d3ea2ea185 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -84,7 +84,7 @@ You can manage your roles, privileges, and spaces in **{stack-manage-app}** in If the {kib} ingest options don't work for you, you can index your data into Elasticsearch with {ref}/getting-started-index.html[REST APIs] or https://www.elastic.co/guide/en/elasticsearch/client/index.html[client libraries]. -After you add your data, you're required to create an <> to tell +After you add your data, you're required to create a <> to tell {kib} where to find the data. * To add data for Elastic Observability, refer to {observability-guide}/add-observability-data.html[Send data to Elasticsearch]. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 9ccfeb6894713d..c9a8210f82198b 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -351,7 +351,7 @@ When `includeElasticMapsService` is turned off, only tile layer configured by << | Specifies the URL of a self hosted <> | [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon} - | The map attribution string. + | The map attribution string. Provide attributions in markdown and use '|' to delimit attributions, for example: `"[attribution 1](https://www.attribution1)|[attribution 2](https://www.attribution2)"`. *Default: `"© [Elastic Maps Service](https://www.elastic.co/elastic-maps-service)"`* | [[tilemap-max-zoom]] `map.tilemap.options.maxZoom:` {ess-icon} diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 1cd8eacc456c7f..1f469b697c2189 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -43,7 +43,7 @@ To create workpads, you must meet the minimum requirements. * If you need to set up {kib}, use https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[our free trial]. -* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and an <>. +* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and a <>. * Have an understanding of {ref}/documents-indices.html[{es} documents and indices]. diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index a2e0eb6bf92e90..474b45f4989fbf 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -5,7 +5,7 @@ -- **_Visualize your data with dashboards._** -The best way to understand your data is to visualize it. With dashboards, you can turn your data from one or more <> into a collection of panels +The best way to understand your data is to visualize it. With dashboards, you can turn your data from one or more <> into a collection of panels that bring clarity to your data, tell a story about your data, and allow you to focus on only the data that's important to you. [role="screenshot"] @@ -53,7 +53,7 @@ To create dashboards, you must meet the minimum requirements. * If you need to set up {kib}, use https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[our free trial]. -* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and an <>. +* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and a <>. * When the read-only indicator appears, you have insufficient privileges to create or save dashboards, and the options to create and save dashboards are not visible. For more information, diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc index d5a52428cff369..02e0afd2c03118 100644 --- a/docs/user/dashboard/lens-advanced.asciidoc +++ b/docs/user/dashboard/lens-advanced.asciidoc @@ -33,7 +33,7 @@ Open *Lens*, then make sure the correct fields appear. . Make sure the *kibana_sample_data_ecommerce* index appears. + -If you are using your own data, select the <> that contains your data. +If you are using your own data, select the <> that contains your data. [discrete] [[custom-time-interval]] diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index 9fe6af2d3da6df..c944ec2c9e0837 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -8,7 +8,7 @@ With *TSVB*, you can: * Combine an infinite number of <> to display your data. * Annotate time series data with timestamped events from an {es} index. * View the data in several types of visualizations, including charts, data tables, and markdown panels. -* Display multiple <> in each visualization. +* Display multiple <> in each visualization. * Use custom functions and some math on aggregations. * Customize the data with labels and colors. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index e52531f9decdc9..a485bb4c96efe3 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -64,7 +64,7 @@ Tell {kib} where to find the data you want to explore, and then specify the time . Select the data you want to work with. + -{kib} uses an <> to tell it where to find +{kib} uses a <> to tell it where to find your {es} data. To view the ecommerce sample data, make sure the index pattern is set to **kibana_sample_data_ecommerce**. + diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 086c0707b3c2cd..5e87efc5e8acae 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -3,7 +3,7 @@ == Create a graph You must index data into {es} before you can create a graph. -<> or get started with a <>. +<> or get started with a <>. [float] [[exploring-connections]] diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index e2f21e3f8470cb..926331008e9909 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -12,46 +12,15 @@ model for authentication, data index authorization, and features that are driven by cluster-wide privileges. For more information on enabling audit logging in {es}, refer to {ref}/auditing.html[Auditing security events]. -[IMPORTANT] -============================================================================ -Kibana offers two audit logs: a **deprecated** legacy audit logger, and a new -ECS-compliant audit logger. We strongly advise using the <>, -as the legacy audit logger will be removed in an upcoming version. -============================================================================ - [NOTE] ============================================================================ Audit logs are **disabled** by default. To enable this functionality, you must -set `xpack.security.audit.enabled` to `true` in `kibana.yml`, and configure +set `xpack.security.audit.enabled` to `true` in `kibana.yml`, and optionally configure an <> to write the audit log to a location of your choosing. ============================================================================ -The legacy audit logger uses the standard {kib} logging output, -which can be configured in `kibana.yml`. For more information, refer to <>. -The <> uses a separate logger and can be configured using -the options in <>. - -==== Legacy audit event types - -When you are auditing security events, each request can generate multiple audit -events. The following is a list of the events that can be generated: - -|====== -| `saved_objects_authorization_success` | Logged when a user is authorized to access a saved - objects when using a role with <> -| `saved_objects_authorization_failure` | Logged when a user isn't authorized to access a saved - objects when using a role with <> -|====== - [[xpack-security-ecs-audit-logging]] -==== ECS audit events - -[IMPORTANT] -============================================================================ -The following events are only logged if the ECS audit logger is enabled. -For information on how to configure `xpack.security.audit.appender`, refer to -<>. -============================================================================ +==== Audit events Refer to the table of events that can be logged for auditing purposes. @@ -81,6 +50,9 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | User has logged in successfully. | `failure` | Failed login attempt (e.g. due to invalid credentials). +| `access_agreement_acknowledged` +| N/A | User has acknowledged the access agreement. + 3+a| ===== Category: database ====== Type: creation @@ -172,6 +144,10 @@ Refer to the corresponding {es} logs for potential write errors. | `unknown` | User is updating a space. | `failure` | User is not authorized to update a space. +.2+| `alert_update` +| `unknown` | User is updating an alert. +| `failure` | User is not authorized to update an alert. + 3+a| ====== Type: deletion @@ -242,6 +218,14 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | User has accessed a space as part of a search operation. | `failure` | User is not authorized to search for spaces. +.2+| `alert_get` +| `success` | User has accessed an alert. +| `failure` | User is not authorized to access an alert. + +.2+| `alert_find` +| `success` | User has accessed an alert as part of a search operation. +| `failure` | User is not authorized to access alerts. + 3+a| ===== Category: web @@ -255,7 +239,7 @@ Refer to the corresponding {es} logs for potential write errors. [[xpack-security-ecs-audit-schema]] -==== ECS audit schema +==== Audit schema Audit logs are written in JSON using https://www.elastic.co/guide/en/ecs/1.6/index.html[Elastic Common Schema (ECS)] specification. diff --git a/examples/bfetch_explorer/public/plugin.tsx b/examples/bfetch_explorer/public/plugin.tsx index f96a900063340a..fd4687c5fb149e 100644 --- a/examples/bfetch_explorer/public/plugin.tsx +++ b/examples/bfetch_explorer/public/plugin.tsx @@ -52,7 +52,7 @@ export class BfetchExplorerPlugin implements Plugin { links: [ { label: 'README', - href: 'https://github.com/elastic/kibana/blob/master/src/plugins/bfetch/README.md', + href: 'https://github.com/elastic/kibana/blob/main/src/plugins/bfetch/README.md', iconType: 'logoGithub', size: 's', target: '_blank', diff --git a/examples/developer_examples/README.md b/examples/developer_examples/README.md index 1a57838c43d249..db847b342d46b7 100644 --- a/examples/developer_examples/README.md +++ b/examples/developer_examples/README.md @@ -14,7 +14,7 @@ services. Add your a link to your example using the developerExamples `register` links: [ { label: 'README', - href: 'https://github.com/elastic/kibana/tree/master/src/plugins/foo/README.md', + href: 'https://github.com/elastic/kibana/tree/main/src/plugins/foo/README.md', iconType: 'logoGithub', target: '_blank', size: 's', diff --git a/examples/developer_examples/public/index.ts b/examples/developer_examples/public/index.ts index a6e5748765ab69..8fcbda387abf0c 100644 --- a/examples/developer_examples/public/index.ts +++ b/examples/developer_examples/public/index.ts @@ -10,4 +10,4 @@ import { DeveloperExamplesPlugin } from './plugin'; export const plugin = () => new DeveloperExamplesPlugin(); -export { DeveloperExamplesSetup } from './plugin'; +export type { DeveloperExamplesSetup } from './plugin'; diff --git a/examples/embeddable_examples/common/index.ts b/examples/embeddable_examples/common/index.ts index bea814e5a3ed08..4ff60ffb3afcf7 100644 --- a/examples/embeddable_examples/common/index.ts +++ b/examples/embeddable_examples/common/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ -export { TodoSavedObjectAttributes } from './todo_saved_object_attributes'; -export { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from './book_saved_object_attributes'; +export type { TodoSavedObjectAttributes } from './todo_saved_object_attributes'; +export type { BookSavedObjectAttributes } from './book_saved_object_attributes'; +export { BOOK_SAVED_OBJECT } from './book_saved_object_attributes'; diff --git a/examples/embeddable_examples/public/index.ts b/examples/embeddable_examples/public/index.ts index 365c001559843b..43d8db39692c7f 100644 --- a/examples/embeddable_examples/public/index.ts +++ b/examples/embeddable_examples/public/index.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ +export type { HelloWorldEmbeddableFactory } from './hello_world'; export { HELLO_WORLD_EMBEDDABLE, HelloWorldEmbeddable, HelloWorldEmbeddableFactoryDefinition, - HelloWorldEmbeddableFactory, } from './hello_world'; -export { ListContainer, LIST_CONTAINER, ListContainerFactory } from './list_container'; -export { TODO_EMBEDDABLE, TodoEmbeddableFactory } from './todo'; +export type { ListContainerFactory } from './list_container'; +export { ListContainer, LIST_CONTAINER } from './list_container'; +export type { TodoEmbeddableFactory } from './todo'; +export { TODO_EMBEDDABLE } from './todo'; export { BOOK_EMBEDDABLE } from './book'; @@ -21,10 +23,8 @@ export { SIMPLE_EMBEDDABLE } from './migrations'; import { EmbeddableExamplesPlugin } from './plugin'; -export { - SearchableListContainer, - SEARCHABLE_LIST_CONTAINER, - SearchableListContainerFactory, -} from './searchable_list_container'; -export { MULTI_TASK_TODO_EMBEDDABLE, MultiTaskTodoEmbeddableFactory } from './multi_task_todo'; +export type { SearchableListContainerFactory } from './searchable_list_container'; +export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container'; +export type { MultiTaskTodoEmbeddableFactory } from './multi_task_todo'; +export { MULTI_TASK_TODO_EMBEDDABLE } from './multi_task_todo'; export const plugin = () => new EmbeddableExamplesPlugin(); diff --git a/examples/embeddable_examples/public/list_container/index.ts b/examples/embeddable_examples/public/list_container/index.ts index cda0e55e59eef4..299c3eeae42cd4 100644 --- a/examples/embeddable_examples/public/list_container/index.ts +++ b/examples/embeddable_examples/public/list_container/index.ts @@ -7,4 +7,5 @@ */ export { ListContainer, LIST_CONTAINER } from './list_container'; -export { ListContainerFactoryDefinition, ListContainerFactory } from './list_container_factory'; +export type { ListContainerFactory } from './list_container_factory'; +export { ListContainerFactoryDefinition } from './list_container_factory'; diff --git a/examples/embeddable_examples/public/searchable_list_container/index.ts b/examples/embeddable_examples/public/searchable_list_container/index.ts index 5448731abf2c14..cea0154be812d4 100644 --- a/examples/embeddable_examples/public/searchable_list_container/index.ts +++ b/examples/embeddable_examples/public/searchable_list_container/index.ts @@ -7,7 +7,5 @@ */ export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container'; -export { - SearchableListContainerFactoryDefinition, - SearchableListContainerFactory, -} from './searchable_list_container_factory'; +export type { SearchableListContainerFactory } from './searchable_list_container_factory'; +export { SearchableListContainerFactoryDefinition } from './searchable_list_container_factory'; diff --git a/examples/embeddable_explorer/public/plugin.tsx b/examples/embeddable_explorer/public/plugin.tsx index 2154a69ce1a87e..f6cdee1da126c6 100644 --- a/examples/embeddable_explorer/public/plugin.tsx +++ b/examples/embeddable_explorer/public/plugin.tsx @@ -63,7 +63,7 @@ export class EmbeddableExplorerPlugin implements Plugin { demonstrated. You can read more about it{' '} here diff --git a/examples/expressions_explorer/public/plugin.tsx b/examples/expressions_explorer/public/plugin.tsx index 7eee563c43f27b..329dcfbb11d456 100644 --- a/examples/expressions_explorer/public/plugin.tsx +++ b/examples/expressions_explorer/public/plugin.tsx @@ -72,7 +72,7 @@ export class ExpressionsExplorerPlugin implements Plugin new LocatorExamplesPlugin(); diff --git a/examples/locator_explorer/public/plugin.tsx b/examples/locator_explorer/public/plugin.tsx index 45b1d2d7939eaf..240a1a34caef9c 100644 --- a/examples/locator_explorer/public/plugin.tsx +++ b/examples/locator_explorer/public/plugin.tsx @@ -47,7 +47,7 @@ export class LocatorExplorerPlugin implements Plugin { const onCompleteSetup = async ({ shouldReloadConfig }: { shouldReloadConfig: boolean }) => { @@ -41,7 +41,8 @@ export const App = ({ http, token }: { http: HttpSetup; token?: string }) => { .post('/api/preboot/connect_to_es', { body: JSON.stringify(elasticsearchConfig) }) .then( (response) => setConnectResponse(JSON.stringify(response)), - (err: IHttpFetchError) => setConnectResponse(err?.body?.message || 'ERROR') + (err: IHttpFetchError) => + setConnectResponse(err?.body?.message || 'ERROR') ); }; diff --git a/examples/routing_example/public/app.tsx b/examples/routing_example/public/app.tsx index e9794c86efbc9f..e8b7b44a0a0973 100644 --- a/examples/routing_example/public/app.tsx +++ b/examples/routing_example/public/app.tsx @@ -47,21 +47,21 @@ function RoutingExplorer({ listItems={[ { label: 'IRouter API docs', - href: 'https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.irouter.md', + href: 'https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.irouter.md', iconType: 'logoGithub', target: '_blank', size: 's', }, { label: 'HttpHandler (core.http.fetch) API docs', - href: 'https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.httphandler.md', + href: 'https://github.com/elastic/kibana/blob/main/docs/development/core/public/kibana-plugin-core-public.httphandler.md', iconType: 'logoGithub', target: '_blank', size: 's', }, { label: 'Conventions', - href: 'https://github.com/elastic/kibana/tree/master/STYLEGUIDE.mdx#api-endpoints', + href: 'https://github.com/elastic/kibana/tree/main/STYLEGUIDE.mdx#api-endpoints', iconType: 'logoGithub', target: '_blank', size: 's', diff --git a/examples/routing_example/public/plugin.tsx b/examples/routing_example/public/plugin.tsx index 00a92227e067ce..d47d31f7631de7 100644 --- a/examples/routing_example/public/plugin.tsx +++ b/examples/routing_example/public/plugin.tsx @@ -41,14 +41,14 @@ export class RoutingExamplePlugin implements Plugin<{}, {}, SetupDeps, {}> { links: [ { label: 'IRouter', - href: 'https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.irouter.md', + href: 'https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.irouter.md', iconType: 'logoGithub', target: '_blank', size: 's', }, { label: 'HttpHandler (core.http.fetch)', - href: 'https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.httphandler.md', + href: 'https://github.com/elastic/kibana/blob/main/docs/development/core/public/kibana-plugin-core-public.httphandler.md', iconType: 'logoGithub', target: '_blank', size: 's', diff --git a/examples/screenshot_mode_example/README.md b/examples/screenshot_mode_example/README.md index ebae7480ca5fe0..0e37f9bab78cc8 100755 --- a/examples/screenshot_mode_example/README.md +++ b/examples/screenshot_mode_example/README.md @@ -6,4 +6,4 @@ A Kibana plugin ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/examples/search_examples/README.md b/examples/search_examples/README.md index bcc17bf7f33338..0ffd4b6cf96c64 100644 --- a/examples/search_examples/README.md +++ b/examples/search_examples/README.md @@ -6,4 +6,4 @@ ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/examples/search_examples/public/application.tsx b/examples/search_examples/public/application.tsx index 1920cdbe5c697c..4e5212b6f76a2d 100644 --- a/examples/search_examples/public/application.tsx +++ b/examples/search_examples/public/application.tsx @@ -27,7 +27,7 @@ const LINKS: ExampleLink[] = [ title: 'Search Sessions', }, { - path: 'https://github.com/elastic/kibana/blob/master/src/plugins/data/README.mdx', + path: 'https://github.com/elastic/kibana/blob/main/src/plugins/data/README.mdx', title: 'README (GitHub)', }, ]; diff --git a/examples/search_examples/public/index.ts b/examples/search_examples/public/index.ts index fd557f752a38ed..c1361facc29414 100644 --- a/examples/search_examples/public/index.ts +++ b/examples/search_examples/public/index.ts @@ -15,4 +15,4 @@ import { SearchExamplesPlugin } from './plugin'; export function plugin() { return new SearchExamplesPlugin(); } -export { SearchExamplesPluginSetup, SearchExamplesPluginStart } from './types'; +export type { SearchExamplesPluginSetup, SearchExamplesPluginStart } from './types'; diff --git a/examples/search_examples/public/plugin.ts b/examples/search_examples/public/plugin.ts index 95ea688f49cc24..368231018e1a97 100644 --- a/examples/search_examples/public/plugin.ts +++ b/examples/search_examples/public/plugin.ts @@ -59,7 +59,7 @@ export class SearchExamplesPlugin links: [ { label: 'README', - href: 'https://github.com/elastic/kibana/tree/master/src/plugins/data/README.mdx', + href: 'https://github.com/elastic/kibana/tree/main/src/plugins/data/README.mdx', iconType: 'logoGithub', target: '_blank', size: 's', diff --git a/examples/search_examples/server/index.ts b/examples/search_examples/server/index.ts index f351681a1041a3..75c23e8e892579 100644 --- a/examples/search_examples/server/index.ts +++ b/examples/search_examples/server/index.ts @@ -13,4 +13,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new SearchExamplesPlugin(initializerContext); } -export { SearchExamplesPluginSetup, SearchExamplesPluginStart } from './types'; +export type { SearchExamplesPluginSetup, SearchExamplesPluginStart } from './types'; diff --git a/examples/state_containers_examples/public/plugin.ts b/examples/state_containers_examples/public/plugin.ts index ac65d42ae40506..bfc20fd2b48d20 100644 --- a/examples/state_containers_examples/public/plugin.ts +++ b/examples/state_containers_examples/public/plugin.ts @@ -88,14 +88,14 @@ export class StateContainersExamplesPlugin implements Plugin { links: [ { label: 'State containers README', - href: 'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers', + href: 'https://github.com/elastic/kibana/tree/main/src/plugins/kibana_utils/docs/state_containers', iconType: 'logoGithub', size: 's', target: '_blank', }, { label: 'State sync utils README', - href: 'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_sync', + href: 'https://github.com/elastic/kibana/tree/main/src/plugins/kibana_utils/docs/state_sync', iconType: 'logoGithub', size: 's', target: '_blank', diff --git a/examples/ui_actions_explorer/public/plugin.tsx b/examples/ui_actions_explorer/public/plugin.tsx index d3a87c5b64fd60..2b4365e987ac84 100644 --- a/examples/ui_actions_explorer/public/plugin.tsx +++ b/examples/ui_actions_explorer/public/plugin.tsx @@ -85,7 +85,7 @@ export class UiActionsExplorerPlugin implements Plugin KibanaResponse | Promise; ``` and accepts next Kibana specific parameters as arguments: -- context: [Context](https://github.com/elastic/kibana/blob/master/rfcs/text/0003_handler_interface.md#handler-context). A handler context contains core service and plugin functionality already scoped to the incoming request. -- request: [KibanaRequest](https://github.com/elastic/kibana/blob/master/src/core/server/http/router/request.ts). An immutable representation of the incoming request details, such as body, parameters, query, url and route information. Note: you **must** to specify route schema during route declaration to have access to `body, parameters, query` in the request object. You cannot extend KibanaRequest with arbitrary data nor remove any properties from it. +- context: [Context](https://github.com/elastic/kibana/blob/main/rfcs/text/0003_handler_interface.md#handler-context). A handler context contains core service and plugin functionality already scoped to the incoming request. +- request: [KibanaRequest](https://github.com/elastic/kibana/blob/main/src/core/server/http/router/request.ts). An immutable representation of the incoming request details, such as body, parameters, query, url and route information. Note: you **must** to specify route schema during route declaration to have access to `body, parameters, query` in the request object. You cannot extend KibanaRequest with arbitrary data nor remove any properties from it. ```typescript interface KibanaRequest { url: url.Url; @@ -51,7 +51,7 @@ interface KibanaRequest { } } ``` -- t: [KibanaResponseToolkit](https://github.com/elastic/kibana/blob/master/src/core/server/http/router/response.ts#L27) +- t: [KibanaResponseToolkit](https://github.com/elastic/kibana/blob/main/src/core/server/http/router/response.ts#L27) Provides a set of pre-configured methods to respond to an incoming request. It is expected that handler **always** returns a result of one of `KibanaResponseToolkit` methods as an output: ```typescript interface KibanaResponseToolkit { diff --git a/legacy_rfcs/text/0006_management_section_service.md b/legacy_rfcs/text/0006_management_section_service.md index 1a52e85a4ff167..c2a62c3686680e 100644 --- a/legacy_rfcs/text/0006_management_section_service.md +++ b/legacy_rfcs/text/0006_management_section_service.md @@ -30,7 +30,7 @@ This means that we will basically need to rebuild the service anyway in order to - Flexibility to potentially support alternate layouts in the future (see mockups in [reference section](#reference) below) # Basic example -This API is influenced heavily by the [application service mounting RFC](https://github.com/elastic/kibana/blob/master/rfcs/text/0004_application_service_mounting.md). The intent is to make the experience consistent with that service; the Management section is basically one big app with a bunch of registered "subapps". +This API is influenced heavily by the [application service mounting RFC](https://github.com/elastic/kibana/blob/main/rfcs/text/0004_application_service_mounting.md). The intent is to make the experience consistent with that service; the Management section is basically one big app with a bunch of registered "subapps". ```ts // my_plugin/public/plugin.ts diff --git a/legacy_rfcs/text/0014_api_documentation.md b/legacy_rfcs/text/0014_api_documentation.md index b70636c63aad37..0f934c2f09faf4 100644 --- a/legacy_rfcs/text/0014_api_documentation.md +++ b/legacy_rfcs/text/0014_api_documentation.md @@ -14,7 +14,7 @@ plugin APIs. # Technology: ts-morph vs api-extractor -[Api-extractor](https://api-extractor.com/) is a utility built from microsoft that parses typescript code into json files that can then be used in a custom [api-documenter](https://api-extractor.com/pages/setup/generating_docs/) in order to build documentation. This is what we [have now](https://github.com/elastic/kibana/tree/master/docs/development), except we use the default api-documenter. +[Api-extractor](https://api-extractor.com/) is a utility built from microsoft that parses typescript code into json files that can then be used in a custom [api-documenter](https://api-extractor.com/pages/setup/generating_docs/) in order to build documentation. This is what we [have now](https://github.com/elastic/kibana/tree/main/docs/development), except we use the default api-documenter. ## Limitations with the current implementation using api-extractor & api-documenter diff --git a/legacy_rfcs/text/0015_bazel.md b/legacy_rfcs/text/0015_bazel.md index 1bdd80e2cbaaf3..82c93c3c7e9c18 100644 --- a/legacy_rfcs/text/0015_bazel.md +++ b/legacy_rfcs/text/0015_bazel.md @@ -32,7 +32,7 @@ Yarn Package Manager handles the installation of NPM dependencies, and the migra ### Building packages -The building of [packages](https://github.com/elastic/kibana/tree/master/packages) happens during the bootstrap process initiated by running `yarn kbn bootstrap` and without any cache takes about a minute. Currently, we maintain a single cache item per package, so drastic changes like switching branches frequently results in the worst-case scenario of no-cache being usable. +The building of [packages](https://github.com/elastic/kibana/tree/main/packages) happens during the bootstrap process initiated by running `yarn kbn bootstrap` and without any cache takes about a minute. Currently, we maintain a single cache item per package, so drastic changes like switching branches frequently results in the worst-case scenario of no-cache being usable. ### Building TypeScript project references @@ -40,7 +40,7 @@ The size of the project and the amount of TypeScript has created scaling issues, ### Building client-side plugins -The [@kbn/optimizer](https://github.com/elastic/kibana/tree/master/packages/kbn-optimizer) package is responsible for building client-side plugins and is initiated during `yarn start`. Without any cache, it takes between three and four minutes, but is highly dependent on the amount of CPU cores available. The caching works similar to packages and requires a rebuild if any files change. Under the hood, this package is managing a set number of workers to run individual Webpack instances. When we first introduced Webpack back in [June of 2015](https://github.com/elastic/kibana/pull/4335), it was responsible for bundling all client-side code within a single process. As the Kibana project continued to grow over time, this Webpack process continued to impact the developer experience. A common theme to address these issues was through reducing the responsibilities of Webpack by separating [SCSS](https://github.com/elastic/kibana/pull/19643) and [vendor code](https://github.com/elastic/kibana/pull/22618). Knowing we would need to continue to scale, one of the new platform’s core objectives was to be able to build each plugin independently. This work paved the way for what we are proposing here and led to the [creation of @kbn/optimizer](https://github.com/elastic/kibana/pull/53976), which improved performance by separating and parallelizing Webpack builds. +The [@kbn/optimizer](https://github.com/elastic/kibana/tree/main/packages/kbn-optimizer) package is responsible for building client-side plugins and is initiated during `yarn start`. Without any cache, it takes between three and four minutes, but is highly dependent on the amount of CPU cores available. The caching works similar to packages and requires a rebuild if any files change. Under the hood, this package is managing a set number of workers to run individual Webpack instances. When we first introduced Webpack back in [June of 2015](https://github.com/elastic/kibana/pull/4335), it was responsible for bundling all client-side code within a single process. As the Kibana project continued to grow over time, this Webpack process continued to impact the developer experience. A common theme to address these issues was through reducing the responsibilities of Webpack by separating [SCSS](https://github.com/elastic/kibana/pull/19643) and [vendor code](https://github.com/elastic/kibana/pull/22618). Knowing we would need to continue to scale, one of the new platform’s core objectives was to be able to build each plugin independently. This work paved the way for what we are proposing here and led to the [creation of @kbn/optimizer](https://github.com/elastic/kibana/pull/53976), which improved performance by separating and parallelizing Webpack builds. ### Compiling server-side code @@ -76,7 +76,7 @@ A Bazel [macro](https://docs.bazel.build/versions/master/skylark/macros.html) wi A Bazel [macro](https://docs.bazel.build/versions/master/skylark/macros.html) will be created to centralize the usage of Webpack. The macro will, at minimum, accept a configuration file and supply a base `webpack.config.js` file. Currently, all plugins share the same Webpack configuration. Allowing a plugin to provide additional configuration will allow plugins the ability to add loaders without affecting the performance of others. -While running Kibana from source in development, the proxy server will ensure that client-side code for plugins is compiled and available. This is currently handled by the [basePathProxy](https://github.com/elastic/kibana/blob/master/src/core/server/http/base_path_proxy_server.ts), where server restarts and optimizer builds are observed and cause the proxy to pause requests. With Bazel, we will utilize [iBazel](https://github.com/bazelbuild/bazel-watche) to watch for file changes and re-build the plugin targets when necessary. The watcher will emit [events](https://github.com/bazelbuild/bazel-watcher#remote-events) that we will use to block requests and provide feedback to the logs. +While running Kibana from source in development, the proxy server will ensure that client-side code for plugins is compiled and available. This is currently handled by the [basePathProxy](https://github.com/elastic/kibana/blob/main/src/core/server/http/base_path_proxy_server.ts), where server restarts and optimizer builds are observed and cause the proxy to pause requests. With Bazel, we will utilize [iBazel](https://github.com/bazelbuild/bazel-watche) to watch for file changes and re-build the plugin targets when necessary. The watcher will emit [events](https://github.com/bazelbuild/bazel-watcher#remote-events) that we will use to block requests and provide feedback to the logs. While there are a few proofs of concepts for a Webpack 5 Bazel rule, none currently exist which are deemed production-ready. In the meantime, we can use the Webpack CLI directly. One of the main advantages being explored in these rules will be the support for using the Bazel worker to provide incremental builds similar to what `@kbn/optimizer` is doing today. @@ -85,7 +85,7 @@ We are aware there are quite a few alternatives to Webpack, but our plan is to c ### Unit Testing -A Bazel macro will be created to centralize the usage of Jest unit testing. The macro will, at minimum, accept a Jest configuration file, add the [Jest preset](https://github.com/elastic/kibana/blob/master/packages/kbn-test/jest-preset.js) and its dependencies as sources, then use the Jest CLI to execute tests. +A Bazel macro will be created to centralize the usage of Jest unit testing. The macro will, at minimum, accept a Jest configuration file, add the [Jest preset](https://github.com/elastic/kibana/blob/main/packages/kbn-test/jest-preset.js) and its dependencies as sources, then use the Jest CLI to execute tests. Developers currently use `yarn test:jest` to efficiently run tests in a given directory without remembering the command or path. This command will continue to work as it does today, but will begin running tests through Bazel for packages or plugins which have been migrated. diff --git a/package.json b/package.json index 0e5c042e4236d4..440a30d36534e4 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ], "private": true, "version": "8.1.0", - "branch": "master", + "branch": "main", "types": "./kibana.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", "build": { @@ -87,7 +87,7 @@ "**/underscore": "^1.13.1" }, "engines": { - "node": "16.11.1", + "node": "16.13.0", "yarn": "^1.21.1" }, "dependencies": { @@ -95,14 +95,14 @@ "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", - "@elastic/apm-synthtrace": "link:bazel-bin/packages/elastic-apm-synthtrace", "@elastic/apm-rum": "^5.9.1", "@elastic/apm-rum-react": "^1.3.1", - "@elastic/charts": "38.1.0", + "@elastic/apm-synthtrace": "link:bazel-bin/packages/elastic-apm-synthtrace", + "@elastic/charts": "38.1.3", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.35", "@elastic/ems-client": "8.0.0", - "@elastic/eui": "40.0.0", + "@elastic/eui": "40.1.0", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.3.0", "@elastic/node-crypto": "1.2.1", @@ -111,7 +111,10 @@ "@elastic/request-crypto": "1.1.4", "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set", "@elastic/search-ui-app-search-connector": "^1.6.0", + "@emotion/cache": "^11.4.0", + "@emotion/css": "^11.4.0", "@emotion/react": "^11.4.0", + "@emotion/serialize": "^1.0.2", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.4", "@hapi/cookie": "^11.0.2", @@ -192,7 +195,7 @@ "brace": "0.11.1", "broadcast-channel": "^4.2.0", "chalk": "^4.1.0", - "cheerio": "^1.0.0-rc.9", + "cheerio": "^1.0.0-rc.10", "chokidar": "^3.4.3", "chroma-js": "^1.4.1", "classnames": "2.2.6", @@ -435,7 +438,7 @@ "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", - "@elastic/synthetics": "^1.0.0-beta.12", + "@elastic/synthetics": "^1.0.0-beta.16", "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/jest": "^11.3.0", "@istanbuljs/schema": "^0.1.2", @@ -542,6 +545,7 @@ "@types/jest-when": "^2.7.2", "@types/joi": "^17.2.3", "@types/jquery": "^3.3.31", + "@types/js-levenshtein": "^1.1.0", "@types/js-search": "^1.4.0", "@types/js-yaml": "^3.11.1", "@types/jsdom": "^16.2.3", @@ -595,6 +599,7 @@ "@types/react-router-dom": "^5.1.5", "@types/react-test-renderer": "^16.9.1", "@types/react-virtualized": "^9.18.7", + "@types/react-vis": "^1.11.9", "@types/read-pkg": "^4.0.0", "@types/recompose": "^0.30.6", "@types/reduce-reducers": "^1.0.0", @@ -633,9 +638,9 @@ "@types/xml2js": "^0.4.5", "@types/yauzl": "^2.9.1", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^4.31.2", - "@typescript-eslint/parser": "^4.31.2", - "@typescript-eslint/typescript-estree": "^4.31.2", + "@typescript-eslint/eslint-plugin": "^5.2.0", + "@typescript-eslint/parser": "^5.2.0", + "@typescript-eslint/typescript-estree": "^5.2.0", "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", "aggregate-error": "^3.1.0", @@ -667,6 +672,7 @@ "cypress": "^8.5.0", "cypress-axe": "^0.13.0", "cypress-cucumber-preprocessor": "^2.5.2", + "cypress-file-upload": "^5.0.8", "cypress-multi-reporters": "^1.5.0", "cypress-pipe": "^2.0.0", "cypress-real-events": "^1.5.1", diff --git a/packages/elastic-apm-synthtrace/src/index.ts b/packages/elastic-apm-synthtrace/src/index.ts index 7007e92012a66a..70105438ff5ae4 100644 --- a/packages/elastic-apm-synthtrace/src/index.ts +++ b/packages/elastic-apm-synthtrace/src/index.ts @@ -7,9 +7,15 @@ */ export { service } from './lib/service'; +export { browser } from './lib/browser'; export { timerange } from './lib/timerange'; export { getTransactionMetrics } from './lib/utils/get_transaction_metrics'; export { getSpanDestinationMetrics } from './lib/utils/get_span_destination_metrics'; export { getObserverDefaults } from './lib/defaults/get_observer_defaults'; +export { getChromeUserAgentDefaults } from './lib/defaults/get_chrome_user_agent_defaults'; export { toElasticsearchOutput } from './lib/output/to_elasticsearch_output'; export { getBreakdownMetrics } from './lib/utils/get_breakdown_metrics'; +export { cleanWriteTargets } from './lib/utils/clean_write_targets'; +export { getWriteTargets } from './lib/utils/get_write_targets'; +export { SynthtraceEsClient } from './lib/client/synthtrace_es_client'; +export { createLogger, LogLevel } from './lib/utils/create_logger'; diff --git a/packages/elastic-apm-synthtrace/src/lib/browser.ts b/packages/elastic-apm-synthtrace/src/lib/browser.ts new file mode 100644 index 00000000000000..0fd8b44b69851d --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/browser.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 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 { Entity, UserAgentFields } from './entity'; +import { RumSpan } from './rum_span'; +import { RumTransaction } from './rum_transaction'; + +export class Browser extends Entity { + transaction(transactionName: string, transactionType: string = 'page-load') { + return new RumTransaction({ + ...this.fields, + 'transaction.name': transactionName, + 'transaction.type': transactionType, + }); + } + + span(spanName: string, spanType: string, spanSubtype: string) { + return new RumSpan({ + ...this.fields, + 'span.name': spanName, + 'span.type': spanType, + 'span.subtype': spanSubtype, + }); + } +} + +export function browser(serviceName: string, production: string, userAgent: UserAgentFields) { + return new Browser({ + 'agent.name': 'rum-js', + 'service.name': serviceName, + 'service.environment': production, + ...userAgent, + }); +} diff --git a/packages/elastic-apm-synthtrace/src/lib/client/synthtrace_es_client.ts b/packages/elastic-apm-synthtrace/src/lib/client/synthtrace_es_client.ts new file mode 100644 index 00000000000000..546214f83c364e --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/client/synthtrace_es_client.ts @@ -0,0 +1,55 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { uploadEvents } from '../../scripts/utils/upload_events'; +import { Fields } from '../entity'; +import { cleanWriteTargets } from '../utils/clean_write_targets'; +import { getBreakdownMetrics } from '../utils/get_breakdown_metrics'; +import { getSpanDestinationMetrics } from '../utils/get_span_destination_metrics'; +import { getTransactionMetrics } from '../utils/get_transaction_metrics'; +import { getWriteTargets } from '../utils/get_write_targets'; +import { Logger } from '../utils/logger'; + +export class SynthtraceEsClient { + constructor(private readonly client: Client, private readonly logger: Logger) {} + + private getWriteTargets() { + return getWriteTargets({ client: this.client }); + } + + clean() { + return this.getWriteTargets().then((writeTargets) => + cleanWriteTargets({ client: this.client, writeTargets, logger: this.logger }) + ); + } + + async index(events: Fields[]) { + const eventsToIndex = [ + ...events, + ...getTransactionMetrics(events), + ...getSpanDestinationMetrics(events), + ...getBreakdownMetrics(events), + ]; + + const writeTargets = await this.getWriteTargets(); + + await uploadEvents({ + batchSize: 1000, + client: this.client, + clientWorkers: 5, + events: eventsToIndex, + logger: this.logger, + writeTargets, + }); + + return this.client.indices.refresh({ + index: Object.values(writeTargets), + }); + } +} diff --git a/packages/elastic-apm-synthtrace/src/lib/defaults/get_chrome_user_agent_defaults.ts b/packages/elastic-apm-synthtrace/src/lib/defaults/get_chrome_user_agent_defaults.ts new file mode 100644 index 00000000000000..0031456248c1a8 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/defaults/get_chrome_user_agent_defaults.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 { UserAgentFields } from '../entity'; + +export function getChromeUserAgentDefaults(): UserAgentFields { + return { + 'user_agent.original': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36', + 'user_agent.device.name': 'MacBook', + 'user_agent.name': 'Chrome', + 'user_agent.version': 95, + 'user_agent.os.name': 'MacOS', + }; +} diff --git a/packages/elastic-apm-synthtrace/src/lib/entity.ts b/packages/elastic-apm-synthtrace/src/lib/entity.ts index bf8fc10efd3a7e..c6e0c7193f8bac 100644 --- a/packages/elastic-apm-synthtrace/src/lib/entity.ts +++ b/packages/elastic-apm-synthtrace/src/lib/entity.ts @@ -15,6 +15,14 @@ export type ApplicationMetricFields = Partial<{ 'system.process.cpu.total.norm.pct': number; }>; +export type UserAgentFields = Partial<{ + 'user_agent.original': string; + 'user_agent.os.name': string; + 'user_agent.name': string; + 'user_agent.device.name': string; + 'user_agent.version': number; +}>; + export interface Exception { message: string; } @@ -32,6 +40,7 @@ export type Fields = Partial<{ 'error.grouping_name': string; 'error.grouping_key': string; 'host.name': string; + 'kubernetes.pod.uid': string; 'metricset.name': string; 'observer.version': string; 'observer.version_major': number; diff --git a/packages/elastic-apm-synthtrace/src/lib/instance.ts b/packages/elastic-apm-synthtrace/src/lib/instance.ts index 3570f497c90555..08444fde48ba6b 100644 --- a/packages/elastic-apm-synthtrace/src/lib/instance.ts +++ b/packages/elastic-apm-synthtrace/src/lib/instance.ts @@ -38,6 +38,11 @@ export class Instance extends Entity { }); } + podId(podId: string) { + this.fields['kubernetes.pod.uid'] = podId; + return this; + } + appMetrics(metrics: ApplicationMetricFields) { return new Metricset({ ...this.fields, diff --git a/typings/js_levenshtein.d.ts b/packages/elastic-apm-synthtrace/src/lib/rum_span.ts similarity index 75% rename from typings/js_levenshtein.d.ts rename to packages/elastic-apm-synthtrace/src/lib/rum_span.ts index 7c934333dbc7b6..620da9041ddd8c 100644 --- a/typings/js_levenshtein.d.ts +++ b/packages/elastic-apm-synthtrace/src/lib/rum_span.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -declare module 'js-levenshtein' { - const levenshtein: (a: string, b: string) => number; - export = levenshtein; -} +import { Span } from './span'; + +export class RumSpan extends Span {} diff --git a/typings/global_fetch.d.ts b/packages/elastic-apm-synthtrace/src/lib/rum_transaction.ts similarity index 62% rename from typings/global_fetch.d.ts rename to packages/elastic-apm-synthtrace/src/lib/rum_transaction.ts index 597bc7e89497c5..84528874101790 100644 --- a/typings/global_fetch.d.ts +++ b/packages/elastic-apm-synthtrace/src/lib/rum_transaction.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -// This type needs to still exist due to apollo-link-http-common hasn't yet updated -// it's usage (https://github.com/apollographql/apollo-link/issues/1131) -declare type GlobalFetch = WindowOrWorkerGlobalScope; +import { Transaction } from './transaction'; + +export class RumTransaction extends Transaction {} diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/clean_write_targets.ts b/packages/elastic-apm-synthtrace/src/lib/utils/clean_write_targets.ts similarity index 100% rename from packages/elastic-apm-synthtrace/src/scripts/utils/clean_write_targets.ts rename to packages/elastic-apm-synthtrace/src/lib/utils/clean_write_targets.ts diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/create_logger.ts b/packages/elastic-apm-synthtrace/src/lib/utils/create_logger.ts new file mode 100644 index 00000000000000..88d0d4af3a66be --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/utils/create_logger.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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. + */ + +function isPromise(val: any): val is Promise { + return val && typeof val === 'object' && 'then' in val && typeof val.then === 'function'; +} + +export enum LogLevel { + trace = 0, + debug = 1, + info = 2, + error = 3, +} + +function getTimeString() { + return `[${new Date().toLocaleTimeString()}]`; +} + +export function createLogger(logLevel: LogLevel) { + function logPerf(name: string, start: bigint) { + // eslint-disable-next-line no-console + console.debug( + getTimeString(), + `${name}: ${Number(process.hrtime.bigint() - start) / 1000000}ms` + ); + } + return { + perf: (name: string, cb: () => T): T => { + if (logLevel <= LogLevel.trace) { + const start = process.hrtime.bigint(); + const val = cb(); + if (isPromise(val)) { + val.then(() => { + logPerf(name, start); + }); + } else { + logPerf(name, start); + } + return val; + } + return cb(); + }, + debug: (...args: any[]) => { + if (logLevel <= LogLevel.debug) { + // eslint-disable-next-line no-console + console.debug(getTimeString(), ...args); + } + }, + info: (...args: any[]) => { + if (logLevel <= LogLevel.info) { + // eslint-disable-next-line no-console + console.log(getTimeString(), ...args); + } + }, + error: (...args: any[]) => { + if (logLevel <= LogLevel.error) { + // eslint-disable-next-line no-console + console.log(getTimeString(), ...args); + } + }, + }; +} + +export type Logger = ReturnType; diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/get_write_targets.ts b/packages/elastic-apm-synthtrace/src/lib/utils/get_write_targets.ts similarity index 100% rename from packages/elastic-apm-synthtrace/src/scripts/utils/get_write_targets.ts rename to packages/elastic-apm-synthtrace/src/lib/utils/get_write_targets.ts diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/logger.ts b/packages/elastic-apm-synthtrace/src/lib/utils/logger.ts similarity index 100% rename from packages/elastic-apm-synthtrace/src/scripts/utils/logger.ts rename to packages/elastic-apm-synthtrace/src/lib/utils/logger.ts diff --git a/packages/elastic-apm-synthtrace/src/scripts/run.ts b/packages/elastic-apm-synthtrace/src/scripts/run.ts index 367cdc2b915059..aa427d8e211ae2 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/run.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/run.ts @@ -7,7 +7,7 @@ */ import datemath from '@elastic/datemath'; import yargs from 'yargs/yargs'; -import { cleanWriteTargets } from './utils/clean_write_targets'; +import { cleanWriteTargets } from '../lib/utils/clean_write_targets'; import { intervalToMs } from './utils/interval_to_ms'; import { getCommonResources } from './utils/get_common_resources'; import { startHistoricalDataUpload } from './utils/start_historical_data_upload'; diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_resources.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_resources.ts index 3b51ac6c0c0a73..baa1d8758c3c48 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_resources.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_resources.ts @@ -8,9 +8,9 @@ import { Client } from '@elastic/elasticsearch'; import { getScenario } from './get_scenario'; -import { getWriteTargets } from './get_write_targets'; +import { getWriteTargets } from '../../lib/utils/get_write_targets'; import { intervalToMs } from './interval_to_ms'; -import { createLogger, LogLevel } from './logger'; +import { createLogger, LogLevel } from '../../lib/utils/create_logger'; export async function getCommonResources({ file, diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts index 887969e8459cc0..f8c59cff4febc1 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts @@ -7,7 +7,7 @@ */ import Path from 'path'; import { Fields } from '../../lib/entity'; -import { Logger } from './logger'; +import { Logger } from '../../lib/utils/create_logger'; export type Scenario = (options: { from: number; to: number }) => Fields[]; diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts index e940896fb3687b..dc568170a9744c 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts @@ -10,7 +10,7 @@ import pLimit from 'p-limit'; import Path from 'path'; import { Worker } from 'worker_threads'; import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; -import { Logger, LogLevel } from './logger'; +import { Logger, LogLevel } from '../../lib/utils/create_logger'; export async function startHistoricalDataUpload({ from, diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts index 0032df1d700e99..cec0970420d161 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts @@ -11,7 +11,7 @@ import { partition } from 'lodash'; import { Fields } from '../../lib/entity'; import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; import { Scenario } from './get_scenario'; -import { Logger } from './logger'; +import { Logger } from '../../lib/utils/create_logger'; import { uploadEvents } from './upload_events'; export function startLiveDataUpload({ diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts index 72258ec2815a8c..73829485259865 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts @@ -14,7 +14,7 @@ import { ElasticsearchOutputWriteTargets, toElasticsearchOutput, } from '../../lib/output/to_elasticsearch_output'; -import { Logger } from './logger'; +import { Logger } from '../../lib/utils/create_logger'; export function uploadEvents({ events, @@ -56,23 +56,17 @@ export function uploadEvents({ ); }) ) - ) - .then((results) => { - const errors = results - .flatMap((result) => result.items) - .filter((item) => !!item.index?.error) - .map((item) => item.index?.error); + ).then((results) => { + const errors = results + .flatMap((result) => result.items) + .filter((item) => !!item.index?.error) + .map((item) => item.index?.error); - if (errors.length) { - logger.error(inspect(errors.slice(0, 10), { depth: null })); - throw new Error('Failed to upload some items'); - } + if (errors.length) { + logger.error(inspect(errors.slice(0, 10), { depth: null })); + throw new Error('Failed to upload some items'); + } - logger.debug(`Uploaded ${events.length} in ${new Date().getTime() - time}ms`); - }) - .catch((err) => { - // eslint-disable-next-line no-console - console.error(err); - process.exit(1); - }); + logger.debug(`Uploaded ${events.length} in ${new Date().getTime() - time}ms`); + }); } diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts index 1e0280382e4dbe..2fe5f9b6a6d610 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts @@ -11,7 +11,7 @@ import { Client } from '@elastic/elasticsearch'; import { workerData } from 'worker_threads'; import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; import { getScenario } from './get_scenario'; -import { createLogger, LogLevel } from './logger'; +import { createLogger, LogLevel } from '../../lib/utils/create_logger'; import { uploadEvents } from './upload_events'; const { bucketFrom, bucketTo, file, logLevel, target, writeTargets, clientWorkers, batchSize } = diff --git a/packages/elastic-eslint-config-kibana/package.json b/packages/elastic-eslint-config-kibana/package.json index 5fb485b86fd38c..a5007de28584c4 100644 --- a/packages/elastic-eslint-config-kibana/package.json +++ b/packages/elastic-eslint-config-kibana/package.json @@ -14,7 +14,7 @@ "author": "Spencer Alger ", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/elastic/kibana/tree/master/packages/elastic-eslint-config-kibana" + "url": "https://github.com/elastic/kibana/tree/main/packages/elastic-eslint-config-kibana" }, - "homepage": "https://github.com/elastic/kibana/tree/master/packages/elastic-eslint-config-kibana" + "homepage": "https://github.com/elastic/kibana/tree/main/packages/elastic-eslint-config-kibana" } \ No newline at end of file diff --git a/packages/elastic-safer-lodash-set/LICENSE b/packages/elastic-safer-lodash-set/LICENSE index 049225c0b6647c..ca79374b42cec0 100644 --- a/packages/elastic-safer-lodash-set/LICENSE +++ b/packages/elastic-safer-lodash-set/LICENSE @@ -12,7 +12,7 @@ individuals. For exact contribution history, see the revision history available at the following locations: - https://github.com/lodash/lodash - https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/lodash - - https://github.com/elastic/kibana/tree/master/packages/elastic-safer-lodash-set + - https://github.com/elastic/kibana/tree/main/packages/elastic-safer-lodash-set Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/packages/elastic-safer-lodash-set/package.json b/packages/elastic-safer-lodash-set/package.json index bb27fd336d975c..72b908911000d5 100644 --- a/packages/elastic-safer-lodash-set/package.json +++ b/packages/elastic-safer-lodash-set/package.json @@ -28,7 +28,7 @@ "bugs": { "url": "https://github.com/elastic/kibana/issues" }, - "homepage": "https://github.com/elastic/kibana/tree/master/packages/safer-lodash-set#readme", + "homepage": "https://github.com/elastic/kibana/tree/main/packages/safer-lodash-set#readme", "standard": { "ignore": [ "/lodash/" diff --git a/packages/kbn-babel-code-parser/package.json b/packages/kbn-babel-code-parser/package.json index a4ad8d603a0cba..7018cb3f8815f2 100755 --- a/packages/kbn-babel-code-parser/package.json +++ b/packages/kbn-babel-code-parser/package.json @@ -7,6 +7,6 @@ "license": "SSPL-1.0 OR Elastic License 2.0", "repository": { "type": "git", - "url": "https://github.com/elastic/kibana/tree/master/packages/kbn-babel-code-parser" + "url": "https://github.com/elastic/kibana/tree/main/packages/kbn-babel-code-parser" } } diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts index 9cb902882ffd76..9fa13b013f1959 100644 --- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts +++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts @@ -68,6 +68,7 @@ it('produces the right watch and ignore list', () => { /x-pack/plugins/reporting/chromium, /x-pack/plugins/security_solution/cypress, /x-pack/plugins/apm/scripts, + /x-pack/plugins/apm/ftr_e2e, /x-pack/plugins/canvas/canvas_plugin_src, /x-pack/plugins/cases/server/scripts, /x-pack/plugins/lists/scripts, diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts index 53f52279c8be81..e1bd431d280a42 100644 --- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts +++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts @@ -59,6 +59,7 @@ export function getServerWatchPaths({ pluginPaths, pluginScanDirs }: Options) { fromRoot('x-pack/plugins/reporting/chromium'), fromRoot('x-pack/plugins/security_solution/cypress'), fromRoot('x-pack/plugins/apm/scripts'), + fromRoot('x-pack/plugins/apm/ftr_e2e'), // prevents restarts for APM cypress tests fromRoot('x-pack/plugins/canvas/canvas_plugin_src'), // prevents server from restarting twice for Canvas plugin changes, fromRoot('x-pack/plugins/cases/server/scripts'), fromRoot('x-pack/plugins/lists/scripts'), diff --git a/packages/kbn-config-schema/src/duration/index.ts b/packages/kbn-config-schema/src/duration/index.ts index 6a05a00c9e8719..1b1d47a24abd35 100644 --- a/packages/kbn-config-schema/src/duration/index.ts +++ b/packages/kbn-config-schema/src/duration/index.ts @@ -7,7 +7,8 @@ */ import { Duration, duration as momentDuration, DurationInputArg2, isDuration } from 'moment'; -export { Duration, isDuration }; +export type { Duration }; +export { isDuration }; const timeFormatRegex = /^(0|[1-9][0-9]*)(ms|s|m|h|d|w|M|Y)$/; diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index 68500a18530bc6..8635421beb0a18 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -49,7 +49,8 @@ import { StreamType, } from './types'; -export { ObjectType, TypeOf, Type, Props, NullableProps }; +export type { TypeOf, Props, NullableProps }; +export { ObjectType, Type }; export { ByteSizeValue } from './byte_size_value'; export { SchemaTypeError, ValidationError } from './errors'; export { isConfigSchema } from './typeguards'; diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index d4098a2abf352c..5152137985ff31 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -6,23 +6,35 @@ * Side Public License, v 1. */ -export { Type, TypeOptions } from './type'; +export type { TypeOptions } from './type'; +export { Type } from './type'; export { AnyType } from './any_type'; -export { ArrayOptions, ArrayType } from './array_type'; +export type { ArrayOptions } from './array_type'; +export { ArrayType } from './array_type'; export { BooleanType } from './boolean_type'; export { BufferType } from './buffer_type'; -export { ByteSizeOptions, ByteSizeType } from './byte_size_type'; -export { ConditionalType, ConditionalTypeValue } from './conditional_type'; -export { DurationOptions, DurationType } from './duration_type'; +export type { ByteSizeOptions } from './byte_size_type'; +export { ByteSizeType } from './byte_size_type'; +export type { ConditionalTypeValue } from './conditional_type'; +export { ConditionalType } from './conditional_type'; +export type { DurationOptions } from './duration_type'; +export { DurationType } from './duration_type'; export { LiteralType } from './literal_type'; export { MaybeType } from './maybe_type'; -export { MapOfOptions, MapOfType } from './map_type'; -export { NumberOptions, NumberType } from './number_type'; -export { ObjectType, ObjectTypeOptions, Props, NullableProps, TypeOf } from './object_type'; -export { RecordOfOptions, RecordOfType } from './record_type'; +export type { MapOfOptions } from './map_type'; +export { MapOfType } from './map_type'; +export type { NumberOptions } from './number_type'; +export { NumberType } from './number_type'; +export type { ObjectTypeOptions, Props, NullableProps, TypeOf } from './object_type'; +export { ObjectType } from './object_type'; +export type { RecordOfOptions } from './record_type'; +export { RecordOfType } from './record_type'; export { StreamType } from './stream_type'; -export { StringOptions, StringType } from './string_type'; +export type { StringOptions } from './string_type'; +export { StringType } from './string_type'; export { UnionType } from './union_type'; -export { URIOptions, URIType } from './uri_type'; +export type { URIOptions } from './uri_type'; +export { URIType } from './uri_type'; export { NeverType } from './never_type'; -export { IpType, IpOptions } from './ip_type'; +export type { IpOptions } from './ip_type'; +export { IpType } from './ip_type'; diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index 0068fc87855b0c..272ee7598570cb 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -19,16 +19,15 @@ export type { export { applyDeprecations, configDeprecationFactory } from './deprecation'; -export { - RawConfigurationProvider, - RawConfigService, - RawConfigAdapter, - getConfigFromFiles, -} from './raw'; +export type { RawConfigurationProvider, RawConfigAdapter } from './raw'; +export { RawConfigService, getConfigFromFiles } from './raw'; -export { ConfigService, IConfigService, ConfigValidateParameters } from './config_service'; -export { Config, ConfigPath, isConfigPath, hasConfigPathIntersection } from './config'; +export type { IConfigService, ConfigValidateParameters } from './config_service'; +export { ConfigService } from './config_service'; +export type { Config, ConfigPath } from './config'; +export { isConfigPath, hasConfigPathIntersection } from './config'; export { ObjectToConfigAdapter } from './object_to_config_adapter'; -export { CliArgs, Env, RawPackageInfo } from './env'; -export { EnvironmentMode, PackageInfo } from './types'; +export type { CliArgs, RawPackageInfo } from './env'; +export { Env } from './env'; +export type { EnvironmentMode, PackageInfo } from './types'; export { getPluginSearchPaths } from './plugins'; diff --git a/packages/kbn-config/src/raw/index.ts b/packages/kbn-config/src/raw/index.ts index 01ad83728aa085..be0a206e16b66b 100644 --- a/packages/kbn-config/src/raw/index.ts +++ b/packages/kbn-config/src/raw/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ -export { RawConfigService, RawConfigurationProvider, RawConfigAdapter } from './raw_config_service'; +export type { RawConfigurationProvider, RawConfigAdapter } from './raw_config_service'; +export { RawConfigService } from './raw_config_service'; export { getConfigFromFiles } from './read_config'; diff --git a/packages/kbn-dev-utils/src/proc_runner/errors.ts b/packages/kbn-dev-utils/src/proc_runner/errors.ts deleted file mode 100644 index d4d252476b76e9..00000000000000 --- a/packages/kbn-dev-utils/src/proc_runner/errors.ts +++ /dev/null @@ -1,23 +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. - */ - -const $isCliError = Symbol('isCliError'); - -interface CliError extends Error { - [$isCliError]: boolean; -} - -export function createCliError(message: string) { - const error: Partial = new Error(message); - error[$isCliError] = true; - return error as CliError; -} - -export function isCliError(error: any): error is CliError { - return error && !!error[$isCliError]; -} diff --git a/packages/kbn-dev-utils/src/proc_runner/proc.ts b/packages/kbn-dev-utils/src/proc_runner/proc.ts index c9a520de6eb4d1..8238e294133092 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc.ts @@ -19,7 +19,7 @@ const treeKillAsync = promisify((...args: [number, string, any]) => treeKill(... import { ToolingLog } from '../tooling_log'; import { observeLines } from '../stdio'; -import { createCliError } from './errors'; +import { createFailError } from '../run'; const SECOND = 1000; const STOP_TIMEOUT = 30 * SECOND; @@ -57,7 +57,7 @@ export type Proc = ReturnType; export function startProc(name: string, options: ProcOptions, log: ToolingLog) { const { cmd, args, cwd, env, stdin } = options; - log.info('[%s] > %s', name, cmd, args.join(' ')); + log.info('[%s] > %s', name, cmd === process.execPath ? 'node' : cmd, args.join(' ')); // spawn fails with ENOENT when either the // cmd or cwd don't exist, so we check for the cwd @@ -97,7 +97,9 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { } // JVM exits with 143 on SIGTERM and 130 on SIGINT, dont' treat then as errors if (code > 0 && !(code === 143 || code === 130)) { - throw createCliError(`[${name}] exited with code ${code}`); + throw createFailError(`[${name}] exited with code ${code}`, { + exitCode: code, + }); } return code; diff --git a/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts b/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts index 8ef32411621f8d..cb2ac2604e035c 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc_runner.ts @@ -11,7 +11,7 @@ import { filter, first, catchError, map } from 'rxjs/operators'; import exitHook from 'exit-hook'; import { ToolingLog } from '../tooling_log'; -import { createCliError } from './errors'; +import { createFailError } from '../run'; import { Proc, ProcOptions, startProc } from './proc'; const SECOND = 1000; @@ -61,7 +61,6 @@ export class ProcRunner { */ async run(name: string, options: RunOptions) { const { - cmd, args = [], cwd = process.cwd(), stdin = undefined, @@ -69,6 +68,7 @@ export class ProcRunner { waitTimeout = 15 * MINUTE, env = process.env, } = options; + const cmd = options.cmd === 'node' ? process.execPath : options.cmd; if (this.closing) { throw new Error('ProcRunner is closing'); @@ -99,7 +99,7 @@ export class ProcRunner { first(), catchError((err) => { if (err.name !== 'EmptyError') { - throw createCliError(`[${name}] exited without matching pattern: ${wait}`); + throw createFailError(`[${name}] exited without matching pattern: ${wait}`); } else { throw err; } @@ -110,7 +110,7 @@ export class ProcRunner { : Rx.timer(waitTimeout).pipe( map(() => { const sec = waitTimeout / SECOND; - throw createCliError( + throw createFailError( `[${name}] failed to match pattern within ${sec} seconds [pattern=${wait}]` ); }) diff --git a/packages/kbn-dev-utils/src/tooling_log/index.ts b/packages/kbn-dev-utils/src/tooling_log/index.ts index 4da54ee9bfeaed..2c7216c87c4191 100644 --- a/packages/kbn-dev-utils/src/tooling_log/index.ts +++ b/packages/kbn-dev-utils/src/tooling_log/index.ts @@ -8,8 +8,10 @@ export { ToolingLog } from './tooling_log'; export type { ToolingLogOptions } from './tooling_log'; -export { ToolingLogTextWriter, ToolingLogTextWriterConfig } from './tooling_log_text_writer'; -export { pickLevelFromFlags, parseLogLevel, LogLevel, ParsedLogLevel } from './log_levels'; +export type { ToolingLogTextWriterConfig } from './tooling_log_text_writer'; +export { ToolingLogTextWriter } from './tooling_log_text_writer'; +export type { LogLevel, ParsedLogLevel } from './log_levels'; +export { pickLevelFromFlags, parseLogLevel } from './log_levels'; export { ToolingLogCollectingWriter } from './tooling_log_collecting_writer'; export type { Writer } from './writer'; export type { Message } from './message'; diff --git a/packages/kbn-dev-utils/src/vscode_config/managed_config_keys.ts b/packages/kbn-dev-utils/src/vscode_config/managed_config_keys.ts index 32cc91ad74c508..63a05910653af6 100644 --- a/packages/kbn-dev-utils/src/vscode_config/managed_config_keys.ts +++ b/packages/kbn-dev-utils/src/vscode_config/managed_config_keys.ts @@ -37,6 +37,8 @@ export const MANAGED_CONFIG_KEYS: ManagedConfigKey[] = [ value: { ['**/packages/kbn-pm/dist/index.js']: true, ['**/api_docs']: true, + ['**/tsconfig.tsbuildinfo']: true, + ['**/*.map']: true, }, }, { diff --git a/packages/kbn-es-archiver/src/lib/index.ts b/packages/kbn-es-archiver/src/lib/index.ts index 0e47294909add7..ee37591e1f2c30 100644 --- a/packages/kbn-es-archiver/src/lib/index.ts +++ b/packages/kbn-es-archiver/src/lib/index.ts @@ -20,7 +20,8 @@ export { export { createFilterRecordsStream } from './records'; -export { createStats, Stats } from './stats'; +export type { Stats } from './stats'; +export { createStats } from './stats'; export { isGzip, diff --git a/packages/kbn-es-query/src/es_query/index.ts b/packages/kbn-es-query/src/es_query/index.ts index 8045b625cd921a..192834c9fd485a 100644 --- a/packages/kbn-es-query/src/es_query/index.ts +++ b/packages/kbn-es-query/src/es_query/index.ts @@ -7,11 +7,12 @@ */ export { migrateFilter } from './migrate_filter'; -export { buildEsQuery, EsQueryConfig } from './build_es_query'; +export type { EsQueryConfig } from './build_es_query'; +export { buildEsQuery } from './build_es_query'; export { buildQueryFromFilters } from './from_filters'; export { luceneStringToDsl } from './lucene_string_to_dsl'; export { decorateQuery } from './decorate_query'; -export { +export type { IndexPatternBase, IndexPatternFieldBase, IFieldSubType, diff --git a/packages/kbn-es-query/src/filters/index.ts b/packages/kbn-es-query/src/filters/index.ts index 21011db9462ca1..c202892cd9c3f3 100644 --- a/packages/kbn-es-query/src/filters/index.ts +++ b/packages/kbn-es-query/src/filters/index.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ +export type { FilterCompareOptions } from './helpers'; export { dedupFilters, uniqFilters, compareFilters, COMPARE_ALL_OPTIONS, - FilterCompareOptions, cleanFilter, isFilter, isFilters, @@ -53,7 +53,7 @@ export { getFilterParams, } from './build_filters'; -export { +export type { Query, Filter, LatLon, diff --git a/packages/kbn-es-query/src/kuery/index.ts b/packages/kbn-es-query/src/kuery/index.ts index 6e03b3cb18f4cf..868904125dc44f 100644 --- a/packages/kbn-es-query/src/kuery/index.ts +++ b/packages/kbn-es-query/src/kuery/index.ts @@ -23,4 +23,4 @@ export const toElasticsearchQuery = (...params: Parameters { + const config = OptimizerConfig.create({ + includeCoreBundle: true, + repoRoot: REPO_ROOT, + }); + + const paths = config.bundles.map((b) => Path.resolve(b.outputDir, 'stats.json')); + + log.info('analyzing', paths.length, 'stats files'); + log.verbose(paths); + + const imports = new Set(); + for (const path of paths) { + const stats = parseStats(path); + + for (const module of stats.modules) { + if (!inAnyEntryChunk(stats, module)) { + continue; + } + + for (const { userRequest } of module.reasons) { + if (userRequest.startsWith('@babel/runtime/')) { + imports.add(userRequest); + } + } + } + } + + log.success('found', imports.size, '@babel/register imports in entry bundles'); + log.write( + Array.from(imports, (i) => `'${i}',`) + .sort() + .join('\n') + ); + }); +} diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts new file mode 100644 index 00000000000000..58a3ddf263a1d2 --- /dev/null +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/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 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 './find_babel_runtime_helpers_in_entry_bundles'; diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts new file mode 100644 index 00000000000000..fac0b099b5195d --- /dev/null +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 Fs from 'fs'; + +import dedent from 'dedent'; +import { schema, Props, TypeOf } from '@kbn/config-schema'; + +const partialObject =

(props: P) => { + return schema.object(props, { + unknowns: 'ignore', + }); +}; + +export type Module = TypeOf; +const moduleSchema = partialObject({ + identifier: schema.string(), + chunks: schema.arrayOf(schema.oneOf([schema.string(), schema.number()])), + reasons: schema.arrayOf( + partialObject({ + userRequest: schema.string(), + }) + ), +}); + +export type Chunk = TypeOf; +const chunkSchema = partialObject({ + id: schema.oneOf([schema.string(), schema.number()]), + entry: schema.boolean(), + initial: schema.boolean(), +}); + +const statsSchema = partialObject({ + chunks: schema.arrayOf(chunkSchema), + modules: schema.arrayOf(moduleSchema), +}); + +export interface Stats { + path: string; + modules: Module[]; + chunks: Chunk[]; +} +export function parseStats(path: string): Stats { + try { + return { + path, + ...statsSchema.validate(JSON.parse(Fs.readFileSync(path, 'utf-8'))), + }; + } catch (error) { + if (error.code === 'ENOENT') { + throw new Error(dedent` + unable to find stats file at [${path}]. Make sure you run the following + before running this script: + + node scripts/build_kibana_platform_plugins --dist --profile + `); + } + + throw error; + } +} + +export function inAnyEntryChunk(stats: Stats, module: Module): boolean { + return module.chunks.some((id) => { + const chunk = stats.chunks.find((c) => c.id === id); + if (!chunk) { + throw new Error( + `unable to find chunk ${id} for module ${module.identifier} in ${stats.path}` + ); + } + + return chunk.entry || chunk.initial; + }); +} diff --git a/packages/kbn-optimizer/src/common/theme_tags.test.ts b/packages/kbn-optimizer/src/common/theme_tags.test.ts index 126d1b18338739..bbc7f2831e83f0 100644 --- a/packages/kbn-optimizer/src/common/theme_tags.test.ts +++ b/packages/kbn-optimizer/src/common/theme_tags.test.ts @@ -20,8 +20,6 @@ it('returns default tags when passed undefined', () => { it('returns all tags when passed *', () => { expect(parseThemeTags('*')).toMatchInlineSnapshot(` Array [ - "v7dark", - "v7light", "v8dark", "v8light", ] @@ -37,38 +35,37 @@ it('returns specific tag when passed a single value', () => { }); it('returns specific tags when passed a comma separated list', () => { - expect(parseThemeTags('v8light, v7dark,v7light')).toMatchInlineSnapshot(` + expect(parseThemeTags('v8light,v8dark')).toMatchInlineSnapshot(` Array [ - "v7dark", - "v7light", + "v8dark", "v8light", ] `); }); it('returns specific tags when passed an array', () => { - expect(parseThemeTags(['v8light', 'v7light'])).toMatchInlineSnapshot(` + expect(parseThemeTags(['v8light', 'v8dark'])).toMatchInlineSnapshot(` Array [ - "v7light", + "v8dark", "v8light", ] `); }); it('throws when an invalid tag is in the array', () => { - expect(() => parseThemeTags(['v8light', 'v7light', 'bar'])).toThrowErrorMatchingInlineSnapshot( - `"Invalid theme tags [bar], options: [v7dark, v7light, v8dark, v8light]"` + expect(() => parseThemeTags(['v8light', 'v7light'])).toThrowErrorMatchingInlineSnapshot( + `"Invalid theme tags [v7light], options: [v8dark, v8light]"` ); }); it('throws when an invalid tags in comma separated list', () => { - expect(() => parseThemeTags('v8light ,v7light,bar,box ')).toThrowErrorMatchingInlineSnapshot( - `"Invalid theme tags [bar, box], options: [v7dark, v7light, v8dark, v8light]"` + expect(() => parseThemeTags('v8light ,v7light')).toThrowErrorMatchingInlineSnapshot( + `"Invalid theme tags [v7light], options: [v8dark, v8light]"` ); }); it('returns tags in alphabetical order', () => { - const tags = parseThemeTags(['v7light', 'v8light']); + const tags = parseThemeTags(['v8dark', 'v8light']); expect(tags).toEqual(tags.slice().sort((a, b) => a.localeCompare(b))); }); diff --git a/packages/kbn-optimizer/src/common/theme_tags.ts b/packages/kbn-optimizer/src/common/theme_tags.ts index de95bbdcbcfea8..15366c1d0fbbac 100644 --- a/packages/kbn-optimizer/src/common/theme_tags.ts +++ b/packages/kbn-optimizer/src/common/theme_tags.ts @@ -16,9 +16,9 @@ const isArrayOfStrings = (input: unknown): input is string[] => Array.isArray(input) && input.every((v) => typeof v === 'string'); export type ThemeTags = readonly ThemeTag[]; -export type ThemeTag = 'v7light' | 'v7dark' | 'v8light' | 'v8dark'; +export type ThemeTag = 'v8light' | 'v8dark'; export const DEFAULT_THEMES = tags('v8light', 'v8dark'); -export const ALL_THEMES = tags('v7light', 'v7dark', 'v8light', 'v8dark'); +export const ALL_THEMES = tags('v8light', 'v8dark'); export function parseThemeTags(input?: any): ThemeTags { if (!input) { diff --git a/packages/kbn-optimizer/src/index.ts b/packages/kbn-optimizer/src/index.ts index d5e810d584d29f..d759a4aa02455d 100644 --- a/packages/kbn-optimizer/src/index.ts +++ b/packages/kbn-optimizer/src/index.ts @@ -14,3 +14,4 @@ export * from './node'; export * from './limits'; export * from './cli'; export * from './report_optimizer_timings'; +export * from './babel_runtime_helpers'; diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 8067ee2ce6c0b5..3ca65769a18e19 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -156,7 +156,7 @@ OptimizerConfig { } `; -exports[`prepares assets for distribution: bar bundle 1`] = `"!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){\\"undefined\\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\\"Module\\"}),Object.defineProperty(e,\\"__esModule\\",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&\\"object\\"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,\\"default\\",{enumerable:!0,value:e}),2&n&&\\"string\\"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,\\"a\\",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p=\\"\\",t(t.s=3)}([function(e,n,t){\\"use strict\\";var r,o=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},i=function(){var e={};return function(n){if(void 0===e[n]){var t=document.querySelector(n);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(e){t=null}e[n]=t}return e[n]}}(),a=[];function c(e){for(var n=-1,t=0;t (tag.includes('v7') ? 7 : 8); +const getVersion = (tag: ThemeTag) => 8; const getIsDark = (tag: ThemeTag) => tag.includes('dark'); const compare = (a: ThemeTag, b: ThemeTag) => (getVersion(a) === getVersion(b) ? 1 : 0) + (getIsDark(a) === getIsDark(b) ? 1 : 0); diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 1c16d2f1f77daa..3e46f5a768d87b 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -73,6 +73,10 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: new BundleRefsPlugin(bundle, bundleRefs), new PopulateBundleCachePlugin(worker, bundle), new BundleMetricsPlugin(bundle), + new webpack.DllReferencePlugin({ + context: worker.repoRoot, + manifest: require(UiSharedDepsNpm.dllManifestPath), + }), ...(worker.profileWebpack ? [new EmitStatsPlugin(bundle)] : []), ...(bundle.banner ? [new webpack.BannerPlugin({ banner: bundle.banner, raw: true })] : []), ], @@ -261,10 +265,6 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: test: /\.(js|css)$/, cache: false, }), - new webpack.DllReferencePlugin({ - context: worker.repoRoot, - manifest: require(UiSharedDepsNpm.dllManifestPath), - }), ], optimization: { diff --git a/packages/kbn-plugin-generator/template/README.md.ejs b/packages/kbn-plugin-generator/template/README.md.ejs index 2cd19c904263e1..5627b5d19c5318 100755 --- a/packages/kbn-plugin-generator/template/README.md.ejs +++ b/packages/kbn-plugin-generator/template/README.md.ejs @@ -6,7 +6,7 @@ A Kibana plugin ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. <% if (thirdPartyPlugin) { %> ## Scripts diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index f70c5145516f87..d2c067759e25f4 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -548,12 +548,6 @@ Object.defineProperty(exports, "ToolingLogTextWriter", { return _tooling_log_text_writer.ToolingLogTextWriter; } }); -Object.defineProperty(exports, "ToolingLogTextWriterConfig", { - enumerable: true, - get: function () { - return _tooling_log_text_writer.ToolingLogTextWriterConfig; - } -}); Object.defineProperty(exports, "pickLevelFromFlags", { enumerable: true, get: function () { @@ -566,18 +560,6 @@ Object.defineProperty(exports, "parseLogLevel", { return _log_levels.parseLogLevel; } }); -Object.defineProperty(exports, "LogLevel", { - enumerable: true, - get: function () { - return _log_levels.LogLevel; - } -}); -Object.defineProperty(exports, "ParsedLogLevel", { - enumerable: true, - get: function () { - return _log_levels.ParsedLogLevel; - } -}); Object.defineProperty(exports, "ToolingLogCollectingWriter", { enumerable: true, get: function () { @@ -15580,8 +15562,6 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Log", function() { return Log; }); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_0__); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "LogLevel", function() { return _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_0__["LogLevel"]; }); - function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* @@ -22197,8 +22177,128 @@ module.exports = function globParent(str, opts) { var isExtglob = __webpack_require__(267); var chars = { '{': '}', '(': ')', '[': ']'}; -var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; -var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; +var strictCheck = function(str) { + if (str[0] === '!') { + return true; + } + var index = 0; + var pipeIndex = -2; + var closeSquareIndex = -2; + var closeCurlyIndex = -2; + var closeParenIndex = -2; + var backSlashIndex = -2; + while (index < str.length) { + if (str[index] === '*') { + return true; + } + + if (str[index + 1] === '?' && /[\].+)]/.test(str[index])) { + return true; + } + + if (closeSquareIndex !== -1 && str[index] === '[' && str[index + 1] !== ']') { + if (closeSquareIndex < index) { + closeSquareIndex = str.indexOf(']', index); + } + if (closeSquareIndex > index) { + if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) { + return true; + } + backSlashIndex = str.indexOf('\\', index); + if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) { + return true; + } + } + } + + if (closeCurlyIndex !== -1 && str[index] === '{' && str[index + 1] !== '}') { + closeCurlyIndex = str.indexOf('}', index); + if (closeCurlyIndex > index) { + backSlashIndex = str.indexOf('\\', index); + if (backSlashIndex === -1 || backSlashIndex > closeCurlyIndex) { + return true; + } + } + } + + if (closeParenIndex !== -1 && str[index] === '(' && str[index + 1] === '?' && /[:!=]/.test(str[index + 2]) && str[index + 3] !== ')') { + closeParenIndex = str.indexOf(')', index); + if (closeParenIndex > index) { + backSlashIndex = str.indexOf('\\', index); + if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) { + return true; + } + } + } + + if (pipeIndex !== -1 && str[index] === '(' && str[index + 1] !== '|') { + if (pipeIndex < index) { + pipeIndex = str.indexOf('|', index); + } + if (pipeIndex !== -1 && str[pipeIndex + 1] !== ')') { + closeParenIndex = str.indexOf(')', pipeIndex); + if (closeParenIndex > pipeIndex) { + backSlashIndex = str.indexOf('\\', pipeIndex); + if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) { + return true; + } + } + } + } + + if (str[index] === '\\') { + var open = str[index + 1]; + index += 2; + var close = chars[open]; + + if (close) { + var n = str.indexOf(close, index); + if (n !== -1) { + index = n + 1; + } + } + + if (str[index] === '!') { + return true; + } + } else { + index++; + } + } + return false; +}; + +var relaxedCheck = function(str) { + if (str[0] === '!') { + return true; + } + var index = 0; + while (index < str.length) { + if (/[*?{}()[\]]/.test(str[index])) { + return true; + } + + if (str[index] === '\\') { + var open = str[index + 1]; + index += 2; + var close = chars[open]; + + if (close) { + var n = str.indexOf(close, index); + if (n !== -1) { + index = n + 1; + } + } + + if (str[index] === '!') { + return true; + } + } else { + index++; + } + } + return false; +}; module.exports = function isGlob(str, options) { if (typeof str !== 'string' || str === '') { @@ -22209,32 +22309,14 @@ module.exports = function isGlob(str, options) { return true; } - var regex = strictRegex; - var match; + var check = strictCheck; - // optionally relax regex + // optionally relax check if (options && options.strict === false) { - regex = relaxedRegex; + check = relaxedCheck; } - while ((match = regex.exec(str))) { - if (match[2]) return true; - var idx = match.index + match[0].length; - - // if an open bracket/brace/paren is escaped, - // set the index to the next closing character - var open = match[1]; - var close = open ? chars[open] : null; - if (open && close) { - var n = str.indexOf(close, idx); - if (n !== -1) { - idx = n + 1; - } - } - - str = str.slice(idx); - } - return false; + return check(str); }; diff --git a/packages/kbn-pm/src/utils/log.ts b/packages/kbn-pm/src/utils/log.ts index 9dbfad2895793e..ba4ee6941f5402 100644 --- a/packages/kbn-pm/src/utils/log.ts +++ b/packages/kbn-pm/src/utils/log.ts @@ -38,4 +38,5 @@ class Log extends ToolingLog { } export const log = new Log(); -export { LogLevel, Log }; +export type { LogLevel }; +export { Log }; diff --git a/packages/kbn-securitysolution-autocomplete/src/field/index.tsx b/packages/kbn-securitysolution-autocomplete/src/field/index.tsx index 69408e919bb1e1..a89e0a096b673a 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field/index.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field/index.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { getGenericComboBoxProps, @@ -20,14 +20,14 @@ const AS_PLAIN_TEXT = { asPlainText: true }; interface OperatorProps { fieldInputWidth?: number; fieldTypeFilter?: string[]; - indexPattern: IndexPatternBase | undefined; + indexPattern: DataViewBase | undefined; isClearable: boolean; isDisabled: boolean; isLoading: boolean; isRequired?: boolean; - onChange: (a: IndexPatternFieldBase[]) => void; + onChange: (a: DataViewFieldBase[]) => void; placeholder: string; - selectedField: IndexPatternFieldBase | undefined; + selectedField: DataViewFieldBase | undefined; } export const FieldComponent: React.FC = ({ @@ -56,7 +56,7 @@ export const FieldComponent: React.FC = ({ const handleValuesChange = useCallback( (newOptions: EuiComboBoxOptionOption[]): void => { - const newValues: IndexPatternFieldBase[] = newOptions.map( + const newValues: DataViewFieldBase[] = newOptions.map( ({ label }) => availableFields[labels.indexOf(label)] ); onChange(newValues); @@ -94,13 +94,13 @@ export const FieldComponent: React.FC = ({ FieldComponent.displayName = 'Field'; interface ComboBoxFields { - availableFields: IndexPatternFieldBase[]; - selectedFields: IndexPatternFieldBase[]; + availableFields: DataViewFieldBase[]; + selectedFields: DataViewFieldBase[]; } const getComboBoxFields = ( - indexPattern: IndexPatternBase | undefined, - selectedField: IndexPatternFieldBase | undefined, + indexPattern: DataViewBase | undefined, + selectedField: DataViewFieldBase | undefined, fieldTypeFilter: string[] ): ComboBoxFields => { const existingFields = getExistingFields(indexPattern); @@ -113,29 +113,27 @@ const getComboBoxFields = ( const getComboBoxProps = (fields: ComboBoxFields): GetGenericComboBoxPropsReturn => { const { availableFields, selectedFields } = fields; - return getGenericComboBoxProps({ + return getGenericComboBoxProps({ getLabel: (field) => field.name, options: availableFields, selectedOptions: selectedFields, }); }; -const getExistingFields = (indexPattern: IndexPatternBase | undefined): IndexPatternFieldBase[] => { +const getExistingFields = (indexPattern: DataViewBase | undefined): DataViewFieldBase[] => { return indexPattern != null ? indexPattern.fields : []; }; -const getSelectedFields = ( - selectedField: IndexPatternFieldBase | undefined -): IndexPatternFieldBase[] => { +const getSelectedFields = (selectedField: DataViewFieldBase | undefined): DataViewFieldBase[] => { return selectedField ? [selectedField] : []; }; const getAvailableFields = ( - existingFields: IndexPatternFieldBase[], - selectedFields: IndexPatternFieldBase[], + existingFields: DataViewFieldBase[], + selectedFields: DataViewFieldBase[], fieldTypeFilter: string[] -): IndexPatternFieldBase[] => { - const fieldsByName = new Map(); +): DataViewFieldBase[] => { + const fieldsByName = new Map(); existingFields.forEach((f) => fieldsByName.set(f.name, f)); selectedFields.forEach((f) => fieldsByName.set(f.name, f)); diff --git a/packages/kbn-securitysolution-es-utils/src/get_bootstrap_index_exists/index.ts b/packages/kbn-securitysolution-es-utils/src/get_bootstrap_index_exists/index.ts new file mode 100644 index 00000000000000..959e38b3f7ee4c --- /dev/null +++ b/packages/kbn-securitysolution-es-utils/src/get_bootstrap_index_exists/index.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 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 { ElasticsearchClient } from '../elasticsearch_client'; + +/** + * This function is similar to getIndexExists, but is limited to searching indices that match + * the index pattern used as concrete backing indices (e.g. .siem-signals-default-000001). + * This allows us to separate the indices that are actually .siem-signals indices from + * alerts as data indices that only share the .siem-signals alias. + * + * @param esClient Elasticsearch client to use to make the request + * @param index Index alias name to check for existence + */ +export const getBootstrapIndexExists = async ( + esClient: ElasticsearchClient, + index: string +): Promise => { + try { + const { body } = await esClient.indices.getAlias({ + index: `${index}-*`, + name: index, + }); + return Object.keys(body).length > 0; + } catch (err) { + if (err.body != null && err.body.status === 404) { + return false; + } else { + throw err.body ? err.body : err; + } + } +}; diff --git a/packages/kbn-securitysolution-es-utils/src/index.ts b/packages/kbn-securitysolution-es-utils/src/index.ts index f4b081ac1b0a00..a32cf8968967c8 100644 --- a/packages/kbn-securitysolution-es-utils/src/index.ts +++ b/packages/kbn-securitysolution-es-utils/src/index.ts @@ -13,6 +13,7 @@ export * from './delete_all_index'; export * from './delete_policy'; export * from './delete_template'; export * from './encode_hit_version'; +export * from './get_bootstrap_index_exists'; export * from './get_index_aliases'; export * from './get_index_count'; export * from './get_index_exists'; diff --git a/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts index 886a3dd27befcb..7503f2c5c2be81 100644 --- a/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts +++ b/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts @@ -18,6 +18,13 @@ interface TestArgs { type TestReturn = Promise; describe('useAsync', () => { + /** + * Timeout for both jest tests and for the waitForNextUpdate. + * jest tests default to 5 seconds and waitForNextUpdate defaults to 1 second. + * 20_0000 = 20,000 milliseconds = 20 seconds + */ + const timeout = 20_000; + let fn: jest.Mock; let args: TestArgs; @@ -31,16 +38,20 @@ describe('useAsync', () => { expect(fn).not.toHaveBeenCalled(); }); - it('invokes the function when start is called', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + it( + 'invokes the function when start is called', + async () => { + const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); - act(() => { - result.current.start(args); - }); - await waitForNextUpdate(); + act(() => { + result.current.start(args); + }); + await waitForNextUpdate({ timeout }); - expect(fn).toHaveBeenCalled(); - }); + expect(fn).toHaveBeenCalled(); + }, + timeout + ); it('invokes the function with start args', async () => { const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); @@ -49,84 +60,99 @@ describe('useAsync', () => { act(() => { result.current.start(args); }); - await waitForNextUpdate(); + await waitForNextUpdate({ timeout }); expect(fn).toHaveBeenCalledWith(expectedArgs); }); - it('populates result with the resolved value of the fn', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); - fn.mockResolvedValue({ resolved: 'value' }); - - act(() => { - result.current.start(args); - }); - await waitForNextUpdate(); - - expect(result.current.result).toEqual({ resolved: 'value' }); - expect(result.current.error).toBeUndefined(); - }); - - it('populates error if function rejects', async () => { - fn.mockRejectedValue(new Error('whoops')); - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); - - act(() => { - result.current.start(args); - }); - await waitForNextUpdate(); - - expect(result.current.result).toBeUndefined(); - expect(result.current.error).toEqual(new Error('whoops')); - }); - - it('populates the loading state while the function is pending', async () => { - let resolve: () => void; - fn.mockImplementation(() => new Promise((_resolve) => (resolve = _resolve))); - - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); - - act(() => { - result.current.start(args); - }); - - expect(result.current.loading).toBe(true); - - act(() => resolve()); - await waitForNextUpdate(); - - expect(result.current.loading).toBe(false); - }); - - it('multiple start calls reset state', async () => { - let resolve: (result: string) => void; - fn.mockImplementation(() => new Promise((_resolve) => (resolve = _resolve))); - - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); - - act(() => { - result.current.start(args); - }); - - expect(result.current.loading).toBe(true); - - act(() => resolve('result')); - await waitForNextUpdate(); - - expect(result.current.loading).toBe(false); - expect(result.current.result).toBe('result'); - - act(() => { - result.current.start(args); - }); - - expect(result.current.loading).toBe(true); - expect(result.current.result).toBe(undefined); - - act(() => resolve('result')); - await waitForNextUpdate(); - - expect(result.current.loading).toBe(false); - expect(result.current.result).toBe('result'); - }); + it( + 'populates result with the resolved value of the fn', + async () => { + const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + fn.mockResolvedValue({ resolved: 'value' }); + + act(() => { + result.current.start(args); + }); + await waitForNextUpdate({ timeout }); + + expect(result.current.result).toEqual({ resolved: 'value' }); + expect(result.current.error).toBeUndefined(); + }, + timeout + ); + + it( + 'populates error if function rejects', + async () => { + fn.mockRejectedValue(new Error('whoops')); + const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + + act(() => { + result.current.start(args); + }); + await waitForNextUpdate({ timeout }); + + expect(result.current.result).toBeUndefined(); + expect(result.current.error).toEqual(new Error('whoops')); + }, + timeout + ); + + it( + 'populates the loading state while the function is pending', + async () => { + let resolve: () => void; + fn.mockImplementation(() => new Promise((_resolve) => (resolve = _resolve))); + + const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + + act(() => { + result.current.start(args); + }); + + expect(result.current.loading).toBe(true); + + act(() => resolve()); + await waitForNextUpdate({ timeout }); + + expect(result.current.loading).toBe(false); + }, + timeout + ); + + it( + 'multiple start calls reset state', + async () => { + let resolve: (result: string) => void; + fn.mockImplementation(() => new Promise((_resolve) => (resolve = _resolve))); + + const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + + act(() => { + result.current.start(args); + }); + + expect(result.current.loading).toBe(true); + + act(() => resolve('result')); + await waitForNextUpdate({ timeout }); + + expect(result.current.loading).toBe(false); + expect(result.current.result).toBe('result'); + + act(() => { + result.current.start(args); + }); + + expect(result.current.loading).toBe(true); + expect(result.current.result).toBe(undefined); + act(() => resolve('result')); + await waitForNextUpdate({ timeout }); + + expect(result.current.loading).toBe(false); + expect(result.current.result).toBe('result'); + }, + timeout + ); }); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.mock.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.mock.ts new file mode 100644 index 00000000000000..703c26ffc3ff16 --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.mock.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 { ExportExceptionDetails } from '.'; + +export interface ExportExceptionDetailsMock { + listCount?: number; + missingListsCount?: number; + missingLists?: Array>; + itemCount?: number; + missingItemCount?: number; + missingItems?: Array>; +} + +export const getExceptionExportDetailsMock = ( + details?: ExportExceptionDetailsMock +): ExportExceptionDetails => ({ + exported_exception_list_count: details?.listCount ?? 0, + exported_exception_list_item_count: details?.itemCount ?? 0, + missing_exception_list_item_count: details?.missingItemCount ?? 0, + missing_exception_list_items: details?.missingItems ?? [], + missing_exception_lists: details?.missingLists ?? [], + missing_exception_lists_count: details?.missingListsCount ?? 0, +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.test.ts new file mode 100644 index 00000000000000..96e25a024c71b5 --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.test.ts @@ -0,0 +1,36 @@ +/* + * 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 { getExceptionExportDetailsMock } from './index.mock'; +import { exportExceptionDetailsSchema, ExportExceptionDetails } from '.'; +import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +describe('exportExceptionDetails', () => { + test('it should validate export meta', () => { + const payload = getExceptionExportDetailsMock(); + const decoded = exportExceptionDetailsSchema.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should strip out extra keys', () => { + const payload: ExportExceptionDetails & { + extraKey?: string; + } = getExceptionExportDetailsMock(); + payload.extraKey = 'some extra key'; + const decoded = exportExceptionDetailsSchema.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(getExceptionExportDetailsMock()); + }); +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.ts new file mode 100644 index 00000000000000..3617ae8c9b8b42 --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_export_details/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; + +export const exportExceptionDetails = { + exported_exception_list_count: t.number, + exported_exception_list_item_count: t.number, + missing_exception_list_item_count: t.number, + missing_exception_list_items: t.array( + t.exact( + t.type({ + item_id: NonEmptyString, + }) + ) + ), + missing_exception_lists: t.array( + t.exact( + t.type({ + list_id: NonEmptyString, + }) + ) + ), + missing_exception_lists_count: t.number, +}; + +export const exportExceptionDetailsSchema = t.exact(t.type(exportExceptionDetails)); + +export type ExportExceptionDetails = 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 51b32f3fafa77c..81ecd58cb397c9 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 @@ -23,6 +23,7 @@ export * from './entry_match'; export * from './entry_match_any'; export * from './entry_match_wildcard'; export * from './entry_nested'; +export * from './exception_export_details'; export * from './exception_list'; export * from './exception_list_item_type'; export * from './filter'; diff --git a/packages/kbn-server-route-repository/src/index.ts b/packages/kbn-server-route-repository/src/index.ts index 23621c5b213bcf..1cc7bd0fdeebe2 100644 --- a/packages/kbn-server-route-repository/src/index.ts +++ b/packages/kbn-server-route-repository/src/index.ts @@ -12,7 +12,7 @@ export { formatRequest } from './format_request'; export { parseEndpoint } from './parse_endpoint'; export { decodeRequestParams } from './decode_request_params'; export { routeValidationObject } from './route_validation_object'; -export { +export type { RouteRepositoryClient, ReturnOf, EndpointOf, diff --git a/packages/kbn-spec-to-console/README.md b/packages/kbn-spec-to-console/README.md index be0ada5e38850f..20a5ee855f7f67 100644 --- a/packages/kbn-spec-to-console/README.md +++ b/packages/kbn-spec-to-console/README.md @@ -31,6 +31,6 @@ yarn spec_to_console -g "/rest-api-spec/src/main/reso * Ad hoc additions ### Updating the script -When converting query params defined in the REST API specs to console autocompletion definitions, the script relies on a set of known conversion rules specified in [lib/convert/params.js](https://github.com/elastic/kibana/blob/master/packages/kbn-spec-to-console/lib/convert/params.js). +When converting query params defined in the REST API specs to console autocompletion definitions, the script relies on a set of known conversion rules specified in [lib/convert/params.js](https://github.com/elastic/kibana/blob/main/packages/kbn-spec-to-console/lib/convert/params.js). For example, `"keep_on_completion":{"type":"boolean"}` from REST API specs is converted to `"keep_on_completion": "__flag__"` in console autocomplete definitions. -When an unknown parameter type is encountered in REST API specs, the script will throw an `Unexpected type error` and the file [lib/convert/params.js](https://github.com/elastic/kibana/blob/master/packages/kbn-spec-to-console/lib/convert/params.js) needs to be updated by adding a new conversion rule. \ No newline at end of file +When an unknown parameter type is encountered in REST API specs, the script will throw an `Unexpected type error` and the file [lib/convert/params.js](https://github.com/elastic/kibana/blob/main/packages/kbn-spec-to-console/lib/convert/params.js) needs to be updated by adding a new conversion rule. \ No newline at end of file diff --git a/packages/kbn-std/src/index.ts b/packages/kbn-std/src/index.ts index 33b40c20039f2c..66fb98888444ff 100644 --- a/packages/kbn-std/src/index.ts +++ b/packages/kbn-std/src/index.ts @@ -7,13 +7,15 @@ */ export { assertNever } from './assert_never'; -export { deepFreeze, Freezable } from './deep_freeze'; +export type { Freezable } from './deep_freeze'; +export { deepFreeze } from './deep_freeze'; export { get } from './get'; export { mapToObject } from './map_to_object'; export { merge } from './merge'; export { pick } from './pick'; export { withTimeout, isPromise } from './promise'; -export { isRelativeUrl, modifyUrl, getUrlOrigin, URLMeaningfulParts } from './url'; +export type { URLMeaningfulParts } from './url'; +export { isRelativeUrl, modifyUrl, getUrlOrigin } from './url'; export { unset } from './unset'; export { getFlattenedObject } from './get_flattened_object'; export { ensureNoUnsafeProperties } from './ensure_no_unsafe_properties'; diff --git a/packages/kbn-storybook/src/lib/register.ts b/packages/kbn-storybook/src/lib/register.ts index 1e49606323422f..f102e43c8c0425 100644 --- a/packages/kbn-storybook/src/lib/register.ts +++ b/packages/kbn-storybook/src/lib/register.ts @@ -16,7 +16,7 @@ addons.setConfig({ theme: create({ base: 'light', brandTitle: 'Kibana Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/master/packages/kbn-storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/main/packages/kbn-storybook', }), showPanel: false, isFullscreen: false, diff --git a/packages/kbn-storybook/src/lib/theme_switcher.tsx b/packages/kbn-storybook/src/lib/theme_switcher.tsx index 24ddec1fdf51cd..fd3e36b4cc3062 100644 --- a/packages/kbn-storybook/src/lib/theme_switcher.tsx +++ b/packages/kbn-storybook/src/lib/theme_switcher.tsx @@ -25,14 +25,12 @@ export function ThemeSwitcher() { const links: Link[] = [ { id: 'v8.light', - title: 'Amsterdam: Light', + title: 'Light', }, { id: 'v8.dark', - title: 'Amsterdam: Dark', + title: 'Dark', }, - { id: 'v7.light', title: 'Light' }, - { id: 'v7.dark', title: 'Dark' }, ].map((link) => ({ ...link, onClick: (_event, item) => { diff --git a/packages/kbn-storybook/src/webpack.config.ts b/packages/kbn-storybook/src/webpack.config.ts index f77207b8033554..27e887eda65ce3 100644 --- a/packages/kbn-storybook/src/webpack.config.ts +++ b/packages/kbn-storybook/src/webpack.config.ts @@ -58,7 +58,7 @@ export default function ({ config: storybookConfig }: { config: Configuration }) additionalData(content: string, loaderContext: any) { return `@import ${stringifyRequest( loaderContext, - resolve(REPO_ROOT, 'src/core/public/core_app/styles/_globals_v7light.scss') + resolve(REPO_ROOT, 'src/core/public/core_app/styles/_globals_v8light.scss') )};\n${content}`; }, implementation: require('node-sass'), diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts index 80fe8340e6a11b..193bc668ce0033 100644 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -51,9 +51,9 @@ export function runFailedTestsReporterCli() { branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH || ''; isPr = !!process.env.ghprbPullId; - const isMasterOrVersion = branch === 'master' || branch.match(/^\d+\.(x|\d+)$/); - if (!isMasterOrVersion || isPr) { - log.info('Failure issues only created on master/version branch jobs'); + const isMainOrVersion = branch === 'main' || branch.match(/^\d+\.(x|\d+)$/); + if (!isMainOrVersion || isPr) { + log.info('Failure issues only created on main/version branch jobs'); updateGithub = false; } } diff --git a/packages/kbn-test/src/functional_test_runner/public_types.ts b/packages/kbn-test/src/functional_test_runner/public_types.ts index d94f61e23b8b80..d1a0f7998b0a98 100644 --- a/packages/kbn-test/src/functional_test_runner/public_types.ts +++ b/packages/kbn-test/src/functional_test_runner/public_types.ts @@ -103,4 +103,4 @@ export interface FtrConfigProviderContext { readConfigFile(path: string): Promise; } -export { Test, Suite }; +export type { Test, Suite }; diff --git a/packages/kbn-test/src/functional_tests/lib/index.ts b/packages/kbn-test/src/functional_tests/lib/index.ts index 93700a692dc816..bf2cc431595269 100644 --- a/packages/kbn-test/src/functional_tests/lib/index.ts +++ b/packages/kbn-test/src/functional_tests/lib/index.ts @@ -8,6 +8,7 @@ export { runKibanaServer } from './run_kibana_server'; export { runElasticsearch } from './run_elasticsearch'; -export { runFtr, hasTests, assertNoneExcluded, CreateFtrOptions, CreateFtrParams } from './run_ftr'; +export type { CreateFtrOptions, CreateFtrParams } from './run_ftr'; +export { runFtr, hasTests, assertNoneExcluded } from './run_ftr'; export { KIBANA_ROOT, KIBANA_FTR_SCRIPT, FUNCTIONAL_CONFIG_PATH, API_CONFIG_PATH } from './paths'; export { runCli } from './run_cli'; diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 0ef9fbfed07a0a..29e7e775ec1718 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -25,14 +25,8 @@ export { runTests, startServers } from './functional_tests/tasks'; // @internal export { KIBANA_ROOT } from './functional_tests/lib/paths'; -export { - esTestConfig, - createTestEsCluster, - CreateTestEsClusterOptions, - EsTestCluster, - ICluster, - convertToKibanaClient, -} from './es'; +export type { CreateTestEsClusterOptions, EsTestCluster, ICluster } from './es'; +export { esTestConfig, createTestEsCluster, convertToKibanaClient } from './es'; export { kbnTestConfig, kibanaServerTestUser, kibanaTestUser, adminTestUser } from './kbn'; diff --git a/packages/kbn-test/src/jest/utils/testbed/index.ts b/packages/kbn-test/src/jest/utils/testbed/index.ts index 7abd4f000ab587..dfa5f011853c05 100644 --- a/packages/kbn-test/src/jest/utils/testbed/index.ts +++ b/packages/kbn-test/src/jest/utils/testbed/index.ts @@ -7,4 +7,4 @@ */ export { registerTestBed } from './testbed'; -export { TestBed, TestBedConfig, SetupFunc, UnwrapPromise } from './types'; +export type { TestBed, TestBedConfig, SetupFunc, UnwrapPromise } from './types'; diff --git a/packages/kbn-ui-shared-deps-npm/src/index.js b/packages/kbn-ui-shared-deps-npm/src/index.js index 435371049e46be..678e7c845e6a5e 100644 --- a/packages/kbn-ui-shared-deps-npm/src/index.js +++ b/packages/kbn-ui-shared-deps-npm/src/index.js @@ -6,6 +6,10 @@ * Side Public License, v 1. */ +/** + * @typedef {'v8'} ThemeVersion + */ + const Path = require('path'); /** @@ -25,23 +29,27 @@ exports.dllFilename = 'kbn-ui-shared-deps-npm.dll.js'; /** * Filename of the light-theme css file in the distributable directory + * @param {ThemeVersion} themeVersion */ -exports.lightCssDistFilename = 'kbn-ui-shared-deps-npm.v7.light.css'; +exports.lightCssDistFilename = (themeVersion) => { + if (themeVersion !== 'v8') { + throw new Error(`unsupported theme version [${themeVersion}]`); + } -/** - * Filename of the light-theme css file in the distributable directory - */ -exports.lightV8CssDistFilename = 'kbn-ui-shared-deps-npm.v8.light.css'; + return 'kbn-ui-shared-deps-npm.v8.light.css'; +}; /** * Filename of the dark-theme css file in the distributable directory + * @param {ThemeVersion} themeVersion */ -exports.darkCssDistFilename = 'kbn-ui-shared-deps-npm.v7.dark.css'; +exports.darkCssDistFilename = (themeVersion) => { + if (themeVersion !== 'v8') { + throw new Error(`unsupported theme version [${themeVersion}]`); + } -/** - * Filename of the dark-theme css file in the distributable directory - */ -exports.darkV8CssDistFilename = 'kbn-ui-shared-deps-npm.v8.dark.css'; + return 'kbn-ui-shared-deps-npm.v8.dark.css'; +}; /** * Webpack loader for configuring the public path lookup from `window.__kbnPublicPath__`. diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 6b572dc3ea208d..80043bd0857eee 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -38,14 +38,37 @@ module.exports = (_, argv) => { 'whatwg-fetch', 'symbol-observable', + /** + * babel runtime helpers referenced from entry chunks + * determined by running: + * + * node scripts/build_kibana_platform_plugins --dist --profile + * node scripts/find_babel_runtime_helpers_in_use.js + */ + '@babel/runtime/helpers/assertThisInitialized', + '@babel/runtime/helpers/classCallCheck', + '@babel/runtime/helpers/classPrivateFieldGet', + '@babel/runtime/helpers/classPrivateFieldSet', + '@babel/runtime/helpers/createSuper', + '@babel/runtime/helpers/defineProperty', + '@babel/runtime/helpers/extends', + '@babel/runtime/helpers/inherits', + '@babel/runtime/helpers/interopRequireDefault', + '@babel/runtime/helpers/interopRequireWildcard', + '@babel/runtime/helpers/objectSpread2', + '@babel/runtime/helpers/objectWithoutPropertiesLoose', + '@babel/runtime/helpers/slicedToArray', + '@babel/runtime/helpers/toArray', + '@babel/runtime/helpers/toConsumableArray', + '@babel/runtime/helpers/typeof', + '@babel/runtime/helpers/wrapNativeSuper', + // modules from npm '@elastic/charts', '@elastic/eui', '@elastic/eui/dist/eui_charts_theme', '@elastic/eui/lib/services', '@elastic/eui/lib/services/format', - '@elastic/eui/dist/eui_theme_light.json', - '@elastic/eui/dist/eui_theme_dark.json', '@elastic/eui/dist/eui_theme_amsterdam_light.json', '@elastic/eui/dist/eui_theme_amsterdam_dark.json', '@elastic/numeral', @@ -71,8 +94,6 @@ module.exports = (_, argv) => { 'styled-components', 'tslib', ], - 'kbn-ui-shared-deps-npm.v7.dark': ['@elastic/eui/dist/eui_theme_dark.css'], - 'kbn-ui-shared-deps-npm.v7.light': ['@elastic/eui/dist/eui_theme_light.css'], 'kbn-ui-shared-deps-npm.v8.dark': ['@elastic/eui/dist/eui_theme_amsterdam_dark.css'], 'kbn-ui-shared-deps-npm.v8.light': ['@elastic/eui/dist/eui_theme_amsterdam_light.css'], }, diff --git a/packages/kbn-ui-shared-deps-src/src/theme.ts b/packages/kbn-ui-shared-deps-src/src/theme.ts index 05c2210042ac67..f058913cdeeab6 100644 --- a/packages/kbn-ui-shared-deps-src/src/theme.ts +++ b/packages/kbn-ui-shared-deps-src/src/theme.ts @@ -6,30 +6,21 @@ * Side Public License, v 1. */ -import { default as v7Light } from '@elastic/eui/dist/eui_theme_light.json'; -import { default as v7Dark } from '@elastic/eui/dist/eui_theme_dark.json'; import { default as v8Light } from '@elastic/eui/dist/eui_theme_amsterdam_light.json'; import { default as v8Dark } from '@elastic/eui/dist/eui_theme_amsterdam_dark.json'; const globals: any = typeof window === 'undefined' ? {} : window; -export type Theme = typeof v7Light | typeof v8Light; +export type Theme = typeof v8Light; // in the Kibana app we can rely on this global being defined, but in // some cases (like jest) the global is undefined -export const tag: string = globals.__kbnThemeTag__ || 'v7light'; -export const version = tag.startsWith('v7') ? 7 : 8; +export const tag: string = globals.__kbnThemeTag__ || 'v8light'; +export const version = 8; export const darkMode = tag.endsWith('dark'); -export let euiLightVars: Theme; -export let euiDarkVars: Theme; -if (version === 7) { - euiLightVars = v7Light; - euiDarkVars = v7Dark; -} else { - euiLightVars = v8Light; - euiDarkVars = v8Dark; -} +export const euiLightVars: Theme = v8Light; +export const euiDarkVars: Theme = v8Dark; /** * EUI Theme vars that automatically adjust to light/dark theme diff --git a/packages/kbn-utility-types/src/index.ts b/packages/kbn-utility-types/src/index.ts index 921f056c6b7551..92aa8d7ecc9892 100644 --- a/packages/kbn-utility-types/src/index.ts +++ b/packages/kbn-utility-types/src/index.ts @@ -7,7 +7,7 @@ */ import { PromiseType } from 'utility-types'; -export { $Values, Assign, Class, Optional, Required } from 'utility-types'; +export type { $Values, Assign, Class, Optional, Required } from 'utility-types'; export type { JsonArray, diff --git a/renovate.json5 b/renovate.json5 index 69f642712ca6f2..d979b24f1fe098 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -11,7 +11,7 @@ 'npm', ], baseBranches: [ - 'master', + 'main', '7.16', '7.15', ], @@ -38,7 +38,7 @@ groupName: '@elastic/charts', packageNames: ['@elastic/charts'], reviewers: ['team:datavis'], - matchBaseBranches: ['master'], + matchBaseBranches: ['main'], labels: ['release_note:skip', 'v8.0.0', 'v7.16.0', 'auto-backport'], draftPR: true, enabled: true, @@ -47,7 +47,7 @@ groupName: '@elastic/elasticsearch', packageNames: ['@elastic/elasticsearch'], reviewers: ['team:kibana-operations', 'team:kibana-core'], - matchBaseBranches: ['master'], + matchBaseBranches: ['main'], labels: ['release_note:skip', 'v8.0.0', 'Team:Operations', 'Team:Core', 'backport:skip'], enabled: true, }, @@ -72,7 +72,7 @@ packageNames: ['@types/babel__core'], matchPackagePatterns: ["^@babel", "^babel-plugin"], reviewers: ['team:kibana-operations'], - matchBaseBranches: ['master'], + matchBaseBranches: ['main'], labels: ['Team:Operations', 'release_note:skip'], enabled: true, }, @@ -82,7 +82,7 @@ matchPackagePatterns: ["polyfill"], excludePackageNames: ['@loaders.gl/polyfills'], reviewers: ['team:kibana-operations'], - matchBaseBranches: ['master'], + matchBaseBranches: ['main'], labels: ['Team:Operations', 'release_note:skip'], enabled: true, }, @@ -90,7 +90,7 @@ groupName: 'vega related modules', packageNames: ['vega', 'vega-lite', 'vega-schema-url-parser', 'vega-tooltip'], reviewers: ['team:kibana-vis-editors'], - matchBaseBranches: ['master'], + matchBaseBranches: ['main'], labels: ['Feature:Vega', 'Team:VisEditors'], enabled: true, }, @@ -99,7 +99,7 @@ packageNames: ['eslint-plugin-cypress'], matchPackagePatterns: ["^cypress"], reviewers: ['Team:apm', 'Team: SecuritySolution'], - matchBaseBranches: ['master'], + matchBaseBranches: ['main'], labels: ['buildkite-ci', 'ci:all-cypress-suites'], enabled: true, } @@ -113,7 +113,7 @@ 'xml-crypto', '@types/xml-crypto' ], reviewers: ['team:kibana-security'], - matchBaseBranches: ['master'], + matchBaseBranches: ['main'], labels: ['Team:Security', 'release_note:skip', 'auto-backport'], enabled: true, }, diff --git a/scripts/eslint_with_types.js b/scripts/eslint_with_types.js new file mode 100644 index 00000000000000..e0371bc5321701 --- /dev/null +++ b/scripts/eslint_with_types.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 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. + */ + +require('../src/setup_node_env'); +require('../src/dev/eslint').runEslintWithTypes(); diff --git a/scripts/find_babel_runtime_helpers_in_use.js b/scripts/find_babel_runtime_helpers_in_use.js new file mode 100644 index 00000000000000..a229c8e11a2ca0 --- /dev/null +++ b/scripts/find_babel_runtime_helpers_in_use.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 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. + */ + +require('../src/setup_node_env/no_transpilation'); +require('@kbn/optimizer').runFindBabelHelpersInEntryBundlesCli(); diff --git a/src/core/public/apm_resource_counter.ts b/src/core/public/apm_resource_counter.ts new file mode 100644 index 00000000000000..2c4216c3ddeabe --- /dev/null +++ b/src/core/public/apm_resource_counter.ts @@ -0,0 +1,47 @@ +/* + * 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 class CachedResourceObserver { + private loaded = { + networkOrDisk: 0, + memory: 0, + }; + private observer?: PerformanceObserver; + + constructor() { + if (!window.PerformanceObserver) return; + + const cb = (entries: PerformanceObserverEntryList) => { + const e = entries.getEntries(); + e.forEach((entry: Record) => { + if (entry.initiatorType === 'script' || entry.initiatorType === 'link') { + // If the resource is fetched from a local cache, or if it is a cross-origin resource, this property returns zero. + // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/transferSize + if (entry.name.indexOf(window.location.host) > -1 && entry.transferSize === 0) { + this.loaded.memory++; + } else { + this.loaded.networkOrDisk++; + } + } + }); + }; + this.observer = new PerformanceObserver(cb); + this.observer.observe({ + type: 'resource', + buffered: true, + }); + } + + public getCounts() { + return this.loaded; + } + + public destroy() { + this.observer?.disconnect(); + } +} diff --git a/src/core/public/apm_system.test.ts b/src/core/public/apm_system.test.ts index 85d5406c924e6a..f62421cb55abc9 100644 --- a/src/core/public/apm_system.test.ts +++ b/src/core/public/apm_system.test.ts @@ -7,9 +7,11 @@ */ jest.mock('@elastic/apm-rum'); -import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import type { DeeplyMockedKeys, MockedKeys } from '@kbn/utility-types/jest'; import { init, apm } from '@elastic/apm-rum'; import { ApmSystem } from './apm_system'; +import { Subject } from 'rxjs'; +import { InternalApplicationStart } from './application/types'; const initMock = init as jest.Mocked; const apmMock = apm as DeeplyMockedKeys; @@ -39,6 +41,119 @@ describe('ApmSystem', () => { expect(apm.addLabels).toHaveBeenCalledWith({ alpha: 'one' }); }); + describe('manages the page load transaction', () => { + it('does nothing if theres no transaction', async () => { + const apmSystem = new ApmSystem({ active: true }); + const mockTransaction: MockedKeys = { + type: 'wrong', + // @ts-expect-error 2345 + block: jest.fn(), + mark: jest.fn(), + }; + apmMock.getCurrentTransaction.mockReturnValue(mockTransaction); + await apmSystem.setup(); + expect(mockTransaction.mark).not.toHaveBeenCalled(); + // @ts-expect-error 2345 + expect(mockTransaction.block).not.toHaveBeenCalled(); + }); + + it('blocks a page load transaction', async () => { + const apmSystem = new ApmSystem({ active: true }); + const mockTransaction: MockedKeys = { + type: 'page-load', + // @ts-expect-error 2345 + block: jest.fn(), + mark: jest.fn(), + }; + apmMock.getCurrentTransaction.mockReturnValue(mockTransaction); + await apmSystem.setup(); + expect(mockTransaction.mark).toHaveBeenCalledTimes(1); + expect(mockTransaction.mark).toHaveBeenCalledWith('apm-setup'); + // @ts-expect-error 2345 + expect(mockTransaction.block).toHaveBeenCalledTimes(1); + }); + + it('marks apm start', async () => { + const apmSystem = new ApmSystem({ active: true }); + const currentAppId$ = new Subject(); + const mark = jest.fn(); + const mockTransaction: MockedKeys = { + type: 'page-load', + mark, + // @ts-expect-error 2345 + block: jest.fn(), + end: jest.fn(), + addLabels: jest.fn(), + }; + + apmMock.getCurrentTransaction.mockReturnValue(mockTransaction); + await apmSystem.setup(); + + mark.mockReset(); + + await apmSystem.start({ + application: { + currentAppId$, + } as any as InternalApplicationStart, + }); + + expect(mark).toHaveBeenCalledWith('apm-start'); + }); + + it('closes the page load transaction once', async () => { + const apmSystem = new ApmSystem({ active: true }); + const currentAppId$ = new Subject(); + const mockTransaction: MockedKeys = { + type: 'page-load', + // @ts-expect-error 2345 + block: jest.fn(), + mark: jest.fn(), + end: jest.fn(), + addLabels: jest.fn(), + }; + apmMock.getCurrentTransaction.mockReturnValue(mockTransaction); + await apmSystem.setup(); + await apmSystem.start({ + application: { + currentAppId$, + } as any as InternalApplicationStart, + }); + currentAppId$.next('myapp'); + + expect(mockTransaction.end).toHaveBeenCalledTimes(1); + + currentAppId$.next('another-app'); + + expect(mockTransaction.end).toHaveBeenCalledTimes(1); + }); + + it('adds resource load labels', async () => { + const apmSystem = new ApmSystem({ active: true }); + const currentAppId$ = new Subject(); + const mockTransaction: Transaction = { + type: 'page-load', + // @ts-expect-error 2345 + block: jest.fn(), + mark: jest.fn(), + end: jest.fn(), + addLabels: jest.fn(), + }; + apmMock.getCurrentTransaction.mockReturnValue(mockTransaction); + await apmSystem.setup(); + await apmSystem.start({ + application: { + currentAppId$, + } as any as InternalApplicationStart, + }); + currentAppId$.next('myapp'); + + expect(mockTransaction.addLabels).toHaveBeenCalledWith({ + 'loaded-resources': 0, + 'cached-resources': 0, + }); + }); + }); + describe('http request normalization', () => { let windowSpy: any; diff --git a/src/core/public/apm_system.ts b/src/core/public/apm_system.ts index c64c1923f1131d..5201b7005c66e1 100644 --- a/src/core/public/apm_system.ts +++ b/src/core/public/apm_system.ts @@ -8,6 +8,7 @@ import type { ApmBase, AgentConfigOptions } from '@elastic/apm-rum'; import { modifyUrl } from '@kbn/std'; +import { CachedResourceObserver } from './apm_resource_counter'; import type { InternalApplicationStart } from './application'; /** "GET protocol://hostname:port/pathname" */ @@ -31,17 +32,21 @@ interface StartDeps { export class ApmSystem { private readonly enabled: boolean; private pageLoadTransaction?: Transaction; + private resourceObserver: CachedResourceObserver; + private apm?: ApmBase; /** * `apmConfig` would be populated with relevant APM RUM agent * configuration if server is started with elastic.apm.* config. */ constructor(private readonly apmConfig?: ApmConfig, private readonly basePath = '') { this.enabled = apmConfig != null && !!apmConfig.active; + this.resourceObserver = new CachedResourceObserver(); } async setup() { if (!this.enabled) return; const { init, apm } = await import('@elastic/apm-rum'); + this.apm = apm; const { globalLabels, ...apmConfig } = this.apmConfig!; if (globalLabels) { apm.addLabels(globalLabels); @@ -50,36 +55,23 @@ export class ApmSystem { this.addHttpRequestNormalization(apm); init(apmConfig); - this.pageLoadTransaction = apm.getCurrentTransaction(); - - // Keep the page load transaction open until all resources finished loading - if (this.pageLoadTransaction && this.pageLoadTransaction.type === 'page-load') { - // @ts-expect-error 2339 - this.pageLoadTransaction.block(true); - this.pageLoadTransaction.mark('apm-setup'); - } + // hold page load transaction blocks a transaction implicitly created by init. + this.holdPageLoadTransaction(apm); } async start(start?: StartDeps) { if (!this.enabled || !start) return; - if (this.pageLoadTransaction && this.pageLoadTransaction.type === 'page-load') { - this.pageLoadTransaction.mark('apm-start'); - } + this.markPageLoadStart(); /** * Register listeners for navigation changes and capture them as * route-change transactions after Kibana app is bootstrapped */ start.application.currentAppId$.subscribe((appId) => { - const apmInstance = (window as any).elasticApm; - if (appId && apmInstance && typeof apmInstance.startTransaction === 'function') { - // Close the page load transaction - if (this.pageLoadTransaction && this.pageLoadTransaction.type === 'page-load') { - this.pageLoadTransaction.end(); - this.pageLoadTransaction = undefined; - } - apmInstance.startTransaction(`/app/${appId}`, 'route-change', { + if (appId && this.apm) { + this.closePageLoadTransaction(); + this.apm.startTransaction(`/app/${appId}`, 'route-change', { managed: true, canReuse: true, }); @@ -87,6 +79,39 @@ export class ApmSystem { }); } + /* Hold the page load transaction open, until all resources actually finish loading */ + private holdPageLoadTransaction(apm: ApmBase) { + const transaction = apm.getCurrentTransaction(); + + // Keep the page load transaction open until all resources finished loading + if (transaction && transaction.type === 'page-load') { + this.pageLoadTransaction = transaction; + // @ts-expect-error 2339 block is a private property of Transaction interface + this.pageLoadTransaction.block(true); + this.pageLoadTransaction.mark('apm-setup'); + } + } + + /* Close and clear the page load transaction */ + private closePageLoadTransaction() { + if (this.pageLoadTransaction) { + const loadCounts = this.resourceObserver.getCounts(); + this.pageLoadTransaction.addLabels({ + 'loaded-resources': loadCounts.networkOrDisk, + 'cached-resources': loadCounts.memory, + }); + this.resourceObserver.destroy(); + this.pageLoadTransaction.end(); + this.pageLoadTransaction = undefined; + } + } + + private markPageLoadStart() { + if (this.pageLoadTransaction) { + this.pageLoadTransaction.mark('apm-start'); + } + } + /** * Adds an observer to the APM configuration for normalizing transactions of the 'http-request' type to remove the * hostname, protocol, port, and base path. Allows for coorelating data cross different deployments. diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 571b564f903298..a16c15555f5e5d 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -425,7 +425,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` } >

- diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index e73d5e8002a029..d2b1078641437a 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -1272,45 +1272,7 @@ exports[`Header renders 1`] = ` "closed": false, "hasError": false, "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], + "observers": Array [], "thrownError": null, } } @@ -4338,7 +4300,6 @@ exports[`Header renders 1`] = ` } homeHref="/" id="generated-id" - isLocked={true} isNavOpen={false} navLinks$={ BehaviorSubject { diff --git a/src/core/public/chrome/ui/header/collapsible_nav.scss b/src/core/public/chrome/ui/header/collapsible_nav.scss index d72775d374d47f..5f84863ad73094 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.scss +++ b/src/core/public/chrome/ui/header/collapsible_nav.scss @@ -3,3 +3,24 @@ max-height: $euiSize * 10; margin-right: -$euiSizeS; } + +/** + * 1. Increase the hit area of the link (anchor) + * 2. Only show the text underline when hovering on the text/anchor portion + */ + +.kbnCollapsibleNav__solutionGroupButton { + display: block; /* 1 */ + + &:hover { + text-decoration: none; /* 2 */ + } +} + +.kbnCollapsibleNav__solutionGroupLink { + display: block; /* 1 */ + + &:hover { + text-decoration: underline; /* 2 */ + } +} diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index ccc0e17b655b14..ef380ee47e235e 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -14,13 +14,12 @@ import { EuiHorizontalRule, EuiListGroup, EuiListGroupItem, - EuiShowFor, EuiCollapsibleNavProps, EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { groupBy, sortBy } from 'lodash'; -import React, { Fragment, useMemo, useRef } from 'react'; +import React, { Fragment, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import * as Rx from 'rxjs'; import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../..'; @@ -33,6 +32,7 @@ import { createRecentNavLink, isModifiedOrPrevented, createEuiButtonItem, + createOverviewLink, } from './nav_link'; function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; @@ -72,7 +72,6 @@ interface Props { appId$: InternalApplicationStart['currentAppId$']; basePath: HttpStart['basePath']; id: string; - isLocked: boolean; isNavOpen: boolean; homeHref: string; navLinks$: Rx.Observable; @@ -86,10 +85,17 @@ interface Props { button: EuiCollapsibleNavProps['button']; } +const overviewIDsToHide = ['kibanaOverview', 'enterpriseSearch']; +const overviewIDs = [ + ...overviewIDsToHide, + 'observability-overview', + 'securitySolutionUI:overview', + 'management', +]; + export function CollapsibleNav({ basePath, id, - isLocked, isNavOpen, homeHref, storage = window.localStorage, @@ -104,23 +110,29 @@ export function CollapsibleNav({ const allowedLinks = useMemo( () => allLinks.filter( - // Filterting out hidden links and the integrations one in favor of a specific Add Data button at the bottom - (link) => !link.hidden && link.id !== 'integrations' + (link) => + // Filterting out hidden links, + !link.hidden && + // integrations link in favor of a specific Add Data button at the bottom, + link.id !== 'integrations' && + // and non-data overview pages + !overviewIDsToHide.includes(link.id) ), [allLinks] ); + // Find just the integrations link const integrationsLink = useMemo( - () => - allLinks.find( - // Find just the integrations link - (link) => link.id === 'integrations' - ), + () => allLinks.find((link) => link.id === 'integrations'), + [allLinks] + ); + // Find all the overview (landing page) links + const overviewLinks = useMemo( + () => allLinks.filter((link) => overviewIDs.includes(link.id)), [allLinks] ); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); const customNavLink = useObservable(observables.customNavLink$, undefined); const appId = useObservable(observables.appId$, ''); - const lockRef = useRef(null); const groupedNavLinks = groupBy(allowedLinks, (link) => link?.category?.id); const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); @@ -153,7 +165,7 @@ export function CollapsibleNav({ @@ -166,12 +178,13 @@ export function CollapsibleNav({ dataTestSubj: 'collapsibleNavCustomNavLink', onClick: closeNav, externalLink: true, + iconProps: { color: 'ghost' }, }), ]} maxWidth="none" - color="text" gutterSize="none" size="s" + color="ghost" /> @@ -270,13 +283,31 @@ export function CollapsibleNav({ {/* Kibana, Observability, Security, and Management sections */} {orderedCategories.map((categoryName) => { const category = categoryDictionary[categoryName]!; + const overviewLink = overviewLinks.find((link) => link.category === category); return ( + {category.label} + + ) : ( + category.label + ) + } isCollapsible={true} initialIsOpen={getIsCategoryOpen(category.id, storage)} onToggle={(isCategoryOpen) => setIsCategoryOpen(category.id, isCategoryOpen, storage)} @@ -305,45 +336,6 @@ export function CollapsibleNav({ ))} - - {/* Docking button only for larger screens that can support it*/} - - - - { - onIsLockedUpdate(!isLocked); - if (lockRef.current) { - lockRef.current.focus(); - } - }} - iconType={isLocked ? 'lock' : 'lockOpen'} - /> - - - {integrationsLink && ( @@ -355,7 +347,6 @@ export function CollapsibleNav({ link: integrationsLink, navigateToUrl, onClick: closeNav, - dataTestSubj: `collapsibleNavAppButton-${integrationsLink.id}`, })} fill fullWidth diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 578c87411e5437..40108760cc0be4 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -84,7 +84,6 @@ export function Header({ ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); - const isLocked = useObservable(observables.isLocked$, false); const [isNavOpen, setIsNavOpen] = useState(false); const [navId] = useState(htmlIdGenerator()()); const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$); @@ -160,7 +159,6 @@ export function Header({ : undefined, }), @@ -77,7 +80,7 @@ export function createEuiButtonItem({ navigateToUrl, dataTestSubj, }: Omit) { - const { href, disabled, url } = link; + const { href, disabled, url, id } = link; return { href, @@ -90,7 +93,30 @@ export function createEuiButtonItem({ navigateToUrl(url); }, isDisabled: disabled, - 'data-test-subj': dataTestSubj, + dataTestSubj: `collapsibleNavAppButton-${id}`, + }; +} + +export function createOverviewLink({ + link, + onClick = () => {}, + navigateToUrl, +}: Omit) { + const { href, url } = link; + + return { + href, + /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ + onClick(event: React.MouseEvent) { + // Prevent the accordions from opening or closing when clicking just the link + event.stopPropagation(); + if (!isModifiedOrPrevented(event)) { + onClick(); + } + event.preventDefault(); + navigateToUrl(url); + }, + 'data-test-subj': `collapsibleNavAppLink-overview`, }; } diff --git a/src/core/public/core_app/styles/_globals_v7dark.scss b/src/core/public/core_app/styles/_globals_v7dark.scss deleted file mode 100644 index 93416010897377..00000000000000 --- a/src/core/public/core_app/styles/_globals_v7dark.scss +++ /dev/null @@ -1,10 +0,0 @@ -// v7dark global scope -// -// prepended to all .scss imports (from JS, when v7dark theme selected) - -@import '@elastic/eui/src/themes/eui/eui_colors_dark'; -@import '@elastic/eui/src/themes/eui/eui_globals'; - -@import './mixins'; - -$kbnThemeVersion: 'v7dark'; diff --git a/src/core/public/core_app/styles/_globals_v7light.scss b/src/core/public/core_app/styles/_globals_v7light.scss deleted file mode 100644 index e1ff6ec70aab52..00000000000000 --- a/src/core/public/core_app/styles/_globals_v7light.scss +++ /dev/null @@ -1,10 +0,0 @@ -// v7light global scope -// -// prepended to all .scss imports (from JS, when v7light theme selected) - -@import '@elastic/eui/src/themes/eui/eui_colors_light'; -@import '@elastic/eui/src/themes/eui/eui_globals'; - -@import './mixins'; - -$kbnThemeVersion: 'v7light'; diff --git a/src/core/public/core_app/styles/_mixins.scss b/src/core/public/core_app/styles/_mixins.scss index b2adbdb691bb6c..d088a47144f33a 100644 --- a/src/core/public/core_app/styles/_mixins.scss +++ b/src/core/public/core_app/styles/_mixins.scss @@ -120,31 +120,3 @@ } } } - -@mixin kbnThemeStyle($theme, $mode: 'both') { - $themes: 'v7', 'v8'; - @if (index($themes, $theme)) { - @if ($mode == 'both') { - $themeLight: $theme + 'light'; - $themeDark: $theme + 'dark'; - // $kbnThemeVersion comes from the active theme's globals file (e.g. _globals_v8light.scss) - @if ($kbnThemeVersion == $themeLight or $kbnThemeVersion == $themeDark) { - @content; - } - } @else if ($mode == 'light') { - $themeLight: $theme + 'light'; - @if ($kbnThemeVersion == $themeLight) { - @content; - } - } @else if ($mode == 'dark') { - $themeDark: $theme + 'dark'; - @if ($kbnThemeVersion == $themeDark) { - @content; - } - } @else { - @warn 'The second parameter must be a valid mode (light, dark, or both) -- got #{$mode}'; - } - } @else { - @warn 'Invalid $theme. Valid options are: #{$themes}. Got #{$theme} instead'; - } -} diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 2bbb4703ecd19d..5dc214407e2063 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -18,7 +18,9 @@ export class DocLinksService { public setup() {} public start({ injectedMetadata }: StartDeps): DocLinksStart { - const DOC_LINK_VERSION = injectedMetadata.getKibanaBranch(); + const kibanaBranch = injectedMetadata.getKibanaBranch(); + // Documentation for `main` branches is still published at a `master` URL. + const DOC_LINK_VERSION = kibanaBranch === 'main' ? 'master' : kibanaBranch; const ELASTIC_WEBSITE_URL = 'https://www.elastic.co/'; const ELASTICSEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`; const KIBANA_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/`; @@ -38,9 +40,9 @@ export class DocLinksService { kibanaSettings: `${KIBANA_DOCS}apm-settings-in-kibana.html`, supportedServiceMaps: `${KIBANA_DOCS}service-maps.html#service-maps-supported`, customLinks: `${KIBANA_DOCS}custom-links.html`, - droppedTransactionSpans: `${APM_DOCS}get-started/master/transaction-spans.html#dropped-spans`, - upgrading: `${APM_DOCS}server/master/upgrading.html`, - metaData: `${APM_DOCS}get-started/master/metadata.html`, + droppedTransactionSpans: `${APM_DOCS}guide/${DOC_LINK_VERSION}/data-model-spans.html#data-model-dropped-spans`, + upgrading: `${APM_DOCS}guide/${DOC_LINK_VERSION}/upgrade.html`, + metaData: `${APM_DOCS}guide/${DOC_LINK_VERSION}/data-model-metadata.html`, }, canvas: { guide: `${KIBANA_DOCS}canvas.html`, @@ -303,9 +305,9 @@ export class DocLinksService { }, observability: { guide: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/index.html`, - infrastructureThreshold: `{ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/infrastructure-threshold-alert.html`, - logsThreshold: `{ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/logs-threshold-alert.html`, - metricsThreshold: `{ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/metrics-threshold-alert.html`, + infrastructureThreshold: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/infrastructure-threshold-alert.html`, + logsThreshold: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/logs-threshold-alert.html`, + metricsThreshold: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/metrics-threshold-alert.html`, monitorStatus: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/monitor-status-alert.html`, monitorUptime: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/monitor-uptime.html`, tlsCertificate: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/tls-certificate-alert.html`, @@ -479,6 +481,7 @@ export class DocLinksService { settingsFleetServerHostSettings: `${FLEET_DOCS}fleet-settings.html#fleet-server-hosts-setting`, troubleshooting: `${FLEET_DOCS}fleet-troubleshooting.html`, elasticAgent: `${FLEET_DOCS}elastic-agent-installation.html`, + beatsAgentComparison: `${FLEET_DOCS}beats-agent-comparison.html`, datastreams: `${FLEET_DOCS}data-streams.html`, datastreamsILM: `${FLEET_DOCS}data-streams.html#data-streams-ilm`, datastreamsNamingScheme: `${FLEET_DOCS}data-streams.html#data-streams-naming-scheme`, @@ -736,6 +739,7 @@ export interface DocLinksStart { readonly ingest: Record; readonly fleet: Readonly<{ datastreamsILM: string; + beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; diff --git a/src/core/public/http/external_url_service.test.ts b/src/core/public/http/external_url_service.test.ts index dcd99280151bfa..ee757c50467600 100644 --- a/src/core/public/http/external_url_service.test.ts +++ b/src/core/public/http/external_url_service.test.ts @@ -155,6 +155,40 @@ describe('External Url Service', () => { }); }); + internalRequestScenarios.forEach(({ description, policy, allowExternal }) => { + describe(description, () => { + it('allows relative URLs without absolute path replacing last path segment', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `my_other_app?foo=bar`; + const result = setup.validateUrl(urlCandidate); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}/app/${urlCandidate}`); + }); + + it('allows relative URLs without absolute path replacing multiple path segments', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `/api/my_other_app?foo=bar`; + const result = setup.validateUrl(`..${urlCandidate}`); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}${urlCandidate}`); + }); + + if (!allowExternal) { + describe('handles bypass of base path via relative URL', () => { + it('does not allow relative URLs that escape base path', () => { + const { setup } = setupService({ location, serverBasePath, policy: [] }); + const urlCandidate = `../../base_path_escape`; + const result = setup.validateUrl(urlCandidate); + + expect(result).toBeNull(); + }); + }); + } + }); + }); + describe('handles protocol resolution bypass', () => { it('does not allow relative URLs that include a host', () => { const { setup } = setupService({ location, serverBasePath, policy: [] }); @@ -194,7 +228,7 @@ describe('External Url Service', () => { internalRequestScenarios.forEach(({ description, policy }) => { describe(description, () => { - it('allows relative URLs', () => { + it('allows relative URLs with absolute path', () => { const { setup } = setupService({ location, serverBasePath, policy }); const urlCandidate = `/some/path?foo=bar`; const result = setup.validateUrl(`${serverBasePath}${urlCandidate}`); @@ -214,6 +248,28 @@ describe('External Url Service', () => { }); }); + internalRequestScenarios.forEach(({ description, policy }) => { + describe(description, () => { + it('allows relative URLs without absolute path replacing last path segment', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `my_other_app?foo=bar`; + const result = setup.validateUrl(urlCandidate); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}/app/${urlCandidate}`); + }); + + it('allows relative URLs without absolute path replacing multiple path segments', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `/api/my_other_app?foo=bar`; + const result = setup.validateUrl(`..${urlCandidate}`); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}${urlCandidate}`); + }); + }); + }); + describe('handles protocol resolution bypass', () => { it('does not allow relative URLs that include a host', () => { const { setup } = setupService({ location, serverBasePath, policy: [] }); diff --git a/src/core/public/http/external_url_service.ts b/src/core/public/http/external_url_service.ts index dbd7313b65059b..166e167b3b994b 100644 --- a/src/core/public/http/external_url_service.ts +++ b/src/core/public/http/external_url_service.ts @@ -14,7 +14,7 @@ import { InjectedMetadataSetup } from '../injected_metadata'; import { Sha256 } from '../utils'; interface SetupDeps { - location: Pick; + location: Pick; injectedMetadata: InjectedMetadataSetup; } @@ -52,11 +52,11 @@ function normalizeProtocol(protocol: string) { const createExternalUrlValidation = ( rules: IExternalUrlPolicy[], - location: Pick, + location: Pick, serverBasePath: string ) => { - const base = new URL(location.origin + serverBasePath); return function validateExternalUrl(next: string) { + const base = new URL(location.href); const url = new URL(next, base); const isInternalURL = diff --git a/src/core/public/http/fetch.test.ts b/src/core/public/http/fetch.test.ts index d1af183e9e180e..e897d69057e029 100644 --- a/src/core/public/http/fetch.test.ts +++ b/src/core/public/http/fetch.test.ts @@ -438,7 +438,7 @@ describe('Fetch', () => { headers: { 'Content-Type': 'application/ndjson' }, }); - const data = await fetchInstance.post('/my/path', { + const data = await fetchInstance.post('/my/path', { body, headers: { 'Content-Type': undefined, diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts index 372445b2b09028..4ee81f4b47aa0c 100644 --- a/src/core/public/http/fetch.ts +++ b/src/core/public/http/fetch.ts @@ -92,9 +92,9 @@ export class Fetch { ); if (optionsWithPath.asResponse) { - resolve(interceptedResponse); + resolve(interceptedResponse as HttpResponse); } else { - resolve(interceptedResponse.body); + resolve(interceptedResponse.body as TResponseBody); } } catch (error) { if (!(error instanceof HttpInterceptHaltError)) { @@ -142,7 +142,9 @@ export class Fetch { return new Request(url, fetchOptions as RequestInit); } - private async fetchResponse(fetchOptions: HttpFetchOptionsWithPath): Promise> { + private async fetchResponse( + fetchOptions: HttpFetchOptionsWithPath + ): Promise> { const request = this.createRequest(fetchOptions); let response: Response; let body = null; @@ -181,9 +183,15 @@ export class Fetch { } private shorthand(method: string): HttpHandler { - return (pathOrOptions: string | HttpFetchOptionsWithPath, options?: HttpFetchOptions) => { - const optionsWithPath = validateFetchArguments(pathOrOptions, options); - return this.fetch({ ...optionsWithPath, method }); + return ( + pathOrOptions: string | HttpFetchOptionsWithPath, + options?: HttpFetchOptions + ) => { + const optionsWithPath: HttpFetchOptionsWithPath = validateFetchArguments( + pathOrOptions, + options + ); + return this.fetch>({ ...optionsWithPath, method }); }; } } diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 4d59c52813e182..876799765ea1c4 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -296,18 +296,19 @@ export interface HttpFetchOptionsWithPath extends HttpFetchOptions { * @public */ export interface HttpHandler { - (path: string, options: HttpFetchOptions & { asResponse: true }): Promise< + ( + path: string, + options: HttpFetchOptions & { asResponse: true } + ): Promise>; + (options: HttpFetchOptionsWithPath & { asResponse: true }): Promise< HttpResponse >; - (options: HttpFetchOptionsWithPath & { asResponse: true }): Promise< - HttpResponse - >; - (path: string, options?: HttpFetchOptions): Promise; - (options: HttpFetchOptionsWithPath): Promise; + (path: string, options?: HttpFetchOptions): Promise; + (options: HttpFetchOptionsWithPath): Promise; } /** @public */ -export interface HttpResponse { +export interface HttpResponse { /** The original {@link HttpFetchOptionsWithPath} used to send this request. */ readonly fetchOptions: Readonly; /** Raw request sent to Kibana server. */ @@ -322,7 +323,7 @@ export interface HttpResponse { * Properties that can be returned by HttpInterceptor.request to override the response. * @public */ -export interface IHttpResponseInterceptorOverrides { +export interface IHttpResponseInterceptorOverrides { /** Raw response received, may be undefined if there was an error. */ readonly response?: Readonly; /** Parsed body received, may be undefined if there was an error. */ @@ -330,7 +331,14 @@ export interface IHttpResponseInterceptorOverrides { } /** @public */ -export interface IHttpFetchError extends Error { +export interface ResponseErrorBody { + message: string; + statusCode: number; + attributes?: Record; +} + +/** @public */ +export interface IHttpFetchError extends Error { readonly name: string; readonly request: Request; readonly response?: Response; @@ -342,7 +350,7 @@ export interface IHttpFetchError extends Error { * @deprecated Provided for legacy compatibility. Prefer the `response` property instead. */ readonly res?: Response; - readonly body?: any; // TODO: this should be unknown + readonly body?: TResponseBody; } /** @public */ diff --git a/src/core/public/index.ts b/src/core/public/index.ts index b2d3d21a09999c..40326d9c676065 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -157,6 +157,7 @@ export type { IAnonymousPaths, IExternalUrl, IHttpInterceptController, + ResponseErrorBody, IHttpFetchError, IHttpResponseInterceptorOverrides, } from './http'; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index cf0b526aa9fd92..6c377bd2870aef 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -697,6 +697,7 @@ export interface DocLinksStart { readonly ingest: Record; readonly fleet: Readonly<{ datastreamsILM: string; + beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; @@ -804,17 +805,17 @@ export interface HttpFetchQuery { // @public export interface HttpHandler { // (undocumented) - (path: string, options: HttpFetchOptions & { + (path: string, options: HttpFetchOptions & { asResponse: true; }): Promise>; // (undocumented) - (options: HttpFetchOptionsWithPath & { + (options: HttpFetchOptionsWithPath & { asResponse: true; }): Promise>; // (undocumented) - (path: string, options?: HttpFetchOptions): Promise; + (path: string, options?: HttpFetchOptions): Promise; // (undocumented) - (options: HttpFetchOptionsWithPath): Promise; + (options: HttpFetchOptionsWithPath): Promise; } // @public @@ -866,7 +867,7 @@ export interface HttpRequestInit { } // @public (undocumented) -export interface HttpResponse { +export interface HttpResponse { readonly body?: TResponseBody; readonly fetchOptions: Readonly; readonly request: Readonly; @@ -933,9 +934,9 @@ export interface IExternalUrlPolicy { } // @public (undocumented) -export interface IHttpFetchError extends Error { +export interface IHttpFetchError extends Error { // (undocumented) - readonly body?: any; + readonly body?: TResponseBody; // (undocumented) readonly name: string; // @deprecated (undocumented) @@ -955,7 +956,7 @@ export interface IHttpInterceptController { } // @public -export interface IHttpResponseInterceptorOverrides { +export interface IHttpResponseInterceptorOverrides { readonly body?: TResponseBody; readonly response?: Readonly; } @@ -1187,6 +1188,16 @@ export interface ResolvedSimpleSavedObject { saved_object: SimpleSavedObject; } +// @public (undocumented) +export interface ResponseErrorBody { + // (undocumented) + attributes?: Record; + // (undocumented) + message: string; + // (undocumented) + statusCode: number; +} + // Warning: (ae-missing-release-tag) "SavedObject" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index d3810d8932f1aa..c19233809a94be 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -292,7 +292,7 @@ export class SavedObjectsClient { overwrite: options.overwrite, }; - const createRequest: Promise> = this.savedObjectsFetch(path, { + const createRequest = this.savedObjectsFetch>(path, { method: 'POST', query, body: JSON.stringify({ @@ -571,10 +571,10 @@ export class SavedObjectsClient { upsert, }; - return this.savedObjectsFetch(path, { + return this.savedObjectsFetch>(path, { method: 'PUT', body: JSON.stringify(body), - }).then((resp: SavedObject) => { + }).then((resp) => { return this.createSavedObject(resp); }); } @@ -588,11 +588,11 @@ export class SavedObjectsClient { public bulkUpdate(objects: SavedObjectsBulkUpdateObject[] = []) { const path = this.getPath(['_bulk_update']); - return this.savedObjectsFetch(path, { + return this.savedObjectsFetch<{ saved_objects: Array> }>(path, { method: 'PUT', body: JSON.stringify(objects), }).then((resp) => { - resp.saved_objects = resp.saved_objects.map((d: SavedObject) => this.createSavedObject(d)); + resp.saved_objects = resp.saved_objects.map((d) => this.createSavedObject(d)); return renameKeys< PromiseType>, SavedObjectsBatchResponse @@ -624,8 +624,8 @@ export class SavedObjectsClient { * the old kfetch error format of `{res: {status: number}}` whereas `http.fetch` * uses `{response: {status: number}}`. */ - private savedObjectsFetch(path: string, { method, query, body }: HttpFetchOptions) { - return this.http.fetch(path, { method, query, body }); + private savedObjectsFetch(path: string, { method, query, body }: HttpFetchOptions) { + return this.http.fetch(path, { method, query, body }); } } diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff deleted file mode 100644 index 908f1912ba5523..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff2 deleted file mode 100644 index bd14f008977ef5..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff deleted file mode 100644 index cab336ca9ae9dc..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff2 deleted file mode 100644 index 1740872acb1122..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff deleted file mode 100644 index c4d9824070d687..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff2 deleted file mode 100644 index 70e5f006624617..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff deleted file mode 100644 index 94aeeee923a44b..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff2 deleted file mode 100644 index 18ed561d025bf6..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff deleted file mode 100644 index 6f50a9ee834716..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff2 deleted file mode 100644 index 4c9e0b718d94fc..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff deleted file mode 100644 index ede913914485ab..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2 deleted file mode 100644 index af7822f92a0b07..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff deleted file mode 100644 index dc7eefc252506a..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2 deleted file mode 100644 index 5508e70a63846a..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff deleted file mode 100644 index 37c434af765268..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2 deleted file mode 100644 index 6dcfeac7b4fe85..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff deleted file mode 100644 index 4932f1e26e1af0..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff2 deleted file mode 100644 index dbbd89279287e8..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff deleted file mode 100644 index 56c3defcde2141..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff2 deleted file mode 100644 index f23481625b23bc..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff deleted file mode 100644 index 94ceeda8f53133..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2 deleted file mode 100644 index a99ed19507a6c2..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff deleted file mode 100644 index d18a8170133a38..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff2 deleted file mode 100644 index 3896022f3d1ea7..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff deleted file mode 100644 index 863ddc75a4e627..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff2 deleted file mode 100644 index b9e43541e4fa6f..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff deleted file mode 100644 index aef2367d7fd475..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff2 deleted file mode 100644 index e75fb707b9d376..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff deleted file mode 100644 index 27e3d0b568c105..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff2 deleted file mode 100644 index f21699d9ff0f86..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff deleted file mode 100644 index 3859942772a90f..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2 deleted file mode 100644 index 7b556d72d3b70a..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff deleted file mode 100644 index 296e229b0778ea..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff2 deleted file mode 100644 index eba73d06fd399f..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff deleted file mode 100644 index aae25d7b57aa15..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2 deleted file mode 100644 index df5e5f86f5304b..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-italic.var.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-italic.var.woff2 deleted file mode 100644 index 7aff5c010865bd..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-italic.var.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-upright.var.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-upright.var.woff2 deleted file mode 100644 index 16b6adaa660a97..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-upright.var.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI.var.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI.var.woff2 deleted file mode 100644 index 132600df3a2781..00000000000000 Binary files a/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI.var.woff2 and /dev/null differ diff --git a/src/core/server/core_app/assets/fonts/inter_ui/LICENSE.txt b/src/core/server/core_app/assets/fonts/inter_ui/LICENSE.txt deleted file mode 100644 index da64fc78e7e8a4..00000000000000 --- a/src/core/server/core_app/assets/fonts/inter_ui/LICENSE.txt +++ /dev/null @@ -1,92 +0,0 @@ -Copyright (c) 2016-2018 The Inter UI Project Authors (me@rsms.me) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION AND CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/core/server/core_app/core_app.test.ts b/src/core/server/core_app/core_app.test.ts index c13116577af710..6eb38bfaa86441 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/src/core/server/core_app/core_app.test.ts @@ -139,6 +139,46 @@ describe('CoreApp', () => { expect(mockResponseFactory.renderCoreApp).toHaveBeenCalled(); }); + + it('main route handles unknown public API requests', () => { + coreApp.preboot(internalCorePreboot, prebootUIPlugins); + + const [[, handler]] = prebootHTTPResourcesRegistrar.register.mock.calls; + const mockResponseFactory = httpResourcesMock.createResponseFactory(); + handler( + {} as unknown as RequestHandlerContext, + httpServerMock.createKibanaRequest({ path: '/api/status' }), + mockResponseFactory + ); + + expect(mockResponseFactory.renderCoreApp).not.toHaveBeenCalled(); + expect(mockResponseFactory.customError).toHaveBeenCalledWith({ + statusCode: 503, + headers: { 'Retry-After': '30' }, + body: 'Kibana server is not ready yet', + bypassErrorFormat: true, + }); + }); + + it('main route handles unknown internal API requests', () => { + coreApp.preboot(internalCorePreboot, prebootUIPlugins); + + const [[, handler]] = prebootHTTPResourcesRegistrar.register.mock.calls; + const mockResponseFactory = httpResourcesMock.createResponseFactory(); + handler( + {} as unknown as RequestHandlerContext, + httpServerMock.createKibanaRequest({ path: '/internal/security/me' }), + mockResponseFactory + ); + + expect(mockResponseFactory.renderCoreApp).not.toHaveBeenCalled(); + expect(mockResponseFactory.customError).toHaveBeenCalledWith({ + statusCode: 503, + headers: { 'Retry-After': '30' }, + body: 'Kibana server is not ready yet', + bypassErrorFormat: true, + }); + }); }); describe('`/app/{id}/{any*}` route', () => { diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index 23ad78ca46d45a..bd7de6b20226c4 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -12,7 +12,7 @@ import { Env } from '@kbn/config'; import { schema } from '@kbn/config-schema'; import { fromRoot } from '@kbn/utils'; -import { IRouter, IBasePath, IKibanaResponse, KibanaResponseFactory } from '../http'; +import { IRouter, IBasePath, IKibanaResponse, KibanaResponseFactory, KibanaRequest } from '../http'; import { HttpResources, HttpResourcesServiceToolkit } from '../http_resources'; import { InternalCorePreboot, InternalCoreSetup } from '../internal_types'; import { CoreContext } from '../core_context'; @@ -27,6 +27,7 @@ interface CommonRoutesParams { basePath: IBasePath; uiPlugins: UiPlugins; onResourceNotFound: ( + req: KibanaRequest, res: HttpResourcesServiceToolkit & KibanaResponseFactory ) => Promise; } @@ -64,7 +65,20 @@ export class CoreApp { httpResources: corePreboot.httpResources.createRegistrar(router), router, uiPlugins, - onResourceNotFound: (res) => res.renderCoreApp(), + onResourceNotFound: async (req, res) => + // THe API consumers might call various Kibana APIs (e.g. `/api/status`) when Kibana is still at the preboot + // stage, and the main HTTP server that registers API handlers isn't up yet. At this stage we don't know if + // the API endpoint exists or not, and hence cannot reply with `404`. We also should not reply with completely + // unexpected response (`200 text/html` for the Core app). The only suitable option is to reply with `503` + // like we do for all other unknown non-GET requests at the preboot stage. + req.route.path.startsWith('/api/') || req.route.path.startsWith('/internal/') + ? res.customError({ + statusCode: 503, + headers: { 'Retry-After': '30' }, + body: 'Kibana server is not ready yet', + bypassErrorFormat: true, + }) + : res.renderCoreApp(), }); }); } @@ -91,7 +105,7 @@ export class CoreApp { httpResources: resources, router, uiPlugins, - onResourceNotFound: async (res) => res.notFound(), + onResourceNotFound: async (req, res) => res.notFound(), }); resources.register( @@ -148,7 +162,7 @@ export class CoreApp { const { query, params } = req; const { path } = params; if (!path || !path.endsWith('/') || path.startsWith('/')) { - return onResourceNotFound(res); + return onResourceNotFound(req, res); } // remove trailing slash diff --git a/src/core/server/rendering/bootstrap/__snapshots__/render_template.test.ts.snap b/src/core/server/rendering/bootstrap/__snapshots__/render_template.test.ts.snap index fde12088b77358..83aacda2b599ac 100644 --- a/src/core/server/rendering/bootstrap/__snapshots__/render_template.test.ts.snap +++ b/src/core/server/rendering/bootstrap/__snapshots__/render_template.test.ts.snap @@ -33,7 +33,7 @@ function kbnBundlesLoader() { var kbnCsp = JSON.parse(document.querySelector('kbn-csp').getAttribute('data')); window.__kbnStrictCsp__ = kbnCsp.strictCsp; -window.__kbnThemeTag__ = \\"v7\\"; +window.__kbnThemeTag__ = \\"v8light\\"; window.__kbnPublicPath__ = {\\"foo\\": \\"bar\\"}; window.__kbnBundles__ = kbnBundlesLoader(); diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts index 36551def5eef03..efa47d810c273c 100644 --- a/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts @@ -85,15 +85,12 @@ describe('bootstrapRenderer', () => { uiSettingsClient, }); - expect(uiSettingsClient.get).toHaveBeenCalledTimes(2); + expect(uiSettingsClient.get).toHaveBeenCalledTimes(1); expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:darkMode'); - expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:version'); }); it('calls getThemeTag with the correct parameters', async () => { - uiSettingsClient.get.mockImplementation((settingName) => { - return Promise.resolve(settingName === 'theme:darkMode' ? true : 'v8'); - }); + uiSettingsClient.get.mockResolvedValue(true); const request = httpServerMock.createKibanaRequest(); @@ -126,15 +123,12 @@ describe('bootstrapRenderer', () => { uiSettingsClient, }); - expect(uiSettingsClient.get).toHaveBeenCalledTimes(2); + expect(uiSettingsClient.get).toHaveBeenCalledTimes(1); expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:darkMode'); - expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:version'); }); it('calls getThemeTag with the correct parameters', async () => { - uiSettingsClient.get.mockImplementation((settingName) => { - return Promise.resolve(settingName === 'theme:darkMode' ? true : 'v8'); - }); + uiSettingsClient.get.mockResolvedValue(true); const request = httpServerMock.createKibanaRequest(); diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.ts index c1268d49d2b7d8..fb4db50e2e34ce 100644 --- a/src/core/server/rendering/bootstrap/bootstrap_renderer.ts +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.ts @@ -8,6 +8,7 @@ import { createHash } from 'crypto'; import { PackageInfo } from '@kbn/config'; +import { ThemeVersion } from '@kbn/ui-shared-deps-npm'; import { UiPlugins } from '../../plugins'; import { IUiSettingsClient } from '../../ui_settings'; import { HttpAuth, KibanaRequest } from '../../http'; @@ -50,12 +51,11 @@ export const bootstrapRendererFactory: BootstrapRendererFactory = ({ return async function bootstrapRenderer({ uiSettingsClient, request }) { let darkMode = false; - let themeVersion = 'v8'; + const themeVersion: ThemeVersion = 'v8'; try { const authenticated = isAuthenticated(request); darkMode = authenticated ? await uiSettingsClient.get('theme:darkMode') : false; - themeVersion = authenticated ? await uiSettingsClient.get('theme:version') : 'v8'; } catch (e) { // just use the default values in case of connectivity issues with ES } diff --git a/src/core/server/rendering/bootstrap/get_theme_tag.test.ts b/src/core/server/rendering/bootstrap/get_theme_tag.test.ts index 6fe56d425c13d3..3bc782707f5ad7 100644 --- a/src/core/server/rendering/bootstrap/get_theme_tag.test.ts +++ b/src/core/server/rendering/bootstrap/get_theme_tag.test.ts @@ -9,22 +9,6 @@ import { getThemeTag } from './get_theme_tag'; describe('getThemeTag', () => { - it('returns the correct value for version:v7 and darkMode:false', () => { - expect( - getThemeTag({ - themeVersion: 'v7', - darkMode: false, - }) - ).toEqual('v7light'); - }); - it('returns the correct value for version:v7 and darkMode:true', () => { - expect( - getThemeTag({ - themeVersion: 'v7', - darkMode: true, - }) - ).toEqual('v7dark'); - }); it('returns the correct value for version:v8 and darkMode:false', () => { expect( getThemeTag({ diff --git a/src/core/server/rendering/bootstrap/get_theme_tag.ts b/src/core/server/rendering/bootstrap/get_theme_tag.ts index f722de6f971f37..058e5dd9e1c1a0 100644 --- a/src/core/server/rendering/bootstrap/get_theme_tag.ts +++ b/src/core/server/rendering/bootstrap/get_theme_tag.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import type { ThemeVersion } from '@kbn/ui-shared-deps-npm'; + /** * Computes the themeTag that will be used on the client-side as `__kbnThemeTag__` * @see `packages/kbn-ui-shared-deps-src/theme.ts` @@ -14,8 +16,8 @@ export const getThemeTag = ({ themeVersion, darkMode, }: { - themeVersion: string; + themeVersion: ThemeVersion; darkMode: boolean; }) => { - return `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`; + return `${themeVersion}${darkMode ? 'dark' : 'light'}`; }; diff --git a/src/core/server/rendering/bootstrap/render_template.test.ts b/src/core/server/rendering/bootstrap/render_template.test.ts index b061e8c31065c9..7aeee9c9c075be 100644 --- a/src/core/server/rendering/bootstrap/render_template.test.ts +++ b/src/core/server/rendering/bootstrap/render_template.test.ts @@ -10,7 +10,7 @@ import { renderTemplate } from './render_template'; function mockParams() { return { - themeTag: 'v7', + themeTag: 'v8light', jsDependencyPaths: ['/js-1', '/js-2'], styleSheetPaths: ['/style-1', '/style-2'], publicPathMap: '{"foo": "bar"}', diff --git a/src/core/server/rendering/render_utils.test.ts b/src/core/server/rendering/render_utils.test.ts index 1eb7db5edd8df7..c8ebd6de4f854c 100644 --- a/src/core/server/rendering/render_utils.test.ts +++ b/src/core/server/rendering/render_utils.test.ts @@ -10,25 +10,6 @@ import { getStylesheetPaths } from './render_utils'; describe('getStylesheetPaths', () => { describe('when darkMode is `true`', () => { - describe('when themeVersion is `v7`', () => { - it('returns the correct list', () => { - expect( - getStylesheetPaths({ - darkMode: true, - themeVersion: 'v7', - basePath: '/base-path', - buildNum: 9000, - }) - ).toMatchInlineSnapshot(` - Array [ - "/base-path/9000/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v7.dark.css", - "/base-path/9000/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css", - "/base-path/node_modules/@kbn/ui-framework/dist/kui_dark.css", - "/base-path/ui/legacy_dark_theme.css", - ] - `); - }); - }); describe('when themeVersion is `v8`', () => { it('returns the correct list', () => { expect( @@ -50,25 +31,6 @@ describe('getStylesheetPaths', () => { }); }); describe('when darkMode is `false`', () => { - describe('when themeVersion is `v7`', () => { - it('returns the correct list', () => { - expect( - getStylesheetPaths({ - darkMode: false, - themeVersion: 'v7', - basePath: '/base-path', - buildNum: 42, - }) - ).toMatchInlineSnapshot(` - Array [ - "/base-path/42/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v7.light.css", - "/base-path/42/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css", - "/base-path/node_modules/@kbn/ui-framework/dist/kui_light.css", - "/base-path/ui/legacy_light_theme.css", - ] - `); - }); - }); describe('when themeVersion is `v8`', () => { it('returns the correct list', () => { expect( diff --git a/src/core/server/rendering/render_utils.ts b/src/core/server/rendering/render_utils.ts index c3d27a1aa25fe2..65cedda7ad4892 100644 --- a/src/core/server/rendering/render_utils.ts +++ b/src/core/server/rendering/render_utils.ts @@ -28,7 +28,7 @@ export const getStylesheetPaths = ({ basePath, buildNum, }: { - themeVersion: string; + themeVersion: UiSharedDepsNpm.ThemeVersion; darkMode: boolean; buildNum: number; basePath: string; @@ -37,17 +37,17 @@ export const getStylesheetPaths = ({ return [ ...(darkMode ? [ - themeVersion === 'v7' - ? `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.darkCssDistFilename}` - : `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.darkV8CssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.darkCssDistFilename( + themeVersion + )}`, `${regularBundlePath}/kbn-ui-shared-deps-src/${UiSharedDepsSrc.cssDistFilename}`, `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, `${basePath}/ui/legacy_dark_theme.css`, ] : [ - themeVersion === 'v7' - ? `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename}` - : `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightV8CssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename( + themeVersion + )}`, `${regularBundlePath}/kbn-ui-shared-deps-src/${UiSharedDepsSrc.cssDistFilename}`, `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, `${basePath}/ui/legacy_light_theme.css`, diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index f75d405fe8bf92..c989e75285077f 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -112,9 +112,6 @@ function renderTestCases( if (settingName === 'theme:darkMode') { return true; } - if (settingName === 'theme:version') { - return 'v8'; - } return settingName; }); diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index f8b99686ff557c..b1c6971d3c42b2 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { take } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; +import type { ThemeVersion } from '@kbn/ui-shared-deps-npm'; import { UiPlugins } from '../plugins'; import { CoreContext } from '../core_context'; @@ -93,7 +94,7 @@ export class RenderingService { }; const darkMode = getSettingValue('theme:darkMode', settings, Boolean); - const themeVersion = getSettingValue('theme:version', settings, String); + const themeVersion: ThemeVersion = 'v8'; const stylesheetPaths = getStylesheetPaths({ darkMode, @@ -109,8 +110,8 @@ export class RenderingService { i18n: i18n.translate, locale: i18n.getLocale(), darkMode, - stylesheetPaths, themeVersion, + stylesheetPaths, injectedMetadata: { version: env.packageInfo.version, buildNumber: env.packageInfo.buildNum, diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 8089878ccefd0c..ca6bab0dff1f89 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -7,6 +7,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { ThemeVersion } from '@kbn/ui-shared-deps-npm'; import { EnvironmentMode, PackageInfo } from '../config'; import { ICspConfig } from '../csp'; @@ -24,7 +25,7 @@ export interface RenderingMetadata { i18n: typeof i18n.translate; locale: string; darkMode: boolean; - themeVersion?: string; + themeVersion: ThemeVersion; stylesheetPaths: string[]; injectedMetadata: { version: string; diff --git a/src/core/server/rendering/views/fonts.tsx b/src/core/server/rendering/views/fonts.tsx index 93de5184bb357f..44030620454e71 100644 --- a/src/core/server/rendering/views/fonts.tsx +++ b/src/core/server/rendering/views/fonts.tsx @@ -14,7 +14,6 @@ import { RenderingMetadata } from '../types'; interface Props { url: RenderingMetadata['uiPublicUrl']; - themeVersion?: string; } interface FontFace { @@ -165,158 +164,6 @@ const getInter = (url: string): FontFace => { }; }; -const getInterUi = (url: string): FontFace => { - return { - family: 'Inter UI', - variants: [ - { - style: 'normal', - weight: 100, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-Thin-BETA.woff2`, - `${url}/fonts/inter_ui/Inter-UI-Thin-BETA.woff`, - ], - }, - { - style: 'italic', - weight: 100, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2`, - `${url}/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff`, - ], - }, - { - style: 'normal', - weight: 200, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2`, - `${url}/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff`, - ], - }, - { - style: 'italic', - weight: 200, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2`, - `${url}/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff`, - ], - }, - { - style: 'normal', - weight: 300, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-Light-BETA.woff2`, - `${url}/fonts/inter_ui/Inter-UI-Light-BETA.woff`, - ], - }, - { - style: 'italic', - weight: 300, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2`, - `${url}/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff`, - ], - }, - { - style: 'normal', - weight: 400, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-Regular.woff2`, - `${url}/fonts/inter_ui/Inter-UI-Regular.woff`, - ], - }, - { - style: 'italic', - weight: 400, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-Italic.woff2`, - `${url}/fonts/inter_ui/Inter-UI-Italic.woff`, - ], - }, - { - style: 'normal', - weight: 500, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-Medium.woff2`, - `${url}/fonts/inter_ui/Inter-UI-Medium.woff`, - ], - }, - { - style: 'italic', - weight: 500, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-MediumItalic.woff2`, - `${url}/fonts/inter_ui/Inter-UI-MediumItalic.woff`, - ], - }, - { - style: 'normal', - weight: 600, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-SemiBold.woff2`, - `${url}/fonts/inter_ui/Inter-UI-SemiBold.woff`, - ], - }, - { - style: 'italic', - weight: 600, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2`, - `${url}/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff`, - ], - }, - { - style: 'normal', - weight: 700, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-Bold.woff2`, - `${url}/fonts/inter_ui/Inter-UI-Bold.woff`, - ], - }, - { - style: 'italic', - weight: 700, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-BoldItalic.woff2`, - `${url}/fonts/inter_ui/Inter-UI-BoldItalic.woff`, - ], - }, - { - style: 'normal', - weight: 800, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-ExtraBold.woff2`, - `${url}/fonts/inter_ui/Inter-UI-ExtraBold.woff`, - ], - }, - { - style: 'italic', - weight: 800, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2`, - `${url}/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff`, - ], - }, - { - style: 'normal', - weight: 900, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-Black.woff2`, - `${url}/fonts/inter_ui/Inter-UI-Black.woff`, - ], - }, - { - style: 'italic', - weight: 900, - sources: [ - `${url}/fonts/inter_ui/Inter-UI-BlackItalic.woff2`, - `${url}/fonts/inter_ui/Inter-UI-BlackItalic.woff`, - ], - }, - ], - }; -}; - const getRoboto = (url: string): FontFace => { return { family: 'Roboto Mono', @@ -372,11 +219,8 @@ const getRoboto = (url: string): FontFace => { }; }; -export const Fonts: FunctionComponent = ({ url, themeVersion }) => { - /** - * If `themeVersion` is not provided, we want to fallback to the newest font family `Inter` - */ - const sansFont = themeVersion === 'v7' ? getInterUi(url) : getInter(url); +export const Fonts: FunctionComponent = ({ url }) => { + const sansFont = getInter(url); const codeFont = getRoboto(url); return ( diff --git a/src/core/server/rendering/views/template.tsx b/src/core/server/rendering/views/template.tsx index e1a9a8a8c3f1dd..971a9e22e4fccd 100644 --- a/src/core/server/rendering/views/template.tsx +++ b/src/core/server/rendering/views/template.tsx @@ -22,7 +22,6 @@ export const Template: FunctionComponent = ({ uiPublicUrl, locale, darkMode, - themeVersion, stylesheetPaths, injectedMetadata, i18n, @@ -37,7 +36,7 @@ export const Template: FunctionComponent = ({ Elastic - + {/* The alternate icon is a fallback for Safari which does not yet support SVG favicons */} diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index 599b5dca0d9042..fe3d6c469726d2 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -46,15 +46,6 @@ describe('KibanaMigrator', () => { beforeEach(() => { (DocumentMigrator as jest.Mock).mockClear(); }); - describe('constructor', () => { - it('coerces the current Kibana version if it has a hyphen', () => { - const options = mockOptions(); - options.kibanaVersion = '3.2.1-SNAPSHOT'; - const migrator = new KibanaMigrator(options); - expect(migrator.kibanaVersion).toEqual('3.2.1'); - expect((DocumentMigrator as jest.Mock).mock.calls[0][0].kibanaVersion).toEqual('3.2.1'); - }); - }); describe('getActiveMappings', () => { it('returns full index mappings w/ core properties', () => { const options = mockOptions(); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index a812339cef07ef..198983538c93da 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -88,7 +88,7 @@ export class KibanaMigrator { this.serializer = new SavedObjectsSerializer(this.typeRegistry); this.mappingProperties = mergeTypes(this.typeRegistry.getAllTypes()); this.log = logger; - this.kibanaVersion = kibanaVersion.split('-')[0]; // coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha) to a regular semver (x.y.z); + this.kibanaVersion = kibanaVersion; this.documentMigrator = new DocumentMigrator({ kibanaVersion: this.kibanaVersion, typeRegistry, diff --git a/src/core/server/saved_objects/migrationsv2/README.md b/src/core/server/saved_objects/migrationsv2/README.md index a6b8e01a3dc6c9..60bf84eef87a6d 100644 --- a/src/core/server/saved_objects/migrationsv2/README.md +++ b/src/core/server/saved_objects/migrationsv2/README.md @@ -90,7 +90,7 @@ able to successfully complete the migration once the cluster has enough heap. For more background information on the problem see the [saved object migrations -RFC](https://github.com/elastic/kibana/blob/master/rfcs/text/0013_saved_object_migrations.md). +RFC](https://github.com/elastic/kibana/blob/main/rfcs/text/0013_saved_object_migrations.md). # Algorithm steps The design goals for the algorithm was to keep downtime below 10 minutes for diff --git a/src/core/server/saved_objects/object_types/registration.ts b/src/core/server/saved_objects/object_types/registration.ts index ce108967471783..a199ce947f96b2 100644 --- a/src/core/server/saved_objects/object_types/registration.ts +++ b/src/core/server/saved_objects/object_types/registration.ts @@ -16,8 +16,9 @@ const legacyUrlAliasType: SavedObjectsType = { dynamic: false, properties: { sourceId: { type: 'keyword' }, - targetType: { type: 'keyword' }, targetNamespace: { type: 'keyword' }, + targetType: { type: 'keyword' }, + targetId: { type: 'keyword' }, resolveCounter: { type: 'long' }, disabled: { type: 'boolean' }, // other properties exist, but we aren't querying or aggregating on those, so we don't need to specify them (because we use `dynamic: false` above) diff --git a/src/core/server/saved_objects/saved_objects_service.test.mocks.ts b/src/core/server/saved_objects/saved_objects_service.test.mocks.ts index d0ac98fa5e3c7a..1faebcc5fcc97c 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.mocks.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.mocks.ts @@ -25,3 +25,8 @@ export const typeRegistryInstanceMock = typeRegistryMock.create(); jest.doMock('./saved_objects_type_registry', () => ({ SavedObjectTypeRegistry: jest.fn().mockImplementation(() => typeRegistryInstanceMock), })); + +export const registerRoutesMock = jest.fn(); +jest.doMock('./routes', () => ({ + registerRoutes: registerRoutesMock, +})); diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index 7321e760273bfd..a4f6c019c96247 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -6,17 +6,25 @@ * Side Public License, v 1. */ +import { join } from 'path'; +import loadJsonFile from 'load-json-file'; + import { - migratorInstanceMock, clientProviderInstanceMock, + KibanaMigratorMock, + migratorInstanceMock, + registerRoutesMock, typeRegistryInstanceMock, } from './saved_objects_service.test.mocks'; import { BehaviorSubject } from 'rxjs'; +import { RawPackageInfo } from '@kbn/config'; import { ByteSizeValue } from '@kbn/config-schema'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { SavedObjectsService } from './saved_objects_service'; import { mockCoreContext } from '../core_context.mock'; import { Env } from '../config'; +import { getEnvOptions } from '../config/mocks'; import { configServiceMock } from '../mocks'; import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; import { coreUsageDataServiceMock } from '../core_usage_data/core_usage_data_service.mock'; @@ -108,6 +116,42 @@ describe('SavedObjectsService', () => { expect(mockRegistry.registerDeprecations).toHaveBeenCalledWith(deprecations); }); + it('registers the deprecation provider with the correct kibanaVersion', async () => { + const pkg = loadJsonFile.sync(join(REPO_ROOT, 'package.json')) as RawPackageInfo; + const kibanaVersion = pkg.version; + + const coreContext = createCoreContext({ + env: Env.createDefault(REPO_ROOT, getEnvOptions(), { + ...pkg, + version: `${kibanaVersion}-beta1`, // test behavior when release has a version qualifier + }), + }); + + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + + expect(getSavedObjectsDeprecationsProvider).toHaveBeenCalledWith( + expect.objectContaining({ kibanaVersion }) + ); + }); + + it('calls registerRoutes with the correct kibanaVersion', async () => { + const pkg = loadJsonFile.sync(join(REPO_ROOT, 'package.json')) as RawPackageInfo; + const kibanaVersion = pkg.version; + + const coreContext = createCoreContext({ + env: Env.createDefault(REPO_ROOT, getEnvOptions(), { + ...pkg, + version: `${kibanaVersion}-beta1`, // test behavior when release has a version qualifier + }), + }); + + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + + expect(registerRoutesMock).toHaveBeenCalledWith(expect.objectContaining({ kibanaVersion })); + }); + describe('#setClientFactoryProvider', () => { it('registers the factory to the clientProvider', async () => { const coreContext = createCoreContext(); @@ -218,6 +262,24 @@ describe('SavedObjectsService', () => { expect(migratorInstanceMock.runMigrations).not.toHaveBeenCalled(); }); + it('calls KibanaMigrator with correct version', async () => { + const pkg = loadJsonFile.sync(join(REPO_ROOT, 'package.json')) as RawPackageInfo; + const kibanaVersion = pkg.version; + + const coreContext = createCoreContext({ + env: Env.createDefault(REPO_ROOT, getEnvOptions(), { + ...pkg, + version: `${kibanaVersion}-beta1`, // test behavior when release has a version qualifier + }), + }); + + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + await soService.start(createStartDeps()); + + expect(KibanaMigratorMock).toHaveBeenCalledWith(expect.objectContaining({ kibanaVersion })); + }); + it('waits for all es nodes to be compatible before running migrations', async (done) => { expect.assertions(2); const coreContext = createCoreContext({ skipMigration: false }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 33d75c38f43695..baa1636dde13f7 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -278,6 +278,7 @@ export class SavedObjectsService implements CoreService { private logger: Logger; + private readonly kibanaVersion: string; private setupDeps?: SavedObjectsSetupDeps; private config?: SavedObjectConfig; @@ -290,6 +291,9 @@ export class SavedObjectsService constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('savedobjects-service'); + this.kibanaVersion = SavedObjectsService.stripVersionQualifier( + this.coreContext.env.packageInfo.version + ); } public async setup(setupDeps: SavedObjectsSetupDeps): Promise { @@ -312,7 +316,7 @@ export class SavedObjectsService getSavedObjectsDeprecationsProvider({ kibanaIndex, savedObjectsConfig: this.config, - kibanaVersion: this.coreContext.env.packageInfo.version, + kibanaVersion: this.kibanaVersion, typeRegistry: this.typeRegistry, }) ); @@ -326,7 +330,7 @@ export class SavedObjectsService config: this.config, migratorPromise: this.migrator$.pipe(first()).toPromise(), kibanaIndex, - kibanaVersion: this.coreContext.env.packageInfo.version, + kibanaVersion: this.kibanaVersion, }); registerCoreObjectTypes(this.typeRegistry); @@ -502,11 +506,19 @@ export class SavedObjectsService return new KibanaMigrator({ typeRegistry: this.typeRegistry, logger: this.logger, - kibanaVersion: this.coreContext.env.packageInfo.version, + kibanaVersion: this.kibanaVersion, soMigrationsConfig, kibanaIndex, client, migrationsRetryDelay, }); } + + /** + * Coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha) + * to regular semver (x.y.z). + */ + private static stripVersionQualifier(version: string) { + return version.split('-')[0]; + } } diff --git a/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.test.mock.ts b/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.test.mock.ts index 382212cfbbd11f..728f3b847b6317 100644 --- a/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.test.mock.ts +++ b/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.test.mock.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { findLegacyUrlAliases } from './find_legacy_url_aliases'; +import type { findLegacyUrlAliases } from './legacy_url_aliases'; import type * as InternalUtils from './internal_utils'; export const mockFindLegacyUrlAliases = jest.fn() as jest.MockedFunction< typeof findLegacyUrlAliases >; -jest.mock('./find_legacy_url_aliases', () => { +jest.mock('./legacy_url_aliases', () => { return { findLegacyUrlAliases: mockFindLegacyUrlAliases }; }); diff --git a/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.ts b/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.ts index 87bb5017aab956..fd2afea999a079 100644 --- a/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.ts +++ b/src/core/server/saved_objects/service/lib/collect_multi_namespace_references.ts @@ -9,7 +9,7 @@ import type { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import type { SavedObjectsSerializer } from '../../serialization'; import type { SavedObject, SavedObjectsBaseOptions } from '../../types'; -import { findLegacyUrlAliases } from './find_legacy_url_aliases'; +import { findLegacyUrlAliases } from './legacy_url_aliases'; import { getRootFields } from './included_fields'; import { getObjectKey, diff --git a/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts b/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts new file mode 100644 index 00000000000000..9585c40e6a161a --- /dev/null +++ b/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.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 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 { getErrorMessage } from '../../../../elasticsearch'; + +export const mockGetEsErrorMessage = jest.fn() as jest.MockedFunction; + +jest.mock('../../../../elasticsearch', () => { + return { getErrorMessage: mockGetEsErrorMessage }; +}); + +// Mock these functions to return empty results, as this simplifies test cases and we don't need to exercise alternate code paths for these +jest.mock('@kbn/es-query', () => { + return { nodeTypes: { function: { buildNode: jest.fn() } } }; +}); +jest.mock('../search_dsl', () => { + return { getSearchDsl: jest.fn() }; +}); diff --git a/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts b/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts new file mode 100644 index 00000000000000..22c57fe3f280f4 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts @@ -0,0 +1,152 @@ +/* + * 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 { mockGetEsErrorMessage } from './delete_legacy_url_aliases.test.mock'; // Note: importing this file applies default mocks for other functions too + +import { errors as EsErrors } from '@elastic/elasticsearch'; + +import { elasticsearchClientMock } from '../../../../elasticsearch/client/mocks'; +import { typeRegistryMock } from '../../../saved_objects_type_registry.mock'; +import { ALL_NAMESPACES_STRING } from '../utils'; +import { deleteLegacyUrlAliases } from './delete_legacy_url_aliases'; +import type { DeleteLegacyUrlAliasesParams } from './delete_legacy_url_aliases'; + +type SetupParams = Pick< + DeleteLegacyUrlAliasesParams, + 'type' | 'id' | 'namespaces' | 'deleteBehavior' +>; + +describe('deleteLegacyUrlAliases', () => { + function setup(setupParams: SetupParams) { + return { + mappings: { properties: {} }, // doesn't matter, only used as an argument to getSearchDsl which is mocked + registry: typeRegistryMock.create(), // doesn't matter, only used as an argument to getSearchDsl which is mocked + client: elasticsearchClientMock.createElasticsearchClient(), + getIndexForType: jest.fn(), // doesn't matter + ...setupParams, + }; + } + + const type = 'obj-type'; + const id = 'obj-id'; + + it('throws an error if namespaces includes the "all namespaces" string', async () => { + const namespaces = [ALL_NAMESPACES_STRING]; + const params = setup({ type, id, namespaces, deleteBehavior: 'inclusive' }); + + await expect(() => deleteLegacyUrlAliases(params)).rejects.toThrowError( + `Failed to delete legacy URL aliases for ${type}/${id}: "namespaces" cannot include the * string` + ); + expect(params.client.updateByQuery).not.toHaveBeenCalled(); + }); + + it('throws an error if updateByQuery fails', async () => { + const namespaces = ['space-a', 'space-b']; + const params = setup({ type, id, namespaces, deleteBehavior: 'inclusive' }); + const esError = new EsErrors.ResponseError( + elasticsearchClientMock.createApiResponse({ + statusCode: 500, + body: { error: { type: 'es_type', reason: 'es_reason' } }, + }) + ); + params.client.updateByQuery.mockResolvedValueOnce( + elasticsearchClientMock.createErrorTransportRequestPromise(esError) + ); + mockGetEsErrorMessage.mockClear(); + mockGetEsErrorMessage.mockReturnValue('Oh no!'); + + await expect(() => deleteLegacyUrlAliases(params)).rejects.toThrowError( + `Failed to delete legacy URL aliases for ${type}/${id}: Oh no!` + ); + expect(params.client.updateByQuery).toHaveBeenCalledTimes(1); + expect(mockGetEsErrorMessage).toHaveBeenCalledTimes(1); + expect(mockGetEsErrorMessage).toHaveBeenCalledWith(esError); + }); + + describe('deleteBehavior "inclusive"', () => { + const deleteBehavior = 'inclusive' as const; + + it('when filtered namespaces is not empty, returns early', async () => { + const namespaces = ['default']; + const params = setup({ type, id, namespaces, deleteBehavior }); + + await deleteLegacyUrlAliases(params); + expect(params.client.updateByQuery).not.toHaveBeenCalled(); + }); + + it('when filtered namespaces is not empty, calls updateByQuery with expected script params', async () => { + const namespaces = ['space-a', 'default', 'space-b']; + const params = setup({ type, id, namespaces, deleteBehavior }); + + await deleteLegacyUrlAliases(params); + expect(params.client.updateByQuery).toHaveBeenCalledTimes(1); + expect(params.client.updateByQuery).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + script: expect.objectContaining({ + params: { + namespaces: ['space-a', 'space-b'], // 'default' is filtered out + matchTargetNamespaceOp: 'delete', + notMatchTargetNamespaceOp: 'noop', + }, + }), + }), + }), + expect.anything() + ); + }); + }); + + describe('deleteBehavior "exclusive"', () => { + const deleteBehavior = 'exclusive' as const; + + it('when filtered namespaces is empty, calls updateByQuery with expected script params', async () => { + const namespaces = ['default']; + const params = setup({ type, id, namespaces, deleteBehavior }); + + await deleteLegacyUrlAliases(params); + expect(params.client.updateByQuery).toHaveBeenCalledTimes(1); + expect(params.client.updateByQuery).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + script: expect.objectContaining({ + params: { + namespaces: [], // 'default' is filtered out + matchTargetNamespaceOp: 'noop', + notMatchTargetNamespaceOp: 'delete', + }, + }), + }), + }), + expect.anything() + ); + }); + + it('when filtered namespaces is not empty, calls updateByQuery with expected script params', async () => { + const namespaces = ['space-a', 'default', 'space-b']; + const params = setup({ type, id, namespaces, deleteBehavior }); + + await deleteLegacyUrlAliases(params); + expect(params.client.updateByQuery).toHaveBeenCalledTimes(1); + expect(params.client.updateByQuery).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + script: expect.objectContaining({ + params: { + namespaces: ['space-a', 'space-b'], // 'default' is filtered out + matchTargetNamespaceOp: 'noop', + notMatchTargetNamespaceOp: 'delete', + }, + }), + }), + }), + expect.anything() + ); + }); + }); +}); diff --git a/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.ts b/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.ts new file mode 100644 index 00000000000000..59c73d1f781a23 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.ts @@ -0,0 +1,113 @@ +/* + * 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 esKuery from '@kbn/es-query'; + +import { getErrorMessage as getEsErrorMessage } from '../../../../elasticsearch'; +import type { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; +import type { IndexMapping } from '../../../mappings'; +import { LEGACY_URL_ALIAS_TYPE } from '../../../object_types'; +import type { RepositoryEsClient } from '../repository_es_client'; +import { getSearchDsl } from '../search_dsl'; +import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; + +/** @internal */ +export interface DeleteLegacyUrlAliasesParams { + mappings: IndexMapping; + registry: ISavedObjectTypeRegistry; + client: RepositoryEsClient; + getIndexForType: (type: string) => string; + /** The object type. */ + type: string; + /** The object ID. */ + id: string; + /** + * The namespaces to include or exclude when searching for legacy URL alias targets (depends on the `deleteBehavior` parameter). + * Note that using `namespaces: [], deleteBehavior: 'exclusive'` will delete all aliases for this object in all spaces. + */ + namespaces: string[]; + /** + * If this is equal to 'inclusive', all aliases with a `targetNamespace` in the `namespaces` array will be deleted. + * If this is equal to 'exclusive', all aliases with a `targetNamespace` _not_ in the `namespaces` array will be deleted. + */ + deleteBehavior: 'inclusive' | 'exclusive'; +} + +/** + * Deletes legacy URL aliases that point to a given object. + * + * Note that aliases are only created when an object is converted to become share-capable, and each targetId is deterministically generated + * with uuidv5 -- this means that the chances of there actually being _multiple_ legacy URL aliases that target a given type/ID are slim to + * none. However, we don't always know exactly what space an alias could be in (if an object exists in multiple spaces, or in all spaces), + * so the most straightforward way for us to ensure that aliases are reliably deleted is to use updateByQuery, which is what this function + * does. + * + * @internal + */ +export async function deleteLegacyUrlAliases(params: DeleteLegacyUrlAliasesParams) { + const { mappings, registry, client, getIndexForType, type, id, namespaces, deleteBehavior } = + params; + + if (namespaces.includes(ALL_NAMESPACES_STRING)) { + throwError(type, id, '"namespaces" cannot include the * string'); + } + + // Legacy URL aliases cannot exist in the default space; filter that out + const filteredNamespaces = namespaces.filter( + (namespace) => namespace !== DEFAULT_NAMESPACE_STRING + ); + if (!filteredNamespaces.length && deleteBehavior === 'inclusive') { + // nothing to do, return early + return; + } + + const { buildNode } = esKuery.nodeTypes.function; + const match1 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.targetType`, type); + const match2 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.targetId`, id); + const kueryNode = buildNode('and', [match1, match2]); + + try { + await client.updateByQuery( + { + index: getIndexForType(LEGACY_URL_ALIAS_TYPE), + refresh: false, // This could be called many times in succession, intentionally do not wait for a refresh + body: { + ...getSearchDsl(mappings, registry, { + type: LEGACY_URL_ALIAS_TYPE, + kueryNode, + }), + script: { + // Intentionally use one script source with variable params to take advantage of ES script caching + source: ` + if (params['namespaces'].indexOf(ctx._source['${LEGACY_URL_ALIAS_TYPE}']['targetNamespace']) > -1) { + ctx.op = params['matchTargetNamespaceOp']; + } else { + ctx.op = params['notMatchTargetNamespaceOp']; + } + `, + params: { + namespaces: filteredNamespaces, + matchTargetNamespaceOp: deleteBehavior === 'inclusive' ? 'delete' : 'noop', + notMatchTargetNamespaceOp: deleteBehavior === 'inclusive' ? 'noop' : 'delete', + }, + lang: 'painless', + }, + conflicts: 'proceed', + }, + }, + { ignore: [404] } + ); + } catch (err) { + const errorMessage = getEsErrorMessage(err); + throwError(type, id, `${errorMessage}`); + } +} + +function throwError(type: string, id: string, message: string) { + throw new Error(`Failed to delete legacy URL aliases for ${type}/${id}: ${message}`); +} diff --git a/src/core/server/saved_objects/service/lib/find_legacy_url_aliases.test.ts b/src/core/server/saved_objects/service/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts similarity index 94% rename from src/core/server/saved_objects/service/lib/find_legacy_url_aliases.test.ts rename to src/core/server/saved_objects/service/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts index 134ea26d53b7de..755fa5794b5755 100644 --- a/src/core/server/saved_objects/service/lib/find_legacy_url_aliases.test.ts +++ b/src/core/server/saved_objects/service/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts @@ -8,12 +8,12 @@ import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; -import { LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE } from '../../object_types'; +import { LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE } from '../../../object_types'; +import type { CreatePointInTimeFinderFn, PointInTimeFinder } from '../point_in_time_finder'; +import { savedObjectsPointInTimeFinderMock } from '../point_in_time_finder.mock'; +import type { ISavedObjectsRepository } from '../repository'; +import { savedObjectsRepositoryMock } from '../repository.mock'; import { findLegacyUrlAliases } from './find_legacy_url_aliases'; -import type { CreatePointInTimeFinderFn, PointInTimeFinder } from './point_in_time_finder'; -import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock'; -import type { ISavedObjectsRepository } from './repository'; -import { savedObjectsRepositoryMock } from './repository.mock'; describe('findLegacyUrlAliases', () => { let savedObjectsMock: jest.Mocked; diff --git a/src/core/server/saved_objects/service/lib/find_legacy_url_aliases.ts b/src/core/server/saved_objects/service/lib/legacy_url_aliases/find_legacy_url_aliases.ts similarity index 87% rename from src/core/server/saved_objects/service/lib/find_legacy_url_aliases.ts rename to src/core/server/saved_objects/service/lib/legacy_url_aliases/find_legacy_url_aliases.ts index aac022fc320984..7c1ce82129710c 100644 --- a/src/core/server/saved_objects/service/lib/find_legacy_url_aliases.ts +++ b/src/core/server/saved_objects/service/lib/legacy_url_aliases/find_legacy_url_aliases.ts @@ -7,9 +7,9 @@ */ import * as esKuery from '@kbn/es-query'; -import { LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE } from '../../object_types'; -import { getObjectKey } from './internal_utils'; -import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import { LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE } from '../../../object_types'; +import { getObjectKey } from '../internal_utils'; +import type { CreatePointInTimeFinderFn } from '../point_in_time_finder'; interface FindLegacyUrlAliasesObject { type: string; @@ -68,6 +68,7 @@ export async function findLegacyUrlAliases( function createAliasKueryFilter(objects: Array<{ type: string; id: string }>) { const { buildNode } = esKuery.nodeTypes.function; + // Note: these nodes include '.attributes' for type-level fields because these are eventually passed to `validateConvertFilterToKueryNode`, which requires it const kueryNodes = objects.reduce((acc, { type, id }) => { const match1 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.attributes.targetType`, type); const match2 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.attributes.sourceId`, id); diff --git a/src/core/server/saved_objects/service/lib/legacy_url_aliases/index.ts b/src/core/server/saved_objects/service/lib/legacy_url_aliases/index.ts new file mode 100644 index 00000000000000..ec10668940d721 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/legacy_url_aliases/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 { findLegacyUrlAliases } from './find_legacy_url_aliases'; + +export { deleteLegacyUrlAliases } from './delete_legacy_url_aliases'; +export type { DeleteLegacyUrlAliasesParams } from './delete_legacy_url_aliases'; diff --git a/src/core/server/saved_objects/service/lib/preflight_check_for_create.test.mock.ts b/src/core/server/saved_objects/service/lib/preflight_check_for_create.test.mock.ts index e774a178abd492..fe8076b51e5dd4 100644 --- a/src/core/server/saved_objects/service/lib/preflight_check_for_create.test.mock.ts +++ b/src/core/server/saved_objects/service/lib/preflight_check_for_create.test.mock.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { findLegacyUrlAliases } from './find_legacy_url_aliases'; +import type { findLegacyUrlAliases } from './legacy_url_aliases'; import type * as InternalUtils from './internal_utils'; export const mockFindLegacyUrlAliases = jest.fn() as jest.MockedFunction< typeof findLegacyUrlAliases >; -jest.mock('./find_legacy_url_aliases', () => { +jest.mock('./legacy_url_aliases', () => { return { findLegacyUrlAliases: mockFindLegacyUrlAliases }; }); diff --git a/src/core/server/saved_objects/service/lib/preflight_check_for_create.ts b/src/core/server/saved_objects/service/lib/preflight_check_for_create.ts index 6788cd8aa3abf6..e5b96a22631c17 100644 --- a/src/core/server/saved_objects/service/lib/preflight_check_for_create.ts +++ b/src/core/server/saved_objects/service/lib/preflight_check_for_create.ts @@ -13,7 +13,7 @@ import type { SavedObjectsRawDocSource, SavedObjectsSerializer, } from '../../serialization'; -import { findLegacyUrlAliases } from './find_legacy_url_aliases'; +import { findLegacyUrlAliases } from './legacy_url_aliases'; import { Either, rawDocExistsInNamespaces } from './internal_utils'; import { getObjectKey, isLeft, isRight } from './internal_utils'; import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 51ec81503956a3..f61a79ca9de662 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -14,6 +14,7 @@ import { mockUpdateObjectsSpaces, mockGetCurrentTime, mockPreflightCheckForCreate, + mockDeleteLegacyUrlAliases, } from './repository.test.mock'; import { SavedObjectsRepository } from './repository'; @@ -2394,9 +2395,11 @@ describe('SavedObjectsRepository', () => { const id = 'logstash-*'; const namespace = 'foo-namespace'; - const deleteSuccess = async (type, id, options) => { + const deleteSuccess = async (type, id, options, internalOptions = {}) => { + const { mockGetResponseValue } = internalOptions; if (registry.isMultiNamespace(type)) { - const mockGetResponse = getMockGetResponse({ type, id }, options?.namespace); + const mockGetResponse = + mockGetResponseValue ?? getMockGetResponse({ type, id }, options?.namespace); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(mockGetResponse) ); @@ -2409,6 +2412,11 @@ describe('SavedObjectsRepository', () => { return result; }; + beforeEach(() => { + mockDeleteLegacyUrlAliases.mockClear(); + mockDeleteLegacyUrlAliases.mockResolvedValue(); + }); + describe('client calls', () => { it(`should use the ES delete action when not using a multi-namespace type`, async () => { await deleteSuccess(type, id); @@ -2482,6 +2490,64 @@ describe('SavedObjectsRepository', () => { }); }); + describe('legacy URL aliases', () => { + it(`doesn't delete legacy URL aliases for single-namespace object types`, async () => { + await deleteSuccess(type, id, { namespace }); + expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); + }); + + // We intentionally do not include a test case for a multi-namespace object with a "not found" preflight result, because that throws + // an error (without deleting aliases) and we already have a test case for that + + it(`deletes legacy URL aliases for multi-namespace object types (all spaces)`, async () => { + const internalOptions = { + mockGetResponseValue: getMockGetResponse( + { type: MULTI_NAMESPACE_TYPE, id }, + ALL_NAMESPACES_STRING + ), + }; + await deleteSuccess(MULTI_NAMESPACE_TYPE, id, { namespace, force: true }, internalOptions); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id, + namespaces: [], + deleteBehavior: 'exclusive', + }) + ); + }); + + it(`deletes legacy URL aliases for multi-namespace object types (specific spaces)`, async () => { + await deleteSuccess(MULTI_NAMESPACE_TYPE, id, { namespace }); // this function mocks a preflight response with the given namespace by default + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id, + namespaces: [namespace], + deleteBehavior: 'inclusive', + }) + ); + }); + + it(`logs a message when deleteLegacyUrlAliases returns an error`, async () => { + client.get.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + getMockGetResponse({ type: MULTI_NAMESPACE_ISOLATED_TYPE, id, namespace }) + ) + ); + client.delete.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise({ result: 'deleted' }) + ); + mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); + await savedObjectsRepository.delete(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }); + expect(client.get).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith( + 'Unable to delete aliases when deleting an object: Oh no!' + ); + }); + }); + describe('errors', () => { const expectNotFoundError = async (type, id, options) => { await expect(savedObjectsRepository.delete(type, id, options)).rejects.toThrowError( diff --git a/src/core/server/saved_objects/service/lib/repository.test.mock.ts b/src/core/server/saved_objects/service/lib/repository.test.mock.ts index 88eb13e3ca46b8..3ec2cb0a5d8b95 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.mock.ts +++ b/src/core/server/saved_objects/service/lib/repository.test.mock.ts @@ -11,6 +11,7 @@ import type { internalBulkResolve } from './internal_bulk_resolve'; import type * as InternalUtils from './internal_utils'; import type { preflightCheckForCreate } from './preflight_check_for_create'; import type { updateObjectsSpaces } from './update_objects_spaces'; +import type { deleteLegacyUrlAliases } from './legacy_url_aliases'; export const mockCollectMultiNamespaceReferences = jest.fn() as jest.MockedFunction< typeof collectMultiNamespaceReferences @@ -60,3 +61,10 @@ export const pointInTimeFinderMock = jest.fn(); jest.doMock('./point_in_time_finder', () => ({ PointInTimeFinder: pointInTimeFinderMock, })); + +export const mockDeleteLegacyUrlAliases = jest.fn() as jest.MockedFunction< + typeof deleteLegacyUrlAliases +>; +jest.mock('./legacy_url_aliases', () => ({ + deleteLegacyUrlAliases: mockDeleteLegacyUrlAliases, +})); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 383801e790c76e..d538690fb19203 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -100,6 +100,7 @@ import { preflightCheckForCreate, PreflightCheckForCreateObject, } from './preflight_check_for_create'; +import { deleteLegacyUrlAliases } from './legacy_url_aliases'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -717,6 +718,25 @@ export class SavedObjectsRepository { const deleted = body.result === 'deleted'; if (deleted) { + const namespaces = preflightResult?.savedObjectNamespaces; + if (namespaces) { + // This is a multi-namespace object type, and it might have legacy URL aliases that need to be deleted. + await deleteLegacyUrlAliases({ + mappings: this._mappings, + registry: this._registry, + client: this.client, + getIndexForType: this.getIndexForType.bind(this), + type, + id, + ...(namespaces.includes(ALL_NAMESPACES_STRING) + ? { namespaces: [], deleteBehavior: 'exclusive' } // delete legacy URL aliases for this type/ID for all spaces + : { namespaces, deleteBehavior: 'inclusive' }), // delete legacy URL aliases for this type/ID for these specific spaces + }).catch((err) => { + // The object has already been deleted, but we caught an error when attempting to delete aliases. + // A consumer cannot attempt to delete the object again, so just log the error and swallow it. + this._logger.error(`Unable to delete aliases when deleting an object: ${err.message}`); + }); + } return {}; } @@ -1344,10 +1364,12 @@ export class SavedObjectsRepository { options?: SavedObjectsUpdateObjectsSpacesOptions ) { return updateObjectsSpaces({ + mappings: this._mappings, registry: this._registry, allowedTypes: this._allowedTypes, client: this.client, serializer: this._serializer, + logger: this._logger, getIndexForType: this.getIndexForType.bind(this), objects, spacesToAdd, @@ -1646,7 +1668,7 @@ export class SavedObjectsRepository { * * When using incrementCounter for collecting usage data, you need to ensure * that usage collection happens on a best-effort basis and doesn't - * negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/master/src/plugins/usage_collection/README.mdx#tracking-interactions-with-incrementcounter) + * negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/main/src/plugins/usage_collection/README.mdx#tracking-interactions-with-incrementcounter) * * @example * ```ts diff --git a/src/core/server/saved_objects/service/lib/update_objects_spaces.test.mock.ts b/src/core/server/saved_objects/service/lib/update_objects_spaces.test.mock.ts index d7aa762e01aab7..043975d5bb52b1 100644 --- a/src/core/server/saved_objects/service/lib/update_objects_spaces.test.mock.ts +++ b/src/core/server/saved_objects/service/lib/update_objects_spaces.test.mock.ts @@ -7,6 +7,7 @@ */ import type * as InternalUtils from './internal_utils'; +import type { deleteLegacyUrlAliases } from './legacy_url_aliases'; export const mockGetBulkOperationError = jest.fn() as jest.MockedFunction< typeof InternalUtils['getBulkOperationError'] @@ -27,3 +28,10 @@ jest.mock('./internal_utils', () => { rawDocExistsInNamespace: mockRawDocExistsInNamespace, }; }); + +export const mockDeleteLegacyUrlAliases = jest.fn() as jest.MockedFunction< + typeof deleteLegacyUrlAliases +>; +jest.mock('./legacy_url_aliases', () => ({ + deleteLegacyUrlAliases: mockDeleteLegacyUrlAliases, +})); diff --git a/src/core/server/saved_objects/service/lib/update_objects_spaces.test.ts b/src/core/server/saved_objects/service/lib/update_objects_spaces.test.ts index 11dbe6149878c1..d5b79b70a55a12 100644 --- a/src/core/server/saved_objects/service/lib/update_objects_spaces.test.ts +++ b/src/core/server/saved_objects/service/lib/update_objects_spaces.test.ts @@ -10,12 +10,14 @@ import { mockGetBulkOperationError, mockGetExpectedVersionProperties, mockRawDocExistsInNamespace, + mockDeleteLegacyUrlAliases, } from './update_objects_spaces.test.mock'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import type { ElasticsearchClient } from 'src/core/server/elasticsearch'; import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; +import { loggerMock } from '../../../logging/logger.mock'; import { typeRegistryMock } from '../../saved_objects_type_registry.mock'; import { SavedObjectsSerializer } from '../../serialization'; import type { @@ -23,6 +25,7 @@ import type { UpdateObjectsSpacesParams, } from './update_objects_spaces'; import { updateObjectsSpaces } from './update_objects_spaces'; +import { ALL_NAMESPACES_STRING } from './utils'; type SetupParams = Partial< Pick @@ -53,6 +56,8 @@ beforeEach(() => { mockGetExpectedVersionProperties.mockReturnValue(EXPECTED_VERSION_PROPS); mockRawDocExistsInNamespace.mockReset(); mockRawDocExistsInNamespace.mockReturnValue(true); // return true by default + mockDeleteLegacyUrlAliases.mockReset(); + mockDeleteLegacyUrlAliases.mockResolvedValue(); // resolve an empty promise by default }); afterAll(() => { @@ -71,10 +76,12 @@ describe('#updateObjectsSpaces', () => { client = elasticsearchClientMock.createElasticsearchClient(); const serializer = new SavedObjectsSerializer(registry); return { + mappings: { properties: {} }, // doesn't matter, only used as an argument to deleteLegacyUrlAliases which is mocked registry, allowedTypes: [SHAREABLE_OBJ_TYPE, NON_SHAREABLE_OBJ_TYPE], // SHAREABLE_HIDDEN_OBJ_TYPE is excluded client, serializer, + logger: loggerMock.create(), getIndexForType: (type: string) => `index-for-${type}`, objects, spacesToAdd, @@ -382,6 +389,149 @@ describe('#updateObjectsSpaces', () => { expect(client.bulk).not.toHaveBeenCalled(); }); }); + + describe('legacy URL aliases', () => { + it('does not delete aliases for objects that were not removed from any spaces', async () => { + const space1 = 'space-to-add'; + const space2 = 'space-to-remove'; + const space3 = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will not be changed + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space3] }; // will be updated + + const objects = [obj1, obj2]; + const spacesToAdd = [space1]; + const spacesToRemove = [space2]; + const params = setup({ objects, spacesToAdd, spacesToRemove }); + // this test case does not call mget + mockBulkResults({ error: false }); // result for obj2 + + await updateObjectsSpaces(params); + expect(client.bulk).toHaveBeenCalledTimes(1); + expectBulkArgs({ action: 'update', object: { ...obj2, namespaces: [space3, space1] } }); + expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); + expect(params.logger.error).not.toHaveBeenCalled(); + }); + + it('does not delete aliases for objects that were removed from spaces but were also added to All Spaces (*)', async () => { + const space2 = 'space-to-remove'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space2] }; + + const objects = [obj1]; + const spacesToAdd = [ALL_NAMESPACES_STRING]; + const spacesToRemove = [space2]; + const params = setup({ objects, spacesToAdd, spacesToRemove }); + // this test case does not call mget + mockBulkResults({ error: false }); // result for obj1 + + await updateObjectsSpaces(params); + expect(client.bulk).toHaveBeenCalledTimes(1); + expectBulkArgs({ + action: 'update', + object: { ...obj1, namespaces: [ALL_NAMESPACES_STRING] }, + }); + expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); + expect(params.logger.error).not.toHaveBeenCalled(); + }); + + it('deletes aliases for objects that were removed from specific spaces using "deleteBehavior: exclusive"', async () => { + const space1 = 'space-to-remove'; + const space2 = 'another-space-to-remove'; + const space3 = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space3] }; // will not be changed + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1, space2, space3] }; // will be updated + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will be deleted + + const objects = [obj1, obj2, obj3]; + const spacesToRemove = [space1, space2]; + const params = setup({ objects, spacesToRemove }); + // this test case does not call mget + mockBulkResults({ error: false }, { error: false }); // result2 for obj2 and obj3 + + await updateObjectsSpaces(params); + expect(client.bulk).toHaveBeenCalledTimes(1); + expectBulkArgs( + { action: 'update', object: { ...obj2, namespaces: [space3] } }, + { action: 'delete', object: obj3 } + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledTimes(2); + expect(mockDeleteLegacyUrlAliases).toHaveBeenNthCalledWith( + 1, // the first call resulted in an error which generated a log message (see assertion below) + expect.objectContaining({ + type: obj2.type, + id: obj2.id, + namespaces: [space1, space2], + deleteBehavior: 'inclusive', + }) + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + type: obj3.type, + id: obj3.id, + namespaces: [space1], + deleteBehavior: 'inclusive', + }) + ); + expect(params.logger.error).not.toHaveBeenCalled(); + }); + + it('deletes aliases for objects that were removed from all spaces using "deleteBehavior: inclusive"', async () => { + const space1 = 'space-to-add'; + const space2 = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space2] }; // will be updated to add space1 + const obj2 = { + type: SHAREABLE_OBJ_TYPE, + id: 'id-2', + spaces: [space2, ALL_NAMESPACES_STRING], // will be updated to add space1 and remove * + }; + + const objects = [obj1, obj2]; + const spacesToAdd = [space1]; + const spacesToRemove = [ALL_NAMESPACES_STRING]; + const params = setup({ objects, spacesToAdd, spacesToRemove }); + // this test case does not call mget + mockBulkResults({ error: false }); // result for obj1 + + await updateObjectsSpaces(params); + expect(client.bulk).toHaveBeenCalledTimes(1); + expectBulkArgs( + { action: 'update', object: { ...obj1, namespaces: [space2, space1] } }, + { action: 'update', object: { ...obj2, namespaces: [space2, space1] } } + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledTimes(1); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: obj2.type, + id: obj2.id, + namespaces: [space2, space1], + deleteBehavior: 'exclusive', + }) + ); + expect(params.logger.error).not.toHaveBeenCalled(); + }); + + it('logs a message when deleteLegacyUrlAliases returns an error', async () => { + const space1 = 'space-to-remove'; + const space2 = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1, space2] }; // will be updated + + const objects = [obj1]; + const spacesToRemove = [space1]; + const params = setup({ objects, spacesToRemove }); + // this test case does not call mget + mockBulkResults({ error: false }); // result for obj1 + mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); // result for deleting aliases for obj1 + + await updateObjectsSpaces(params); + expect(client.bulk).toHaveBeenCalledTimes(1); + expectBulkArgs({ action: 'update', object: { ...obj1, namespaces: [space2] } }); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledTimes(1); // don't assert deleteLegacyUrlAliases args, we have tests for that above + expect(params.logger.error).toHaveBeenCalledTimes(1); + expect(params.logger.error).toHaveBeenCalledWith( + 'Unable to delete aliases when unsharing an object: Oh no!' + ); + }); + }); }); describe('returns expected results', () => { diff --git a/src/core/server/saved_objects/service/lib/update_objects_spaces.ts b/src/core/server/saved_objects/service/lib/update_objects_spaces.ts index d88bf700a900e6..90e914b9796c33 100644 --- a/src/core/server/saved_objects/service/lib/update_objects_spaces.ts +++ b/src/core/server/saved_objects/service/lib/update_objects_spaces.ts @@ -6,9 +6,12 @@ * Side Public License, v 1. */ +import pMap from 'p-map'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import intersection from 'lodash/intersection'; +import type { Logger } from '../../../logging'; +import type { IndexMapping } from '../../mappings'; import type { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import type { SavedObjectsRawDocSource, SavedObjectsSerializer } from '../../serialization'; import type { @@ -28,6 +31,9 @@ import { } from './internal_utils'; import { DEFAULT_REFRESH_SETTING } from './repository'; import type { RepositoryEsClient } from './repository_es_client'; +import { ALL_NAMESPACES_STRING } from './utils'; +import type { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; +import { deleteLegacyUrlAliases } from './legacy_url_aliases'; /** * An object that should have its spaces updated. @@ -94,10 +100,12 @@ export interface SavedObjectsUpdateObjectsSpacesResponseObject { * @internal */ export interface UpdateObjectsSpacesParams { + mappings: IndexMapping; registry: ISavedObjectTypeRegistry; allowedTypes: string[]; client: RepositoryEsClient; serializer: SavedObjectsSerializer; + logger: Logger; getIndexForType: (type: string) => string; objects: SavedObjectsUpdateObjectsSpacesObject[]; spacesToAdd: string[]; @@ -105,15 +113,24 @@ export interface UpdateObjectsSpacesParams { options?: SavedObjectsUpdateObjectsSpacesOptions; } +type ObjectToDeleteAliasesFor = Pick< + DeleteLegacyUrlAliasesParams, + 'type' | 'id' | 'namespaces' | 'deleteBehavior' +>; + +const MAX_CONCURRENT_ALIAS_DELETIONS = 10; + /** * Gets all references and transitive references of the given objects. Ignores any object and/or reference that is not a multi-namespace * type. */ export async function updateObjectsSpaces({ + mappings, registry, allowedTypes, client, serializer, + logger, getIndexForType, objects, spacesToAdd, @@ -190,8 +207,12 @@ export async function updateObjectsSpaces({ const time = new Date().toISOString(); let bulkOperationRequestIndexCounter = 0; const bulkOperationParams: estypes.BulkOperationContainer[] = []; + const objectsToDeleteAliasesFor: ObjectToDeleteAliasesFor[] = []; const expectedBulkOperationResults: Array< - Either> + Either< + SavedObjectsUpdateObjectsSpacesResponseObject, + { type: string; id: string; updatedSpaces: string[]; esRequestIndex?: number } + > > = expectedBulkGetResults.map((expectedBulkGetResult) => { if (isLeft(expectedBulkGetResult)) { return expectedBulkGetResult; @@ -225,7 +246,7 @@ export async function updateObjectsSpaces({ versionProperties = getExpectedVersionProperties(version); } - const { newSpaces, isUpdateRequired } = getNewSpacesArray( + const { updatedSpaces, removedSpaces, isUpdateRequired } = analyzeSpaceChanges( currentSpaces, spacesToAdd, spacesToRemove @@ -233,7 +254,7 @@ export async function updateObjectsSpaces({ const expectedResult = { type, id, - newSpaces, + updatedSpaces, ...(isUpdateRequired && { esRequestIndex: bulkOperationRequestIndexCounter++ }), }; @@ -243,13 +264,24 @@ export async function updateObjectsSpaces({ _index: getIndexForType(type), ...versionProperties, }; - if (newSpaces.length) { - const documentToSave = { updated_at: time, namespaces: newSpaces }; + if (updatedSpaces.length) { + const documentToSave = { updated_at: time, namespaces: updatedSpaces }; // @ts-expect-error BulkOperation.retry_on_conflict, BulkOperation.routing. BulkOperation.version, and BulkOperation.version_type are optional bulkOperationParams.push({ update: documentMetadata }, { doc: documentToSave }); } else { bulkOperationParams.push({ delete: documentMetadata }); } + + if (removedSpaces.length && !updatedSpaces.includes(ALL_NAMESPACES_STRING)) { + // The object is being removed from at least one space; make sure to delete aliases appropriately + objectsToDeleteAliasesFor.push({ + type, + id, + ...(removedSpaces.includes(ALL_NAMESPACES_STRING) + ? { namespaces: updatedSpaces, deleteBehavior: 'exclusive' } + : { namespaces: removedSpaces, deleteBehavior: 'inclusive' }), + }); + } } return { tag: 'Right', value: expectedResult }; @@ -260,6 +292,24 @@ export async function updateObjectsSpaces({ ? await client.bulk({ refresh, body: bulkOperationParams, require_alias: true }) : undefined; + // Delete aliases if necessary, ensuring we don't have too many concurrent operations running. + const mapper = async ({ type, id, namespaces, deleteBehavior }: ObjectToDeleteAliasesFor) => + deleteLegacyUrlAliases({ + mappings, + registry, + client, + getIndexForType, + type, + id, + namespaces, + deleteBehavior, + }).catch((err) => { + // The object has already been unshared, but we caught an error when attempting to delete aliases. + // A consumer cannot attempt to unshare the object again, so just log the error and swallow it. + logger.error(`Unable to delete aliases when unsharing an object: ${err.message}`); + }); + await pMap(objectsToDeleteAliasesFor, mapper, { concurrency: MAX_CONCURRENT_ALIAS_DELETIONS }); + return { objects: expectedBulkOperationResults.map( (expectedResult) => { @@ -267,7 +317,7 @@ export async function updateObjectsSpaces({ return expectedResult.value; } - const { type, id, newSpaces, esRequestIndex } = expectedResult.value; + const { type, id, updatedSpaces, esRequestIndex } = expectedResult.value; if (esRequestIndex !== undefined) { const response = bulkOperationResponse?.body.items[esRequestIndex] ?? {}; const rawResponse = Object.values(response)[0] as any; @@ -277,7 +327,7 @@ export async function updateObjectsSpaces({ } } - return { id, type, spaces: newSpaces }; + return { id, type, spaces: updatedSpaces }; } ), }; @@ -289,17 +339,22 @@ function errorContent(error: DecoratedError) { } /** Gets the remaining spaces for an object after adding new ones and removing old ones. */ -function getNewSpacesArray( +function analyzeSpaceChanges( existingSpaces: string[], spacesToAdd: string[], spacesToRemove: string[] ) { const addSet = new Set(spacesToAdd); const removeSet = new Set(spacesToRemove); - const newSpaces = existingSpaces + const removedSpaces: string[] = []; + const updatedSpaces = existingSpaces .filter((x) => { addSet.delete(x); - return !removeSet.delete(x); + const removed = removeSet.delete(x); + if (removed) { + removedSpaces.push(x); + } + return !removed; }) .concat(Array.from(addSet)); @@ -307,5 +362,5 @@ function getNewSpacesArray( const isAnySpaceRemoved = removeSet.size < spacesToRemove.length; const isUpdateRequired = isAnySpaceAdded || isAnySpaceRemoved; - return { newSpaces, isUpdateRequired }; + return { updatedSpaces, removedSpaces, isUpdateRequired }; } diff --git a/src/core/server/ui_settings/integration_tests/index.test.ts b/src/core/server/ui_settings/integration_tests/index.test.ts index 44d98afdf54f51..ef635e90dac704 100644 --- a/src/core/server/ui_settings/integration_tests/index.test.ts +++ b/src/core/server/ui_settings/integration_tests/index.test.ts @@ -20,9 +20,9 @@ describe('uiSettings/routes', function () { jest.setTimeout(120_000); beforeAll(startServers); - /* eslint-disable jest/valid-describe */ + // eslint-disable-next-line jest/valid-describe describe('doc missing', docMissingSuite(savedObjectIndex)); + // eslint-disable-next-line jest/valid-describe describe('doc exists', docExistsSuite(savedObjectIndex)); - /* eslint-enable jest/valid-describe */ afterAll(stopServers); }); diff --git a/src/core/server/ui_settings/saved_objects/migrations.test.ts b/src/core/server/ui_settings/saved_objects/migrations.test.ts index 2d374b0c984243..e89811790060a6 100644 --- a/src/core/server/ui_settings/saved_objects/migrations.test.ts +++ b/src/core/server/ui_settings/saved_objects/migrations.test.ts @@ -186,4 +186,28 @@ describe('ui_settings 8.0.0 migrations', () => { migrationVersion: {}, }); }); + test('removes custom theme:version setting', () => { + const doc = { + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + 'theme:version': 'v7', + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }; + + expect(migration(doc)).toEqual({ + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }); + }); }); diff --git a/src/core/server/ui_settings/saved_objects/migrations.ts b/src/core/server/ui_settings/saved_objects/migrations.ts index 88632923e5514e..91666146655c60 100644 --- a/src/core/server/ui_settings/saved_objects/migrations.ts +++ b/src/core/server/ui_settings/saved_objects/migrations.ts @@ -88,6 +88,7 @@ export const migrations = { // owner: Team:Core 'telemetry:optIn', 'xPackMonitoring:allowReport', + 'theme:version', ].includes(key) ? { ...acc, diff --git a/src/core/server/ui_settings/settings/misc.test.ts b/src/core/server/ui_settings/settings/misc.test.ts deleted file mode 100644 index 7b6788664c9976..00000000000000 --- a/src/core/server/ui_settings/settings/misc.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { UiSettingsParams } from '../../../types'; -import { getMiscUiSettings } from './misc'; - -describe('misc settings', () => { - const miscSettings = getMiscUiSettings(); - - const getValidationFn = (setting: UiSettingsParams) => (value: any) => - setting.schema.validate(value); - - describe('truncate:maxHeight', () => { - const validate = getValidationFn(miscSettings['truncate:maxHeight']); - - it('should only accept positive numeric values', () => { - expect(() => validate(127)).not.toThrow(); - expect(() => validate(-12)).toThrowErrorMatchingInlineSnapshot( - `"Value must be equal to or greater than [0]."` - ); - expect(() => validate('foo')).toThrowErrorMatchingInlineSnapshot( - `"expected value of type [number] but got [string]"` - ); - }); - }); -}); diff --git a/src/core/server/ui_settings/settings/misc.ts b/src/core/server/ui_settings/settings/misc.ts index cd9e43400d3c91..ad7411dfd12afa 100644 --- a/src/core/server/ui_settings/settings/misc.ts +++ b/src/core/server/ui_settings/settings/misc.ts @@ -6,23 +6,11 @@ * Side Public License, v 1. */ -import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { UiSettingsParams } from '../types'; export const getMiscUiSettings = (): Record => { return { - 'truncate:maxHeight': { - name: i18n.translate('core.ui_settings.params.maxCellHeightTitle', { - defaultMessage: 'Maximum table cell height', - }), - value: 115, - description: i18n.translate('core.ui_settings.params.maxCellHeightText', { - defaultMessage: - 'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation', - }), - schema: schema.number({ min: 0 }), - }, buildNum: { readonly: true, schema: schema.maybe(schema.number()), diff --git a/src/core/server/ui_settings/settings/theme.test.ts b/src/core/server/ui_settings/settings/theme.test.ts index 58cbffb255b53e..839ece4eaf8d0d 100644 --- a/src/core/server/ui_settings/settings/theme.test.ts +++ b/src/core/server/ui_settings/settings/theme.test.ts @@ -29,72 +29,28 @@ describe('theme settings', () => { ); }); }); - - describe('theme:version', () => { - const validate = getValidationFn(themeSettings['theme:version']); - - it('should only accept valid values', () => { - expect(() => validate('v7')).not.toThrow(); - expect(() => validate('v8')).not.toThrow(); - expect(() => validate('v12')).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0]: expected value to equal [v7] -- [1]: expected value to equal [v8]" -`); - }); - }); }); describe('process.env.KBN_OPTIMIZER_THEMES handling', () => { - it('provides valid options based on tags', () => { - process.env.KBN_OPTIMIZER_THEMES = 'v7light,v8dark'; - let settings = getThemeSettings({ isDist: false }); - expect(settings['theme:version'].options).toEqual(['v7', 'v8']); - - process.env.KBN_OPTIMIZER_THEMES = 'v8dark,v7light'; - settings = getThemeSettings({ isDist: false }); - expect(settings['theme:version'].options).toEqual(['v7', 'v8']); - - process.env.KBN_OPTIMIZER_THEMES = 'v8dark,v7light,v7dark,v8light'; - settings = getThemeSettings({ isDist: false }); - expect(settings['theme:version'].options).toEqual(['v7', 'v8']); - - process.env.KBN_OPTIMIZER_THEMES = '*'; - settings = getThemeSettings({ isDist: false }); - expect(settings['theme:version'].options).toEqual(['v7', 'v8']); - - process.env.KBN_OPTIMIZER_THEMES = 'v7light'; - settings = getThemeSettings({ isDist: false }); - expect(settings['theme:version'].options).toEqual(['v7']); - - process.env.KBN_OPTIMIZER_THEMES = 'v8light'; - settings = getThemeSettings({ isDist: false }); - expect(settings['theme:version'].options).toEqual(['v8']); - }); - it('defaults to properties of first tag', () => { - process.env.KBN_OPTIMIZER_THEMES = 'v8dark,v7light'; + process.env.KBN_OPTIMIZER_THEMES = 'v8dark,v8light'; let settings = getThemeSettings({ isDist: false }); expect(settings['theme:darkMode'].value).toBe(true); - expect(settings['theme:version'].value).toBe('v8'); - process.env.KBN_OPTIMIZER_THEMES = 'v7light,v8dark'; + process.env.KBN_OPTIMIZER_THEMES = 'v8light,v8dark'; settings = getThemeSettings({ isDist: false }); expect(settings['theme:darkMode'].value).toBe(false); - expect(settings['theme:version'].value).toBe('v7'); }); it('ignores the value when isDist is undefined', () => { - process.env.KBN_OPTIMIZER_THEMES = 'v7light'; + process.env.KBN_OPTIMIZER_THEMES = 'v8dark'; const settings = getThemeSettings({ isDist: undefined }); expect(settings['theme:darkMode'].value).toBe(false); - expect(settings['theme:version'].options).toEqual(['v7', 'v8']); }); it('ignores the value when isDist is true', () => { - process.env.KBN_OPTIMIZER_THEMES = 'v7light'; + process.env.KBN_OPTIMIZER_THEMES = 'v8dark'; const settings = getThemeSettings({ isDist: true }); expect(settings['theme:darkMode'].value).toBe(false); - expect(settings['theme:version'].options).toEqual(['v7', 'v8']); }); }); diff --git a/src/core/server/ui_settings/settings/theme.ts b/src/core/server/ui_settings/settings/theme.ts index 4d2c45a9c84b02..1c343148399167 100644 --- a/src/core/server/ui_settings/settings/theme.ts +++ b/src/core/server/ui_settings/settings/theme.ts @@ -6,19 +6,16 @@ * Side Public License, v 1. */ -import { schema, Type } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; +import type { ThemeVersion } from '@kbn/ui-shared-deps-npm'; import { UiSettingsParams } from '../../../types'; function parseThemeTags() { - if (!process.env.KBN_OPTIMIZER_THEMES) { + if (!process.env.KBN_OPTIMIZER_THEMES || process.env.KBN_OPTIMIZER_THEMES === '*') { return ['v8light', 'v8dark']; } - if (process.env.KBN_OPTIMIZER_THEMES === '*') { - return ['v8light', 'v8dark', 'v7light', 'v7dark']; - } - return process.env.KBN_OPTIMIZER_THEMES.split(',').map((t) => t.trim()); } @@ -26,16 +23,12 @@ function getThemeInfo(options: GetThemeSettingsOptions) { if (options?.isDist ?? true) { return { defaultDarkMode: false, - defaultVersion: 'v8', - availableVersions: ['v7', 'v8'], }; } const themeTags = parseThemeTags(); return { defaultDarkMode: themeTags[0].endsWith('dark'), - defaultVersion: themeTags[0].slice(0, 2), - availableVersions: ['v7', 'v8'].filter((v) => themeTags.some((t) => t.startsWith(v))), }; } @@ -46,9 +39,7 @@ interface GetThemeSettingsOptions { export const getThemeSettings = ( options: GetThemeSettingsOptions = {} ): Record => { - const { availableVersions, defaultDarkMode, defaultVersion } = getThemeInfo(options); - - const onlyOneThemeAvailable = !options?.isDist && availableVersions.length === 1; + const { defaultDarkMode } = getThemeInfo(options); return { 'theme:darkMode': { @@ -62,29 +53,17 @@ export const getThemeSettings = ( requiresPageReload: true, schema: schema.boolean(), }, + /** + * Theme is sticking around as there are still a number of places reading it and + * we might use it again in the future. + */ 'theme:version': { name: i18n.translate('core.ui_settings.params.themeVersionTitle', { defaultMessage: 'Theme version', }), - value: defaultVersion, - type: 'select', - options: availableVersions, - description: i18n.translate('core.ui_settings.params.themeVersionText', { - defaultMessage: - 'Switch between the theme used for the current and next version of Kibana. A page refresh is required for the setting to be applied. {lessOptions}', - values: { - lessOptions: onlyOneThemeAvailable - ? '

There is only one theme available, set KBN_OPTIMIZER_THEMES=v7light,v7dark,v8light,v8dark to get more options.' - : undefined, - }, - }), - requiresPageReload: true, - schema: schema.oneOf(availableVersions.map((v) => schema.literal(v)) as [Type]), - optionLabels: onlyOneThemeAvailable - ? { - [availableVersions[0]]: `${availableVersions[0]} (only)`, - } - : undefined, + value: 'v8' as ThemeVersion, + readonly: true, + schema: schema.literal('v8'), }, }; }; diff --git a/src/dev/bazel/index.bzl b/src/dev/bazel/index.bzl index e38d3d78c0071b..313cc9f06236c2 100644 --- a/src/dev/bazel/index.bzl +++ b/src/dev/bazel/index.bzl @@ -11,5 +11,7 @@ Please do not import from any other files when looking to use a custom rule """ load("//src/dev/bazel:jsts_transpiler.bzl", _jsts_transpiler = "jsts_transpiler") +load("//src/dev/bazel:ts_project.bzl", _ts_project = "ts_project") jsts_transpiler = _jsts_transpiler +ts_project = _ts_project diff --git a/src/dev/bazel/ts_project.bzl b/src/dev/bazel/ts_project.bzl new file mode 100644 index 00000000000000..afd28fa513164a --- /dev/null +++ b/src/dev/bazel/ts_project.bzl @@ -0,0 +1,16 @@ +"Simple wrapper over the general ts_project rule from rules_nodejs so we can override some configs" + +load("@npm//@bazel/typescript:index.bzl", _ts_project = "ts_project") + +def ts_project(validate = False, **kwargs): + """A macro around the upstream ts_project rule. + + Args: + validate: boolean; whether to check that the tsconfig JSON settings match the attributes on this target. Defaults to false + **kwargs: the rest + """ + + _ts_project( + validate = validate, + **kwargs + ) diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts index 340a035adea4cf..96d66b111b0624 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts @@ -22,7 +22,7 @@ function generator({ imageFlavor }: TemplateContext) { server.host: "0.0.0.0" server.shutdownTimeout: "5s" elasticsearch.hosts: [ "http://elasticsearch:9200" ] - ${!imageFlavor ? 'monitoring.ui.container.elasticsearch.enabled: true' : ''} + monitoring.ui.container.elasticsearch.enabled: true `); } diff --git a/src/dev/code_coverage/docs/team_assignment/README.md b/src/dev/code_coverage/docs/team_assignment/README.md index f2884fb42b8d75..bef4e74415b5a3 100644 --- a/src/dev/code_coverage/docs/team_assignment/README.md +++ b/src/dev/code_coverage/docs/team_assignment/README.md @@ -3,7 +3,7 @@ Team assignment occurs once per ci run. The "orchestration" entry point is a [Jenkinsfile Scripted Pipeline](https://github.com/elastic/kibana/blob/f73bc48b3bbbb5ad2042c1aa267aea2150b7b742/.ci/Jenkinsfile_coverage#L21) -This Jenkinsfile runs a [shell script](https://github.com/elastic/kibana/blob/master/src/dev/code_coverage/shell_scripts/generate_team_assignments_and_ingest_coverage.sh#L33) that kicks everything off. +This Jenkinsfile runs a [shell script](https://github.com/elastic/kibana/blob/main/src/dev/code_coverage/shell_scripts/generate_team_assignments_and_ingest_coverage.sh#L33) that kicks everything off. The end result is the data is ingested to our [Kibana Stats Cluster](https://kibana-stats.elastic.dev/app/dashboards#/view/58b8db70-62f9-11ea-8312-7f2d69b79843?_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-7d%2Cto%3Anow))) ## Team Assignment Parsing (from .github/CODEOWNERS) diff --git a/src/dev/eslint/index.ts b/src/dev/eslint/index.ts index 765c7dfce9ed0c..5aeb83c45ad057 100644 --- a/src/dev/eslint/index.ts +++ b/src/dev/eslint/index.ts @@ -8,3 +8,4 @@ export { pickFilesToLint } from './pick_files_to_lint'; export { lintFiles } from './lint_files'; +export { runEslintWithTypes } from './run_eslint_with_types'; diff --git a/src/dev/eslint/run_eslint_with_types.ts b/src/dev/eslint/run_eslint_with_types.ts new file mode 100644 index 00000000000000..750011dea10316 --- /dev/null +++ b/src/dev/eslint/run_eslint_with_types.ts @@ -0,0 +1,171 @@ +/* + * 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 Path from 'path'; +import Fs from 'fs'; +import Os from 'os'; + +import execa from 'execa'; +import * as Rx from 'rxjs'; +import { mergeMap, reduce } from 'rxjs/operators'; +import { supportsColor } from 'chalk'; +import { REPO_ROOT, run, createFailError } from '@kbn/dev-utils'; +import { lastValueFrom } from '@kbn/std'; + +import { PROJECTS } from '../typescript/projects'; +import { Project } from '../typescript/project'; + +export function runEslintWithTypes() { + run( + async ({ log, flags }) => { + const eslintPath = require.resolve('eslint/bin/eslint'); + const ignoreFilePath = Path.resolve(REPO_ROOT, '.eslintignore'); + const configTemplate = Fs.readFileSync( + Path.resolve(__dirname, 'types.eslint.config.template.js'), + 'utf8' + ); + + const projectFilter = + flags.project && typeof flags.project === 'string' + ? Path.resolve(flags.project) + : undefined; + + const projects = PROJECTS.filter((project) => { + if (project.disableTypeCheck) { + log.verbose(`[${project.name}] skipping project with type checking disabled`); + return false; + } + + if (projectFilter && project.tsConfigPath !== projectFilter) { + log.verbose(`[${project.name}] skipping because it doesn't match --project`); + return false; + } + + return true; + }); + + if (!projects.length) { + if (projectFilter) { + throw createFailError(`[${projectFilter}] is not a valid tsconfig project`); + } + + throw createFailError('unable to find projects to lint'); + } + + const concurrency = Math.max(1, Math.round((Os.cpus() || []).length / 2) || 1) || 1; + log.info(`Linting ${projects.length} projects, ${concurrency} at a time`); + + const failures = await lastValueFrom( + Rx.from(projects).pipe( + mergeMap(async (project) => { + const configFilePath = Path.resolve(project.directory, 'types.eslint.config.js'); + + Fs.writeFileSync( + configFilePath, + configTemplate.replace( + `'{PACKAGE_CONFIG}'`, + JSON.stringify(JSON.stringify({ rootDir: project.directory })) + ), + 'utf8' + ); + + const proc = await execa( + process.execPath, + [ + Path.relative(project.directory, eslintPath), + ...project.getIncludePatterns().map((p) => (p.endsWith('*') ? `${p}.{ts,tsx}` : p)), + ...project.getExcludePatterns().flatMap((p) => ['--ignore-pattern', p]), + ...['--ignore-pattern', '**/*.json'], + ...['--ext', '.ts,.tsx'], + '--no-error-on-unmatched-pattern', + '--no-inline-config', + '--no-eslintrc', + ...['--config', Path.relative(project.directory, configFilePath)], + ...['--ignore-path', Path.relative(project.directory, ignoreFilePath)], + ...(flags.verbose ? ['--debug'] : []), + ...(flags.fix ? ['--fix'] : []), + ], + { + cwd: project.directory, + env: { + ...(supportsColor ? { FORCE_COLOR: 'true' } : {}), + ...process.env, + }, + buffer: true, + all: true, + reject: false, + } + ); + + if (proc.exitCode === 0) { + Fs.unlinkSync(configFilePath); + log.success(project.name); + return undefined; + } else { + log.error(`${project.name} failed`); + log.indent(4); + log.write(proc.all); + log.indent(-4); + return project; + } + }, concurrency), + reduce((acc: Project[], project) => { + if (project) { + return [...acc, project]; + } else { + return acc; + } + }, []) + ) + ); + + if (!failures.length) { + log.success(`All projects validated successfully!`); + if (flags.fix) { + log.info(` +❗️ After staging your changes, don't forget to run eslint/prettier on them with: + + node scripts/precommit_hook --fix +`); + } + + return; + } + + throw createFailError( + ` + ${ + failures.length + } projects failed, run the following commands locally to try auto-fixing them: + + ${failures + .map( + (p) => + `node scripts/eslint_with_types --fix --project ${Path.relative( + REPO_ROOT, + p.tsConfigPath + )}` + ) + .join('\n ')} + ` + ); + }, + { + description: + 'Run ESLint in each TS project, feeding it the TS config so it can validate our code using the type information', + flags: { + string: ['project'], + boolean: ['fix'], + help: ` + --project Only run eslint on a specific ts project + --fix Run eslint in --fix mode + `, + }, + } + ); +} diff --git a/src/dev/eslint/types.eslint.config.template.js b/src/dev/eslint/types.eslint.config.template.js new file mode 100644 index 00000000000000..08cbaf3b023250 --- /dev/null +++ b/src/dev/eslint/types.eslint.config.template.js @@ -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. + */ + +/** + * THIS FILE IS WRITTEN AUTOMATICALLY by `node scripts/eslint_with_types` and + * should be deleted automatically unless something goes wrong + */ + +const config = JSON.parse('{PACKAGE_CONFIG}'); + +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: config.rootDir, + project: ['./tsconfig.json'], + }, + plugins: ['@typescript-eslint'], + rules: { + '@typescript-eslint/consistent-type-exports': 'error', + }, +}; diff --git a/src/dev/i18n/index.ts b/src/dev/i18n/index.ts index 111dc6b688e9f7..54ea36a9f82c4a 100644 --- a/src/dev/i18n/index.ts +++ b/src/dev/i18n/index.ts @@ -12,10 +12,6 @@ export { extractMessagesFromPathToMap } from './extract_default_translations'; export { matchEntriesWithExctractors } from './extract_default_translations'; export { arrayify, writeFileAsync, readFileAsync, normalizePath, ErrorReporter } from './utils'; export { serializeToJson, serializeToJson5 } from './serializers'; -export { - I18nConfig, - filterConfigPaths, - assignConfigFromPath, - checkConfigNamespacePrefix, -} from './config'; +export type { I18nConfig } from './config'; +export { filterConfigPaths, assignConfigFromPath, checkConfigNamespacePrefix } from './config'; export { integrateLocaleFiles } from './integrate_locale_files'; diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 4c630ae1c10e1f..f23456bbaca07c 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -75,6 +75,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.0.0': ['Elastic License 2.0'], - '@elastic/eui@40.0.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@40.1.0': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/src/dev/typescript/project.ts b/src/dev/typescript/project.ts index a0c196dd8e9192..baeaf39ec288de 100644 --- a/src/dev/typescript/project.ts +++ b/src/dev/typescript/project.ts @@ -66,8 +66,10 @@ export class Project { const disableTypeCheck = projectOptions?.disableTypeCheck || false; const name = projectOptions?.name || Path.relative(REPO_ROOT, directory) || Path.basename(directory); - const include = config.include ? makeMatchers(directory, config.include) : undefined; - const exclude = config.exclude ? makeMatchers(directory, config.exclude) : undefined; + const includePatterns = config.include; + const include = includePatterns ? makeMatchers(directory, includePatterns) : undefined; + const excludePatterns = config.exclude; + const exclude = excludePatterns ? makeMatchers(directory, excludePatterns) : undefined; let baseProject; if (config.extends) { @@ -99,7 +101,9 @@ export class Project { disableTypeCheck, baseProject, include, - exclude + includePatterns, + exclude, + excludePatterns ); cache.set(tsConfigPath, project); return project; @@ -114,9 +118,22 @@ export class Project { public readonly baseProject?: Project, private readonly include?: IMinimatch[], - private readonly exclude?: IMinimatch[] + private readonly includePatterns?: string[], + private readonly exclude?: IMinimatch[], + private readonly excludePatterns?: string[] ) {} + public getIncludePatterns(): string[] { + return this.includePatterns + ? this.includePatterns + : this.baseProject?.getIncludePatterns() ?? []; + } + public getExcludePatterns(): string[] { + return this.excludePatterns + ? this.excludePatterns + : this.baseProject?.getExcludePatterns() ?? []; + } + private getInclude(): IMinimatch[] { return this.include ? this.include : this.baseProject?.getInclude() ?? []; } diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index de432b51c0bbfd..e5657dd4663a38 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -63,6 +63,9 @@ export const PROJECTS = [ name: 'apm/ftr_e2e', disableTypeCheck: true, }), + createProject('x-pack/plugins/fleet/cypress/tsconfig.json', { + name: 'fleet/cypress', + }), createProject('x-pack/plugins/uptime/e2e/tsconfig.json', { name: 'uptime/synthetics-e2e-tests', diff --git a/src/plugins/advanced_settings/public/index.ts b/src/plugins/advanced_settings/public/index.ts index be57576d176c63..83676b4f7c38d3 100644 --- a/src/plugins/advanced_settings/public/index.ts +++ b/src/plugins/advanced_settings/public/index.ts @@ -9,7 +9,7 @@ import React from 'react'; import { PluginInitializerContext } from 'kibana/public'; import { AdvancedSettingsPlugin } from './plugin'; -export { AdvancedSettingsSetup, AdvancedSettingsStart } from './types'; +export type { AdvancedSettingsSetup, AdvancedSettingsStart } from './types'; export { ComponentRegistry } from './component_registry'; /** diff --git a/src/plugins/bfetch/public/batching/index.ts b/src/plugins/bfetch/public/batching/index.ts index 115fd84cbe979f..e3a8d0169af273 100644 --- a/src/plugins/bfetch/public/batching/index.ts +++ b/src/plugins/bfetch/public/batching/index.ts @@ -6,7 +6,5 @@ * Side Public License, v 1. */ -export { - createStreamingBatchedFunction, - StreamingBatchedFunctionParams, -} from './create_streaming_batched_function'; +export type { StreamingBatchedFunctionParams } from './create_streaming_batched_function'; +export { createStreamingBatchedFunction } from './create_streaming_batched_function'; diff --git a/src/plugins/bfetch/public/index.ts b/src/plugins/bfetch/public/index.ts index 59ee2d63ac57ec..6ab5480327538b 100644 --- a/src/plugins/bfetch/public/index.ts +++ b/src/plugins/bfetch/public/index.ts @@ -9,10 +9,10 @@ import { PluginInitializerContext } from '../../../core/public'; import { BfetchPublicPlugin } from './plugin'; -export { BfetchPublicSetup, BfetchPublicStart, BfetchPublicContract } from './plugin'; +export type { BfetchPublicSetup, BfetchPublicStart, BfetchPublicContract } from './plugin'; export { split } from './streaming'; -export { BatchedFunc } from './batching/types'; +export type { BatchedFunc } from './batching/types'; export function plugin(initializerContext: PluginInitializerContext) { return new BfetchPublicPlugin(initializerContext); diff --git a/src/plugins/bfetch/server/index.ts b/src/plugins/bfetch/server/index.ts index f4c41d10e42cb1..76f86e69750706 100644 --- a/src/plugins/bfetch/server/index.ts +++ b/src/plugins/bfetch/server/index.ts @@ -9,7 +9,7 @@ import { PluginInitializerContext } from '../../../core/server'; import { BfetchServerPlugin } from './plugin'; -export { BfetchServerSetup, BfetchServerStart, BatchProcessingRouteParams } from './plugin'; +export type { BfetchServerSetup, BfetchServerStart, BatchProcessingRouteParams } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new BfetchServerPlugin(initializerContext); diff --git a/src/plugins/chart_expressions/expression_metric/README.md b/src/plugins/chart_expressions/expression_metric/README.md index ec2541ebf74d82..ea49c1b1131ab9 100755 --- a/src/plugins/chart_expressions/expression_metric/README.md +++ b/src/plugins/chart_expressions/expression_metric/README.md @@ -6,4 +6,4 @@ Expression MetricVis plugin adds a `metric` renderer and function to the express ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/chart_expressions/expression_tagcloud/README.md b/src/plugins/chart_expressions/expression_tagcloud/README.md index ae7635ffe01735..2331d6bef8c29f 100755 --- a/src/plugins/chart_expressions/expression_tagcloud/README.md +++ b/src/plugins/chart_expressions/expression_tagcloud/README.md @@ -6,4 +6,4 @@ Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expres ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/charts/common/index.ts b/src/plugins/charts/common/index.ts index d06dbc73e29b1e..825fd74e24041d 100644 --- a/src/plugins/charts/common/index.ts +++ b/src/plugins/charts/common/index.ts @@ -9,23 +9,19 @@ export const COLOR_MAPPING_SETTING = 'visualization:colorMapping'; export const LEGACY_TIME_AXIS = 'visualization:useLegacyTimeAxis'; -export { +export type { CustomPaletteArguments, CustomPaletteState, SystemPaletteArguments, PaletteOutput, - defaultCustomColors, - palette, - systemPalette, } from './palette'; +export { defaultCustomColors, palette, systemPalette } from './palette'; export { paletteIds } from './constants'; +export type { ColorSchema, RawColorSchema, ColorMap } from './static'; export { ColorSchemas, - ColorSchema, - RawColorSchema, - ColorMap, vislibColorMaps, colorSchemas, getHeatmapColors, @@ -34,6 +30,7 @@ export { ColorMode, LabelRotation, defaultCountLabel, + MULTILAYER_TIME_AXIS_STYLE, } from './static'; -export { ColorSchemaParams, Labels, Style } from './types'; +export type { ColorSchemaParams, Labels, Style } from './types'; diff --git a/src/plugins/charts/common/static/color_maps/index.ts b/src/plugins/charts/common/static/color_maps/index.ts index c624c5ceb66858..115758988f04bb 100644 --- a/src/plugins/charts/common/static/color_maps/index.ts +++ b/src/plugins/charts/common/static/color_maps/index.ts @@ -6,13 +6,7 @@ * Side Public License, v 1. */ -export { - ColorSchemas, - ColorSchema, - RawColorSchema, - ColorMap, - vislibColorMaps, - colorSchemas, -} from './color_maps'; +export type { ColorSchema, RawColorSchema, ColorMap } from './color_maps'; +export { ColorSchemas, vislibColorMaps, colorSchemas } from './color_maps'; export { getHeatmapColors } from './heatmap_color'; export { truncatedColorMaps, truncatedColorSchemas } from './truncated_color_maps'; diff --git a/src/plugins/charts/common/static/index.ts b/src/plugins/charts/common/static/index.ts index 9cde3bafe59e43..4a6b3ec2b52bb3 100644 --- a/src/plugins/charts/common/static/index.ts +++ b/src/plugins/charts/common/static/index.ts @@ -6,11 +6,9 @@ * Side Public License, v 1. */ +export type { ColorSchema, RawColorSchema, ColorMap } from './color_maps'; export { ColorSchemas, - ColorSchema, - RawColorSchema, - ColorMap, vislibColorMaps, colorSchemas, getHeatmapColors, @@ -19,3 +17,5 @@ export { } from './color_maps'; export { ColorMode, LabelRotation, defaultCountLabel } from './components'; + +export * from './styles'; diff --git a/typings/react_vis.d.ts b/src/plugins/charts/common/static/styles/index.ts similarity index 90% rename from typings/react_vis.d.ts rename to src/plugins/charts/common/static/styles/index.ts index 209dd398e86f44..688103322c53f4 100644 --- a/typings/react_vis.d.ts +++ b/src/plugins/charts/common/static/styles/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -declare module 'react-vis'; +export * from './multilayer_timeaxis'; diff --git a/src/plugins/charts/common/static/styles/multilayer_timeaxis.ts b/src/plugins/charts/common/static/styles/multilayer_timeaxis.ts new file mode 100644 index 00000000000000..02a5533f53fca5 --- /dev/null +++ b/src/plugins/charts/common/static/styles/multilayer_timeaxis.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 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 { Position, RecursivePartial, AxisStyle } from '@elastic/charts'; + +export const MULTILAYER_TIME_AXIS_STYLE: RecursivePartial = { + tickLabel: { + visible: true, + padding: 0, + rotation: 0, + alignment: { + vertical: Position.Bottom, + horizontal: Position.Left, + }, + }, + tickLine: { + visible: true, + size: 0.0001, + padding: 4, + }, +}; diff --git a/src/plugins/charts/public/index.ts b/src/plugins/charts/public/index.ts index e3d38b797c576a..51d2722e3a24fa 100644 --- a/src/plugins/charts/public/index.ts +++ b/src/plugins/charts/public/index.ts @@ -13,23 +13,28 @@ import { ChartsPlugin } from './plugin'; export const plugin = () => new ChartsPlugin(); -export { ChartsPluginSetup, ChartsPluginStart } from './plugin'; +export type { ChartsPluginSetup, ChartsPluginStart } from './plugin'; export * from './static'; export * from './services/palettes/types'; export { lightenColor } from './services/palettes/lighten_color'; export { useActiveCursor } from './services/active_cursor'; -export { +export type { PaletteOutput, CustomPaletteArguments, CustomPaletteState, SystemPaletteArguments, - paletteIds, - ColorSchemas, ColorSchema, RawColorSchema, ColorMap, + ColorSchemaParams, + Labels, + Style, +} from '../common'; +export { + paletteIds, + ColorSchemas, vislibColorMaps, colorSchemas, getHeatmapColors, @@ -38,7 +43,4 @@ export { ColorMode, LabelRotation, defaultCountLabel, - ColorSchemaParams, - Labels, - Style, } from '../common'; diff --git a/src/plugins/charts/public/services/theme/theme.ts b/src/plugins/charts/public/services/theme/theme.ts index dfea367327e7af..1aad4f0ab6328f 100644 --- a/src/plugins/charts/public/services/theme/theme.ts +++ b/src/plugins/charts/public/services/theme/theme.ts @@ -39,10 +39,8 @@ export class ThemeService { /** A React hook for consuming the dark mode value */ public useDarkMode = (): boolean => { - // eslint-disable-next-line react-hooks/rules-of-hooks const [value, update] = useState(false); - // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { const s = this.darkModeEnabled$.subscribe(update); return () => s.unsubscribe(); @@ -53,12 +51,9 @@ export class ThemeService { /** A React hook for consuming the charts theme */ public useChartsTheme = (): PartialTheme => { - // eslint-disable-next-line react-hooks/rules-of-hooks const [value, update] = useState(this._chartsTheme$.getValue()); - // eslint-disable-next-line react-hooks/rules-of-hooks const ref = useRef(value); - // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { const s = this.chartsTheme$.subscribe((val) => { if (val !== ref.current) { @@ -74,12 +69,9 @@ export class ThemeService { /** A React hook for consuming the charts theme */ public useChartsBaseTheme = (): Theme => { - // eslint-disable-next-line react-hooks/rules-of-hooks const [value, update] = useState(this._chartsBaseTheme$.getValue()); - // eslint-disable-next-line react-hooks/rules-of-hooks const ref = useRef(value); - // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { const s = this.chartsBaseTheme$.subscribe((val) => { if (val !== ref.current) { diff --git a/src/plugins/charts/public/static/index.ts b/src/plugins/charts/public/static/index.ts index 6f5c87ce0df4d3..53078eebe9c56f 100644 --- a/src/plugins/charts/public/static/index.ts +++ b/src/plugins/charts/public/static/index.ts @@ -9,3 +9,4 @@ export * from './colors'; export * from './components'; export * from './utils'; +export * from '../../common/static/styles'; diff --git a/src/plugins/charts/server/index.ts b/src/plugins/charts/server/index.ts index e65318747224d2..73524964efb9bf 100644 --- a/src/plugins/charts/server/index.ts +++ b/src/plugins/charts/server/index.ts @@ -7,12 +7,12 @@ */ import { ChartsServerPlugin } from './plugin'; -export { +export type { PaletteOutput, CustomPaletteArguments, CustomPaletteState, SystemPaletteArguments, - paletteIds, } from '../common'; +export { paletteIds } from '../common'; export const plugin = () => new ChartsServerPlugin(); diff --git a/src/plugins/charts/server/plugin.ts b/src/plugins/charts/server/plugin.ts index c7559b525cd223..86c90c73c38b03 100644 --- a/src/plugins/charts/server/plugin.ts +++ b/src/plugins/charts/server/plugin.ts @@ -37,7 +37,8 @@ export class ChartsServerPlugin implements Plugin { message: i18n.translate( 'charts.advancedSettings.visualization.colorMappingTextDeprecation', { - defaultMessage: 'This setting is deprecated and will not be supported as of 8.0.', + defaultMessage: + 'This setting is deprecated and will not be supported in a future version.', } ), docLinksKey: 'visualizationSettings', diff --git a/src/plugins/console/README.md b/src/plugins/console/README.md index 3bb58d37d1684c..6920ae8b944cf0 100644 --- a/src/plugins/console/README.md +++ b/src/plugins/console/README.md @@ -18,17 +18,17 @@ GET _search ``` ## Architecture -Console uses Ace editor that is wrapped with [`CoreEditor`](https://github.com/elastic/kibana/blob/master/src/plugins/console/public/types/core_editor.ts), so that if needed it can easily be replaced with another editor, for example Monaco. -The autocomplete logic is located in [`autocomplete`](https://github.com/elastic/kibana/blob/master/src/plugins/console/public/lib/autocomplete) folder. Autocomplete rules are computed by classes in `components` sub-folder. +Console uses Ace editor that is wrapped with [`CoreEditor`](https://github.com/elastic/kibana/blob/main/src/plugins/console/public/types/core_editor.ts), so that if needed it can easily be replaced with another editor, for example Monaco. +The autocomplete logic is located in [`autocomplete`](https://github.com/elastic/kibana/blob/main/src/plugins/console/public/lib/autocomplete) folder. Autocomplete rules are computed by classes in `components` sub-folder. ## Autocomplete definitions Kibana users benefit greatly from autocomplete suggestions since not all Elasticsearch APIs can be provided with a corresponding UI. Autocomplete suggestions improve usability of Console for any Elasticsearch API endpoint. Autocomplete definitions are all created in the form of javascript objects loaded from `json` and `js` files. ### Creating definitions -The [`generated`](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/json/generated) folder contains definitions created automatically from Elasticsearch REST API specifications. See this [README](https://github.com/elastic/kibana/blob/master/packages/kbn-spec-to-console/README.md) file for more information on the `spec-to-console` script. +The [`generated`](https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/generated) folder contains definitions created automatically from Elasticsearch REST API specifications. See this [README](https://github.com/elastic/kibana/blob/main/packages/kbn-spec-to-console/README.md) file for more information on the `spec-to-console` script. -Manually created override files in the [`overrides`](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/json/overrides) folder contain fixes for generated files and additions for request body parameters. +Manually created override files in the [`overrides`](https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/overrides) folder contain fixes for generated files and additions for request body parameters. ### Top level keys Use following top level keys in the definitions objects. @@ -214,7 +214,7 @@ Use this type to copy a configuration object specified in a different endpoint d } ``` #### Global scope (`GLOBAL`) -Use `GLOBAL` keyword with `__scope_link` to refer to a reusable set of definitions created in the [`globals`](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/js/globals.ts) file. +Use `GLOBAL` keyword with `__scope_link` to refer to a reusable set of definitions created in the [`globals`](https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/js/globals.ts) file. For example: ```json { @@ -226,11 +226,11 @@ For example: } ``` #### Conditional definition (`__condition: { lines_regex: ... }`) -To provide a different set of autocomplete suggestions based on the value configured in the request. For example, when creating a snapshot repository of different types (`fs`, `url` etc) different properties are displayed in the suggestions list based on the type. See [snapshot.create_repository.json](https://github.com/elastic/kibana/blob/master/src/plugins/console/server/lib/spec_definitions/json/overrides/snapshot.create_repository.json) for an example. +To provide a different set of autocomplete suggestions based on the value configured in the request. For example, when creating a snapshot repository of different types (`fs`, `url` etc) different properties are displayed in the suggestions list based on the type. See [snapshot.create_repository.json](https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/overrides/snapshot.create_repository.json) for an example. ### Variables Some autocomplete definitions need to be configured with dynamic values that can't be hard coded into a json or js file, for example a list of indices in the cluster. -A list of variables is defined in the `parametrizedComponentFactories` function in [`kb.js`](https://github.com/elastic/kibana/blob/master/src/plugins/console/public/lib/kb/kb.js) file. The values of these variables are assigned dynamically for every cluster. +A list of variables is defined in the `parametrizedComponentFactories` function in [`kb.js`](https://github.com/elastic/kibana/blob/main/src/plugins/console/public/lib/kb/kb.js) file. The values of these variables are assigned dynamically for every cluster. Use these variables with curly braces, for example `{indices}`, `{types}`, `{id}`, `{username}`, `{template}`, `{nodes}` etc. diff --git a/src/plugins/console/public/application/components/console_menu.tsx b/src/plugins/console/public/application/components/console_menu.tsx index 1abbcfc2fdeb66..270fc2f0751c02 100644 --- a/src/plugins/console/public/application/components/console_menu.tsx +++ b/src/plugins/console/public/application/components/console_menu.tsx @@ -10,7 +10,13 @@ import React, { Component } from 'react'; import { NotificationsSetup } from 'src/core/public'; -import { EuiIcon, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } from '@elastic/eui'; +import { + EuiIcon, + EuiContextMenuPanel, + EuiContextMenuItem, + EuiPopover, + EuiLink, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -98,8 +104,7 @@ export class ConsoleMenu extends Component { render() { const button = ( - +
); const items = [ diff --git a/src/plugins/console/public/application/components/index.ts b/src/plugins/console/public/application/components/index.ts index 7bf3bc8a9c33b8..8f8292477cb943 100644 --- a/src/plugins/console/public/application/components/index.ts +++ b/src/plugins/console/public/application/components/index.ts @@ -8,9 +8,11 @@ export { NetworkRequestStatusBar } from './network_request_status_bar'; export { SomethingWentWrongCallout } from './something_went_wrong_callout'; -export { TopNavMenuItem, TopNavMenu } from './top_nav_menu'; +export type { TopNavMenuItem } from './top_nav_menu'; +export { TopNavMenu } from './top_nav_menu'; export { ConsoleMenu } from './console_menu'; export { WelcomePanel } from './welcome_panel'; -export { AutocompleteOptions, DevToolsSettingsModal } from './settings_modal'; +export type { AutocompleteOptions } from './settings_modal'; +export { DevToolsSettingsModal } from './settings_modal'; export { HelpPanel } from './help_panel'; export { EditorContentSpinner } from './editor_content_spinner'; diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx index 04b222257cd2a2..f8995479c14168 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -63,7 +63,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { (sendRequestToES as jest.Mock).mockRejectedValue({}); const editor = doMount(); act(() => { - editor.find('[data-test-subj~="sendRequestButton"]').simulate('click'); + editor.find('button[data-test-subj~="sendRequestButton"]').simulate('click'); }); await nextTick(); expect(sendRequestToES).toBeCalledTimes(1); diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index c7f6349a19075a..4ad86e495831e6 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -6,7 +6,14 @@ * Side Public License, v 1. */ -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiScreenReaderOnly, EuiToolTip } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { debounce } from 'lodash'; import { decompressFromEncodedURIComponent } from 'lz-string'; @@ -240,16 +247,16 @@ function EditorUI({ initialTextValue }: EditorProps) { defaultMessage: 'Click to send request', })} > - + + diff --git a/src/plugins/console/public/application/contexts/index.ts b/src/plugins/console/public/application/contexts/index.ts index 693056ec23af6f..2239c4f3354c21 100644 --- a/src/plugins/console/public/application/contexts/index.ts +++ b/src/plugins/console/public/application/contexts/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -export { useServicesContext, ServicesContextProvider, ContextValue } from './services_context'; +export type { ContextValue } from './services_context'; +export { useServicesContext, ServicesContextProvider } from './services_context'; export { useRequestActionContext, diff --git a/src/plugins/console/public/application/lib/index.ts b/src/plugins/console/public/application/lib/index.ts index 3c67718d170ee6..8eba0159103628 100644 --- a/src/plugins/console/public/application/lib/index.ts +++ b/src/plugins/console/public/application/lib/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ -export { createApi, Api } from './api'; +export type { Api } from './api'; +export { createApi } from './api'; export { createEsHostService, EsHostService } from './es_host_service'; diff --git a/src/plugins/console/public/services/index.ts b/src/plugins/console/public/services/index.ts index 2b1e64525d0f9f..c37c9d9359a165 100644 --- a/src/plugins/console/public/services/index.ts +++ b/src/plugins/console/public/services/index.ts @@ -8,4 +8,5 @@ export { createHistory, History } from './history'; export { createStorage, Storage, StorageKeys } from './storage'; -export { createSettings, Settings, DevToolsSettings, DEFAULT_SETTINGS } from './settings'; +export type { DevToolsSettings } from './settings'; +export { createSettings, Settings, DEFAULT_SETTINGS } from './settings'; diff --git a/src/plugins/console/public/styles/_app.scss b/src/plugins/console/public/styles/_app.scss index 3c7ab2c7c00b80..61dc31138c7682 100644 --- a/src/plugins/console/public/styles/_app.scss +++ b/src/plugins/console/public/styles/_app.scss @@ -56,17 +56,6 @@ min-width: 40px; } -.conApp__editorActionButton { - padding: 0 $euiSizeXS; - appearance: none; - border: none; - background: none; -} - -.conApp__editorActionButton--success { - color: $euiColorSuccess; -} - .conApp__resizer { @include kbnResizer; // Give the aria selection border priority when the divider is selected on IE11 and Chrome diff --git a/src/plugins/console/public/types/index.ts b/src/plugins/console/public/types/index.ts index d8b6aaf7b12c45..2a86e53e55b800 100644 --- a/src/plugins/console/public/types/index.ts +++ b/src/plugins/console/public/types/index.ts @@ -11,5 +11,5 @@ export * from './core_editor'; export * from './token'; export * from './tokens_provider'; export * from './common'; -export { ClientConfigType } from './config'; -export { ConsoleUILocatorParams } from './locator'; +export type { ClientConfigType } from './config'; +export type { ConsoleUILocatorParams } from './locator'; diff --git a/src/plugins/console/server/index.ts b/src/plugins/console/server/index.ts index b270b89a3d45a6..598dfa23958c92 100644 --- a/src/plugins/console/server/index.ts +++ b/src/plugins/console/server/index.ts @@ -10,7 +10,7 @@ import { PluginInitializerContext } from 'kibana/server'; import { ConsoleServerPlugin } from './plugin'; -export { ConsoleSetup, ConsoleStart } from './types'; +export type { ConsoleSetup, ConsoleStart } from './types'; export { config } from './config'; diff --git a/src/plugins/custom_integrations/README.md b/src/plugins/custom_integrations/README.md index e7af518e21ec12..a94c7fc97a9704 100755 --- a/src/plugins/custom_integrations/README.md +++ b/src/plugins/custom_integrations/README.md @@ -6,4 +6,4 @@ Register add-data cards ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index 98148bb22c8164..f00b4c39405d52 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -40,16 +40,11 @@ export const INTEGRATION_CATEGORY_DISPLAY = { web: 'Web', // Kibana added - communication: 'Communication', - customer_support: 'Customer Support', - document_storage: 'Document Storage', - enterprise_management: 'Enterprise Management', - knowledge_platform: 'Knowledge Platform', + communications: 'Communications', + file_storage: 'File storage', language_client: 'Language client', - project_management: 'Project Management', - software_development: 'Software Development', upload_file: 'Upload a file', - website_search: 'Website Search', + website_search: 'Website search', }; /** diff --git a/src/plugins/custom_integrations/public/components/replacement_card/index.ts b/src/plugins/custom_integrations/public/components/replacement_card/index.ts index 631dc1fcb2ba2c..3c378d7603f951 100644 --- a/src/plugins/custom_integrations/public/components/replacement_card/index.ts +++ b/src/plugins/custom_integrations/public/components/replacement_card/index.ts @@ -8,7 +8,8 @@ import { ReplacementCard } from './replacement_card'; -export { ReplacementCard, Props } from './replacement_card'; +export type { Props } from './replacement_card'; +export { ReplacementCard } from './replacement_card'; // required for dynamic import using React.lazy() // eslint-disable-next-line import/no-default-export diff --git a/src/plugins/custom_integrations/public/index.ts b/src/plugins/custom_integrations/public/index.ts index 91da75c634a44a..27176c4c5acbe9 100755 --- a/src/plugins/custom_integrations/public/index.ts +++ b/src/plugins/custom_integrations/public/index.ts @@ -14,7 +14,7 @@ export function plugin() { return new CustomIntegrationsPlugin(); } -export { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; +export type { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; export { withSuspense, LazyReplacementCard } from './components'; export { filterCustomIntegrations } from './services/find'; diff --git a/src/plugins/custom_integrations/server/index.ts b/src/plugins/custom_integrations/server/index.ts index 00372df501435c..e2da055e745c24 100755 --- a/src/plugins/custom_integrations/server/index.ts +++ b/src/plugins/custom_integrations/server/index.ts @@ -17,7 +17,7 @@ export function plugin(initializerContext: PluginInitializerContext) { return new CustomIntegrationsPlugin(initializerContext); } -export { CustomIntegrationsPluginSetup, CustomIntegrationsPluginStart } from './types'; +export type { CustomIntegrationsPluginSetup, CustomIntegrationsPluginStart } from './types'; export type { IntegrationCategory, CustomIntegration } from '../common'; diff --git a/src/plugins/custom_integrations/storybook/manager.ts b/src/plugins/custom_integrations/storybook/manager.ts index 99c01efdddfdc0..0b4454798d4e6c 100644 --- a/src/plugins/custom_integrations/storybook/manager.ts +++ b/src/plugins/custom_integrations/storybook/manager.ts @@ -14,7 +14,7 @@ addons.setConfig({ theme: create({ base: 'light', brandTitle: 'Kibana Custom Integrations Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/master/src/plugins/custom_integrations', + brandUrl: 'https://github.com/elastic/kibana/tree/main/src/plugins/custom_integrations', }), showPanel: true.valueOf, selectedPanel: PANEL_ID, diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts index 1ed5bfba3abb91..73e01693977d99 100644 --- a/src/plugins/dashboard/common/index.ts +++ b/src/plugins/dashboard/common/index.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -export { GridData } from './embeddable/types'; -export { +export type { GridData } from './embeddable/types'; +export type { RawSavedDashboardPanel730ToLatest, DashboardDoc730ToLatest, DashboardDoc700To720, DashboardDocPre700, } from './bwc/types'; -export { +export type { DashboardContainerStateWithType, SavedDashboardPanelTo60, SavedDashboardPanel610, diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts index 827ae5bcb4419b..2c6263314cb25b 100644 --- a/src/plugins/dashboard/public/application/actions/index.ts +++ b/src/plugins/dashboard/public/application/actions/index.ts @@ -6,39 +6,22 @@ * Side Public License, v 1. */ +export type { ExpandPanelActionContext } from './expand_panel_action'; +export { ExpandPanelAction, ACTION_EXPAND_PANEL } from './expand_panel_action'; +export type { ReplacePanelActionContext } from './replace_panel_action'; +export { ReplacePanelAction, ACTION_REPLACE_PANEL } from './replace_panel_action'; +export type { ClonePanelActionContext } from './clone_panel_action'; +export { ClonePanelAction, ACTION_CLONE_PANEL } from './clone_panel_action'; +export type { AddToLibraryActionContext } from './add_to_library_action'; +export { AddToLibraryAction, ACTION_ADD_TO_LIBRARY } from './add_to_library_action'; +export type { UnlinkFromLibraryActionContext } from './unlink_from_library_action'; +export { UnlinkFromLibraryAction, ACTION_UNLINK_FROM_LIBRARY } from './unlink_from_library_action'; +export type { CopyToDashboardActionContext } from './copy_to_dashboard_action'; +export { CopyToDashboardAction, ACTION_COPY_TO_DASHBOARD } from './copy_to_dashboard_action'; +export type { LibraryNotificationActionContext } from './library_notification_action'; export { - ExpandPanelAction, - ExpandPanelActionContext, - ACTION_EXPAND_PANEL, -} from './expand_panel_action'; -export { - ReplacePanelAction, - ReplacePanelActionContext, - ACTION_REPLACE_PANEL, -} from './replace_panel_action'; -export { - ClonePanelAction, - ClonePanelActionContext, - ACTION_CLONE_PANEL, -} from './clone_panel_action'; -export { - AddToLibraryAction, - AddToLibraryActionContext, - ACTION_ADD_TO_LIBRARY, -} from './add_to_library_action'; -export { - UnlinkFromLibraryAction, - UnlinkFromLibraryActionContext, - ACTION_UNLINK_FROM_LIBRARY, -} from './unlink_from_library_action'; -export { - CopyToDashboardAction, - CopyToDashboardActionContext, - ACTION_COPY_TO_DASHBOARD, -} from './copy_to_dashboard_action'; -export { - LibraryNotificationActionContext, LibraryNotificationAction, ACTION_LIBRARY_NOTIFICATION, } from './library_notification_action'; -export { ExportContext, ExportCSVAction, ACTION_EXPORT_CSV } from './export_csv_action'; +export type { ExportContext } from './export_csv_action'; +export { ExportCSVAction, ACTION_EXPORT_CSV } from './export_csv_action'; diff --git a/src/plugins/dashboard/public/application/embeddable/index.ts b/src/plugins/dashboard/public/application/embeddable/index.ts index b3ee0f83ee8523..ce8bb5b7169ac0 100644 --- a/src/plugins/dashboard/public/application/embeddable/index.ts +++ b/src/plugins/dashboard/public/application/embeddable/index.ts @@ -6,10 +6,8 @@ * Side Public License, v 1. */ -export { - DashboardContainerFactoryDefinition, - DashboardContainerFactory, -} from './dashboard_container_factory'; +export type { DashboardContainerFactory } from './dashboard_container_factory'; +export { DashboardContainerFactoryDefinition } from './dashboard_container_factory'; export type { DashboardContainer } from './dashboard_container'; export { createPanelState } from './panel'; diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index ff7708689c2211..f25a92275d7236 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -16,22 +16,19 @@ export { } from './application'; export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -export { +export type { DashboardSetup, DashboardStart, DashboardUrlGenerator, DashboardFeatureFlagConfig, } from './plugin'; -export { - DASHBOARD_APP_URL_GENERATOR, - createDashboardUrlGenerator, - DashboardUrlGeneratorState, -} from './url_generator'; -export { DashboardAppLocator, DashboardAppLocatorParams } from './locator'; +export type { DashboardUrlGeneratorState } from './url_generator'; +export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator } from './url_generator'; +export type { DashboardAppLocator, DashboardAppLocatorParams } from './locator'; -export { DashboardSavedObject } from './saved_dashboards'; -export { SavedDashboardPanel, DashboardContainerInput } from './types'; +export type { DashboardSavedObject } from './saved_dashboards'; +export type { SavedDashboardPanel, DashboardContainerInput } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); diff --git a/src/plugins/dashboard/public/services/core.ts b/src/plugins/dashboard/public/services/core.ts index e805c6cd706a99..38c91842de4f29 100644 --- a/src/plugins/dashboard/public/services/core.ts +++ b/src/plugins/dashboard/public/services/core.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -export { +export type { AppMountParameters, CoreSetup, Capabilities, PluginInitializerContext, - ScopedHistory, NotificationsStart, ApplicationStart, } from '../../../../core/public'; +export { ScopedHistory } from '../../../../core/public'; diff --git a/src/plugins/dashboard/public/services/home.ts b/src/plugins/dashboard/public/services/home.ts index 692a1218b9535d..ebbb715e53ca69 100644 --- a/src/plugins/dashboard/public/services/home.ts +++ b/src/plugins/dashboard/public/services/home.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../../home/public'; +export type { HomePublicPluginSetup } from '../../../home/public'; +export { FeatureCatalogueCategory } from '../../../home/public'; diff --git a/src/plugins/dashboard/public/services/kibana_react.ts b/src/plugins/dashboard/public/services/kibana_react.ts index 203b6aa9cd2cbf..4d5a3a5b576579 100644 --- a/src/plugins/dashboard/public/services/kibana_react.ts +++ b/src/plugins/dashboard/public/services/kibana_react.ts @@ -6,6 +6,11 @@ * Side Public License, v 1. */ +export type { + KibanaReactContext, + KibanaReactContextValue, + ExitFullScreenButtonProps, +} from '../../../kibana_react/public'; export { context, useKibana, @@ -13,9 +18,6 @@ export { toMountPoint, TableListView, reactToUiComponent, - KibanaReactContext, ExitFullScreenButton, KibanaContextProvider, - KibanaReactContextValue, - ExitFullScreenButtonProps, } from '../../../kibana_react/public'; diff --git a/src/plugins/dashboard/public/services/kibana_utils.ts b/src/plugins/dashboard/public/services/kibana_utils.ts index 94128f902df02c..599e9b73f504f9 100644 --- a/src/plugins/dashboard/public/services/kibana_utils.ts +++ b/src/plugins/dashboard/public/services/kibana_utils.ts @@ -6,19 +6,21 @@ * Side Public License, v 1. */ +export type { + ISyncStateRef, + IKbnUrlStateStorage, + ReduxLikeStateContainer, +} from '../../../kibana_utils/public'; export { Storage, unhashUrl, syncState, - ISyncStateRef, getQueryParams, setStateToKbnUrl, removeQueryParam, withNotifyOnErrors, - IKbnUrlStateStorage, createKbnUrlTracker, SavedObjectNotFound, createStateContainer, - ReduxLikeStateContainer, createKbnUrlStateStorage, } from '../../../kibana_utils/public'; diff --git a/src/plugins/dashboard/public/services/navigation.ts b/src/plugins/dashboard/public/services/navigation.ts index 60373b0eb44ec4..eb1622eba35e04 100644 --- a/src/plugins/dashboard/public/services/navigation.ts +++ b/src/plugins/dashboard/public/services/navigation.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { NavigationPublicPluginStart } from '../../../navigation/public'; +export type { NavigationPublicPluginStart } from '../../../navigation/public'; diff --git a/src/plugins/dashboard/public/services/presentation_util.ts b/src/plugins/dashboard/public/services/presentation_util.ts index 17bc97db9ac633..d640b6326729d2 100644 --- a/src/plugins/dashboard/public/services/presentation_util.ts +++ b/src/plugins/dashboard/public/services/presentation_util.ts @@ -6,9 +6,5 @@ * Side Public License, v 1. */ -export { - PresentationUtilPluginStart, - LazyDashboardPicker, - withSuspense, - useLabs, -} from '../../../presentation_util/public'; +export type { PresentationUtilPluginStart } from '../../../presentation_util/public'; +export { LazyDashboardPicker, withSuspense, useLabs } from '../../../presentation_util/public'; diff --git a/src/plugins/dashboard/public/services/saved_objects.ts b/src/plugins/dashboard/public/services/saved_objects.ts index bd4a73f8ed8173..305ff3c2014f87 100644 --- a/src/plugins/dashboard/public/services/saved_objects.ts +++ b/src/plugins/dashboard/public/services/saved_objects.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -export { +export type { SaveResult, SavedObject, - showSaveModal, - SavedObjectLoader, SavedObjectsStart, SavedObjectSaveOpts, + SavedObjectLoaderFindOptions, +} from '../../../saved_objects/public'; +export { + showSaveModal, + SavedObjectLoader, SavedObjectSaveModal, getSavedObjectFinder, - SavedObjectLoaderFindOptions, } from '../../../saved_objects/public'; diff --git a/src/plugins/dashboard/public/services/share.ts b/src/plugins/dashboard/public/services/share.ts index 38a516ff80ecb2..7ed9b86571596c 100644 --- a/src/plugins/dashboard/public/services/share.ts +++ b/src/plugins/dashboard/public/services/share.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -export { +export type { SharePluginStart, SharePluginSetup, - downloadMultipleAs, UrlGeneratorContract, } from '../../../share/public'; +export { downloadMultipleAs } from '../../../share/public'; diff --git a/src/plugins/dashboard/public/services/spaces.ts b/src/plugins/dashboard/public/services/spaces.ts index 89a0acaf611bd0..4ebe6644e23932 100644 --- a/src/plugins/dashboard/public/services/spaces.ts +++ b/src/plugins/dashboard/public/services/spaces.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { SpacesPluginStart } from '../../../../../x-pack/plugins/spaces/public'; +export type { SpacesPluginStart } from '../../../../../x-pack/plugins/spaces/public'; diff --git a/src/plugins/dashboard/public/services/ui_actions.ts b/src/plugins/dashboard/public/services/ui_actions.ts index 017f5b232d11ed..5dc2e016a121c0 100644 --- a/src/plugins/dashboard/public/services/ui_actions.ts +++ b/src/plugins/dashboard/public/services/ui_actions.ts @@ -6,9 +6,5 @@ * Side Public License, v 1. */ -export { - Action, - IncompatibleActionError, - UiActionsSetup, - UiActionsStart, -} from '../../../ui_actions/public'; +export type { Action, UiActionsSetup, UiActionsStart } from '../../../ui_actions/public'; +export { IncompatibleActionError } from '../../../ui_actions/public'; diff --git a/src/plugins/dashboard/public/services/usage_collection.ts b/src/plugins/dashboard/public/services/usage_collection.ts index 4bf43106f4e8fd..dee7499c611384 100644 --- a/src/plugins/dashboard/public/services/usage_collection.ts +++ b/src/plugins/dashboard/public/services/usage_collection.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { UsageCollectionSetup } from '../../../usage_collection/public'; +export type { UsageCollectionSetup } from '../../../usage_collection/public'; diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index c4cc00838a3438..d4a6cb20bc5519 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -39,7 +39,7 @@ import { DashboardAppLocatorParams } from './locator'; import { SpacesPluginStart } from './services/spaces'; import type { DashboardControlGroupInput } from './application/lib/dashboard_control_group'; -export { SavedDashboardPanel }; +export type { SavedDashboardPanel }; export type NavAction = (anchorElement?: any) => void; export interface SavedDashboardPanelMap { diff --git a/src/plugins/dashboard/server/index.ts b/src/plugins/dashboard/server/index.ts index 3af8833d5e4a41..c99ca43ad835e5 100644 --- a/src/plugins/dashboard/server/index.ts +++ b/src/plugins/dashboard/server/index.ts @@ -24,5 +24,5 @@ export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); } -export { DashboardPluginSetup, DashboardPluginStart } from './types'; +export type { DashboardPluginSetup, DashboardPluginStart } from './types'; export { findByValueEmbeddables } from './usage/find_by_value_embeddables'; diff --git a/src/plugins/data/common/es_query/index.ts b/src/plugins/data/common/es_query/index.ts index 7029e9d064b214..28361114be6e17 100644 --- a/src/plugins/data/common/es_query/index.ts +++ b/src/plugins/data/common/es_query/index.ts @@ -373,6 +373,21 @@ type EsQueryConfig = oldEsQueryConfig; * @removeBy 8.1 */ +export type { + Filter, + RangeFilterMeta, + RangeFilterParams, + ExistsFilter, + PhrasesFilter, + PhraseFilter, + MatchAllFilter, + CustomFilter, + RangeFilter, + KueryNode, + FilterMeta, + IFieldSubType, + EsQueryConfig, +}; export { COMPARE_ALL_OPTIONS, compareFilters, @@ -414,17 +429,4 @@ export { onlyDisabledFiltersChanged, uniqFilters, FilterStateStore, - Filter, - RangeFilterMeta, - RangeFilterParams, - ExistsFilter, - PhrasesFilter, - PhraseFilter, - MatchAllFilter, - CustomFilter, - RangeFilter, - KueryNode, - FilterMeta, - IFieldSubType, - EsQueryConfig, }; diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index 34a75f8f24dc6a..9a58b73d2c49c6 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -51,6 +51,12 @@ export type { IndexPatternLoadStartDependencies, IndexPatternLoadExpressionFunctionDefinition, } from '../../data_views/common'; +export type { + IndexPatternsContract, + DataViewsContract, + IndexPatternListItem, + DataViewListItem, +} from '../../data_views/common'; export { RUNTIME_FIELD_TYPES, FLEET_ASSETS_TO_IGNORE, @@ -64,13 +70,9 @@ export { DataViewType, IndexPatternType, IndexPatternsService, - IndexPatternsContract, DataViewsService, - DataViewsContract, IndexPattern, - IndexPatternListItem, DataView, - DataViewListItem, DuplicateDataViewError, DataViewSavedObjectConflictError, getIndexPatternLoadMeta, diff --git a/src/plugins/data/common/kbn_field_types/types.ts b/src/plugins/data/common/kbn_field_types/types.ts index cea35a53e9da1b..29471e95a36b8d 100644 --- a/src/plugins/data/common/kbn_field_types/types.ts +++ b/src/plugins/data/common/kbn_field_types/types.ts @@ -8,4 +8,5 @@ import { KbnFieldTypeOptions, ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; -export { KbnFieldTypeOptions, ES_FIELD_TYPES, KBN_FIELD_TYPES }; +export type { KbnFieldTypeOptions }; +export { ES_FIELD_TYPES, KBN_FIELD_TYPES }; diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index fec02a5ae23fda..34c4f0fbf98c50 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -89,14 +89,14 @@ import { aggSinglePercentile, } from './'; -export { IAggConfig, AggConfigSerialized } from './agg_config'; -export { CreateAggConfigParams, IAggConfigs } from './agg_configs'; -export { IAggType } from './agg_type'; -export { AggParam, AggParamOption } from './agg_params'; -export { IFieldParamType } from './param_types'; -export { IMetricAggType } from './metrics/metric_agg_type'; -export { IpRangeKey } from './buckets/lib/ip_range'; -export { OptionedValueProp } from './param_types/optioned'; +export type { IAggConfig, AggConfigSerialized } from './agg_config'; +export type { CreateAggConfigParams, IAggConfigs } from './agg_configs'; +export type { IAggType } from './agg_type'; +export type { AggParam, AggParamOption } from './agg_params'; +export type { IFieldParamType } from './param_types'; +export type { IMetricAggType } from './metrics/metric_agg_type'; +export type { IpRangeKey } from './buckets/lib/ip_range'; +export type { OptionedValueProp } from './param_types/optioned'; /** @internal */ export interface AggsCommonSetup { diff --git a/src/plugins/data/public/actions/index.ts b/src/plugins/data/public/actions/index.ts index cb4eff5c274bbc..0ec33f7e46523f 100644 --- a/src/plugins/data/public/actions/index.ts +++ b/src/plugins/data/public/actions/index.ts @@ -6,11 +6,8 @@ * Side Public License, v 1. */ -export { - ACTION_GLOBAL_APPLY_FILTER, - createFilterAction, - ApplyGlobalFilterActionContext, -} from './apply_filter_action'; +export type { ApplyGlobalFilterActionContext } from './apply_filter_action'; +export { ACTION_GLOBAL_APPLY_FILTER, createFilterAction } from './apply_filter_action'; export { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click'; export { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select'; export * from './select_range_action'; diff --git a/src/plugins/data/public/autocomplete/collectors/index.ts b/src/plugins/data/public/autocomplete/collectors/index.ts index 5cfaab19787dab..e9b5736008ab15 100644 --- a/src/plugins/data/public/autocomplete/collectors/index.ts +++ b/src/plugins/data/public/autocomplete/collectors/index.ts @@ -7,4 +7,5 @@ */ export { createUsageCollector } from './create_usage_collector'; -export { AUTOCOMPLETE_EVENT_TYPE, AutocompleteUsageCollector } from './types'; +export type { AutocompleteUsageCollector } from './types'; +export { AUTOCOMPLETE_EVENT_TYPE } from './types'; diff --git a/src/plugins/data/public/autocomplete/index.ts b/src/plugins/data/public/autocomplete/index.ts index 00aa14b86409a0..b36af8f12eb52f 100644 --- a/src/plugins/data/public/autocomplete/index.ts +++ b/src/plugins/data/public/autocomplete/index.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ -export { +export type { QuerySuggestion, - QuerySuggestionTypes, QuerySuggestionGetFn, QuerySuggestionGetFnArgs, QuerySuggestionBasic, QuerySuggestionField, } from './providers/query_suggestion_provider'; +export { QuerySuggestionTypes } from './providers/query_suggestion_provider'; -export { AutocompleteService, AutocompleteSetup, AutocompleteStart } from './autocomplete_service'; +export type { AutocompleteSetup, AutocompleteStart } from './autocomplete_service'; +export { AutocompleteService } from './autocomplete_service'; diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index 0c98d0733c6474..588bac4739c538 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -56,7 +56,7 @@ export const setupValueSuggestionProvider = ( } const requestSuggestions = memoize( - ( + ( index: string, field: IFieldType, query: string, @@ -68,7 +68,7 @@ export const setupValueSuggestionProvider = ( ) => { usageCollector?.trackRequest(); return core.http - .fetch(`/api/kibana/suggestions/values/${index}`, { + .fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', body: JSON.stringify({ query, diff --git a/src/plugins/data/public/deprecated.ts b/src/plugins/data/public/deprecated.ts index 163d3298582932..8b90f92b932e03 100644 --- a/src/plugins/data/public/deprecated.ts +++ b/src/plugins/data/public/deprecated.ts @@ -137,7 +137,7 @@ export const esFilters = { /** * Deprecated type exports */ -export { +export type { KueryNode, RangeFilter, RangeFilterMeta, @@ -149,9 +149,8 @@ export { MatchAllFilter, IFieldSubType, EsQueryConfig, - isFilter, - isFilters, }; +export { isFilter, isFilters }; /** * @deprecated Import helpers from the "@kbn/es-query" package directly instead. diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 4d51a7ae0ad772..0b749d90f71523 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -70,29 +70,26 @@ export const indexPatterns = { validate: validateDataView, }; -export { - IndexPatternsContract, - DataViewsContract, - IndexPattern, - IndexPatternField, - TypeMeta, -} from './data_views'; +export type { IndexPatternsContract, DataViewsContract, TypeMeta } from './data_views'; +export { IndexPattern, IndexPatternField } from './data_views'; -export { +export type { IIndexPattern, IFieldType, - ES_FIELD_TYPES, - KBN_FIELD_TYPES, IndexPatternAttributes, - UI_SETTINGS, AggregationRestrictions as IndexPatternAggRestrictions, IndexPatternSpec, IndexPatternLoadExpressionFunctionDefinition, - fieldList, GetFieldsOptions, AggregationRestrictions, - IndexPatternType, IndexPatternListItem, +} from '../common'; +export { + ES_FIELD_TYPES, + KBN_FIELD_TYPES, + UI_SETTINGS, + fieldList, + IndexPatternType, DuplicateDataViewError, } from '../common'; @@ -217,7 +214,8 @@ export type { SearchUsageCollector, } from './search'; -export { ISearchOptions, isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; +export type { ISearchOptions } from '../common'; +export { isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; // Search namespace export const search = { @@ -301,7 +299,8 @@ export { export { isTimeRange, isQuery } from '../common'; -export { ACTION_GLOBAL_APPLY_FILTER, ApplyGlobalFilterActionContext } from './actions'; +export type { ApplyGlobalFilterActionContext } from './actions'; +export { ACTION_GLOBAL_APPLY_FILTER } from './actions'; export { APPLY_FILTER_TRIGGER } from './triggers'; /* diff --git a/src/plugins/data/public/now_provider/index.ts b/src/plugins/data/public/now_provider/index.ts index 4d22d674b60b41..b3bfd31fca2c41 100644 --- a/src/plugins/data/public/now_provider/index.ts +++ b/src/plugins/data/public/now_provider/index.ts @@ -6,8 +6,5 @@ * Side Public License, v 1. */ -export { - NowProvider, - NowProviderInternalContract, - NowProviderPublicContract, -} from './now_provider'; +export type { NowProviderInternalContract, NowProviderPublicContract } from './now_provider'; +export { NowProvider } from './now_provider'; diff --git a/src/plugins/data/public/query/query_string/index.ts b/src/plugins/data/public/query/query_string/index.ts index feef1965f8b244..63cc777f6688d5 100644 --- a/src/plugins/data/public/query/query_string/index.ts +++ b/src/plugins/data/public/query/query_string/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { QueryStringContract, QueryStringManager } from './query_string_manager'; +export type { QueryStringContract } from './query_string_manager'; +export { QueryStringManager } from './query_string_manager'; diff --git a/src/plugins/data/public/query/saved_query/index.ts b/src/plugins/data/public/query/saved_query/index.ts index 6cde77c7a77227..03d8f0b35b9b11 100644 --- a/src/plugins/data/public/query/saved_query/index.ts +++ b/src/plugins/data/public/query/saved_query/index.ts @@ -6,5 +6,10 @@ * Side Public License, v 1. */ -export { SavedQuery, SavedQueryAttributes, SavedQueryService, SavedQueryTimeFilter } from './types'; +export type { + SavedQuery, + SavedQueryAttributes, + SavedQueryService, + SavedQueryTimeFilter, +} from './types'; export { createSavedQueryService } from './saved_query_service'; diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.ts b/src/plugins/data/public/query/saved_query/saved_query_service.ts index 8ec9167a3a0c2a..17b47c78c7000a 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.ts @@ -12,14 +12,14 @@ import { SavedQueryAttributes } from '../../../common'; export const createSavedQueryService = (http: HttpStart) => { const createQuery = async (attributes: SavedQueryAttributes, { overwrite = false } = {}) => { - const savedQuery = await http.post('/api/saved_query/_create', { + const savedQuery = await http.post('/api/saved_query/_create', { body: JSON.stringify(attributes), }); return savedQuery; }; const updateQuery = async (id: string, attributes: SavedQueryAttributes) => { - const savedQuery = await http.put(`/api/saved_query/${id}`, { + const savedQuery = await http.put(`/api/saved_query/${id}`, { body: JSON.stringify(attributes), }); return savedQuery; @@ -27,9 +27,10 @@ export const createSavedQueryService = (http: HttpStart) => { // we have to tell the saved objects client how many to fetch, otherwise it defaults to fetching 20 per page const getAllSavedQueries = async (): Promise => { - const { savedQueries } = await http.post('/api/saved_query/_find', { - body: JSON.stringify({ perPage: 10000 }), - }); + const { savedQueries } = await http.post<{ savedQueries: SavedQuery[] }>( + '/api/saved_query/_find', + { body: JSON.stringify({ perPage: 10000 }) } + ); return savedQueries; }; @@ -39,7 +40,10 @@ export const createSavedQueryService = (http: HttpStart) => { perPage: number = 50, page: number = 1 ): Promise<{ total: number; queries: SavedQuery[] }> => { - const { total, savedQueries: queries } = await http.post('/api/saved_query/_find', { + const { total, savedQueries: queries } = await http.post<{ + savedQueries: SavedQuery[]; + total: number; + }>('/api/saved_query/_find', { body: JSON.stringify({ page, perPage, search }), }); @@ -47,15 +51,15 @@ export const createSavedQueryService = (http: HttpStart) => { }; const getSavedQuery = (id: string): Promise => { - return http.get(`/api/saved_query/${id}`); + return http.get(`/api/saved_query/${id}`); }; const deleteSavedQuery = (id: string) => { - return http.delete(`/api/saved_query/${id}`); + return http.delete<{}>(`/api/saved_query/${id}`); }; const getSavedQueryCount = async (): Promise => { - return http.get('/api/saved_query/_count'); + return http.get('/api/saved_query/_count'); }; return { diff --git a/src/plugins/data/public/query/state_sync/index.ts b/src/plugins/data/public/query/state_sync/index.ts index dac51411dfd400..58740cfab06d00 100644 --- a/src/plugins/data/public/query/state_sync/index.ts +++ b/src/plugins/data/public/query/state_sync/index.ts @@ -8,4 +8,4 @@ export { connectToQueryState } from './connect_to_query_state'; export { syncQueryStateWithUrl } from './sync_state_with_url'; -export { QueryState, QueryStateChange } from './types'; +export type { QueryState, QueryStateChange } from './types'; diff --git a/src/plugins/data/public/query/timefilter/index.ts b/src/plugins/data/public/query/timefilter/index.ts index 3dfd4e0fe514f5..604213054fd023 100644 --- a/src/plugins/data/public/query/timefilter/index.ts +++ b/src/plugins/data/public/query/timefilter/index.ts @@ -6,11 +6,14 @@ * Side Public License, v 1. */ -export { TimefilterService, TimefilterSetup } from './timefilter_service'; +export type { TimefilterSetup } from './timefilter_service'; +export { TimefilterService } from './timefilter_service'; export * from './types'; -export { Timefilter, TimefilterContract, AutoRefreshDoneFn } from './timefilter'; -export { TimeHistory, TimeHistoryContract } from './time_history'; +export type { TimefilterContract, AutoRefreshDoneFn } from './timefilter'; +export { Timefilter } from './timefilter'; +export type { TimeHistoryContract } from './time_history'; +export { TimeHistory } from './time_history'; export { changeTimeFilter, convertRangeFilterToTimeRangeString } from './lib/change_time_filter'; export { extractTimeFilter, extractTimeRange } from './lib/extract_time_filter'; export { validateTimeRange } from './lib/validate_timerange'; diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 3b537562586a7b..f3520abb2f46ec 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -25,7 +25,7 @@ import { import { TimeHistoryContract } from './time_history'; import { createAutoRefreshLoop, AutoRefreshDoneFn } from './lib/auto_refresh_loop'; -export { AutoRefreshDoneFn }; +export type { AutoRefreshDoneFn }; // TODO: remove! export class Timefilter { diff --git a/src/plugins/data/public/query/timefilter/types.ts b/src/plugins/data/public/query/timefilter/types.ts index 66584358ccb343..3c35ae2d79ae5f 100644 --- a/src/plugins/data/public/query/timefilter/types.ts +++ b/src/plugins/data/public/query/timefilter/types.ts @@ -23,4 +23,4 @@ export type InputTimeRange = to: Moment; }; -export { TimeRangeBounds } from '../../../common'; +export type { TimeRangeBounds } from '../../../common'; diff --git a/src/plugins/data/public/search/aggs/types.ts b/src/plugins/data/public/search/aggs/types.ts index 34ec3415427d34..5a8110034bbc12 100644 --- a/src/plugins/data/public/search/aggs/types.ts +++ b/src/plugins/data/public/search/aggs/types.ts @@ -9,4 +9,4 @@ import { AggsCommonSetup } from '../../../common'; export type AggsSetup = AggsCommonSetup; -export { AggsStart } from '../../../common'; +export type { AggsStart } from '../../../common'; diff --git a/src/plugins/data/public/search/collectors/index.ts b/src/plugins/data/public/search/collectors/index.ts index b6640a8e61f6c5..9eb96be5a04551 100644 --- a/src/plugins/data/public/search/collectors/index.ts +++ b/src/plugins/data/public/search/collectors/index.ts @@ -7,4 +7,5 @@ */ export { createUsageCollector } from './create_usage_collector'; -export { SEARCH_EVENT_TYPE, SearchUsageCollector } from './types'; +export type { SearchUsageCollector } from './types'; +export { SEARCH_EVENT_TYPE } from './types'; diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 21d607eedb1524..821f16e0cf68ae 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -8,46 +8,51 @@ export * from './expressions'; -export { +export type { ISearchSetup, ISearchStart, ISearchStartSearchSource, SearchUsageCollector, } from './types'; -export { - ES_SEARCH_STRATEGY, +export type { EsQuerySortValue, - extractReferences as extractSearchSourceReferences, - getSearchParamsFromRequest, IEsSearchRequest, IEsSearchResponse, IKibanaSearchRequest, IKibanaSearchResponse, - injectReferences as injectSearchSourceReferences, ISearchGeneric, ISearchSource, - parseSearchSourceJSON, SearchError, SearchRequest, - SearchSource, SearchSourceDependencies, SearchSourceFields, - SortDirection, } from '../../common/search'; export { - SessionService, + ES_SEARCH_STRATEGY, + extractReferences as extractSearchSourceReferences, + getSearchParamsFromRequest, + injectReferences as injectSearchSourceReferences, + parseSearchSourceJSON, + SearchSource, + SortDirection, +} from '../../common/search'; +export type { ISessionService, SearchSessionInfoProvider, + ISessionsClient, + WaitUntilNextSessionCompletesOptions, +} from './session'; +export { + SessionService, SearchSessionState, SessionsClient, - ISessionsClient, noSearchSessionStorageCapabilityMessage, SEARCH_SESSIONS_MANAGEMENT_ID, waitUntilNextSessionCompletes$, - WaitUntilNextSessionCompletesOptions, } from './session'; export { getEsPreference } from './es_search'; -export { SearchInterceptor, SearchInterceptorDeps } from './search_interceptor'; +export type { SearchInterceptorDeps } from './search_interceptor'; +export { SearchInterceptor } from './search_interceptor'; export * from './errors'; diff --git a/src/plugins/data/public/search/search_interceptor/index.ts b/src/plugins/data/public/search/search_interceptor/index.ts index 411c4beefe96ca..bf6930276020e5 100644 --- a/src/plugins/data/public/search/search_interceptor/index.ts +++ b/src/plugins/data/public/search/search_interceptor/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { SearchInterceptor, ISearchInterceptor, SearchInterceptorDeps } from './search_interceptor'; +export type { ISearchInterceptor, SearchInterceptorDeps } from './search_interceptor'; +export { SearchInterceptor } from './search_interceptor'; diff --git a/src/plugins/data/public/search/session/index.ts b/src/plugins/data/public/search/session/index.ts index ce578378a2fe81..c48362538a5fda 100644 --- a/src/plugins/data/public/search/session/index.ts +++ b/src/plugins/data/public/search/session/index.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -export { SessionService, ISessionService, SearchSessionInfoProvider } from './session_service'; +export type { ISessionService, SearchSessionInfoProvider } from './session_service'; +export { SessionService } from './session_service'; export { SearchSessionState } from './search_session_state'; -export { SessionsClient, ISessionsClient } from './sessions_client'; +export type { ISessionsClient } from './sessions_client'; +export { SessionsClient } from './sessions_client'; export { noSearchSessionStorageCapabilityMessage } from './i18n'; export { SEARCH_SESSIONS_MANAGEMENT_ID } from './constants'; -export { - waitUntilNextSessionCompletes$, - WaitUntilNextSessionCompletesOptions, -} from './session_helpers'; +export type { WaitUntilNextSessionCompletesOptions } from './session_helpers'; +export { waitUntilNextSessionCompletes$ } from './session_helpers'; diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 764933d15e065b..3dacad42273aea 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -14,7 +14,7 @@ import { IndexPatternsContract } from '../../common'; import { UsageCollectionSetup } from '../../../usage_collection/public'; import { ISessionsClient, ISessionService } from './session'; -export { ISearchStartSearchSource, SearchUsageCollector }; +export type { ISearchStartSearchSource, SearchUsageCollector }; /** * The setup contract exposed by the Search plugin exposes the search strategy extension diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 1c8df359602b95..026db1b7c09ee6 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -6,8 +6,10 @@ * Side Public License, v 1. */ -export { IndexPatternSelectProps } from './index_pattern_select'; +export type { IndexPatternSelectProps } from './index_pattern_select'; export { FilterLabel, FilterItem } from './filter_bar'; -export { QueryStringInput, QueryStringInputProps } from './query_string_input'; -export { SearchBar, SearchBarProps, StatefulSearchBarProps } from './search_bar'; +export type { QueryStringInputProps } from './query_string_input'; +export { QueryStringInput } from './query_string_input'; +export type { SearchBarProps, StatefulSearchBarProps } from './search_bar'; +export { SearchBar } from './search_bar'; export { SuggestionsComponent } from './typeahead'; diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index 479414c458466b..f8c2f067d9ec5c 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -14,19 +14,17 @@ display: flex; flex: 1 1 100%; position: relative; + background-color: $euiFormBackgroundColor; + border-radius: $euiFormControlBorderRadius; - @include kbnThemeStyle('v8') { - background-color: $euiFormBackgroundColor; - border-radius: $euiFormControlBorderRadius; + &.kbnQueryBar__textareaWrap--hasPrepend { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } - &.kbnQueryBar__textareaWrap--hasPrepend { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - &.kbnQueryBar__textareaWrap--hasAppend { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } + &.kbnQueryBar__textareaWrap--hasAppend { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } } @@ -39,24 +37,17 @@ // shadow to line up correctly. padding: $euiSizeS; box-shadow: 0 0 0 1px $euiFormBorderColor; + padding-bottom: $euiSizeS + 1px; + // Firefox adds margin to textarea + margin: 0; - @include kbnThemeStyle('v7') { - padding-top: $euiSizeS + 2px; + &.kbnQueryBar__textarea--hasPrepend { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } - - @include kbnThemeStyle('v8') { - padding-bottom: $euiSizeS + 1px; - // Firefox adds margin to textarea - margin: 0; - - &.kbnQueryBar__textarea--hasPrepend { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - &.kbnQueryBar__textarea--hasAppend { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } + &.kbnQueryBar__textarea--hasAppend { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } &:not(.kbnQueryBar__textarea--autoHeight):not(:invalid) { diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index f71a3d3b0686a6..90db5abe418b7c 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -231,6 +231,7 @@ export default function QueryBarTopRow(props: QueryBarTopRowProps) { isDisabled={isDateRangeInvalid} isLoading={props.isLoading} onClick={onClickSubmitButton} + fill={false} data-test-subj="querySubmitButton" /> ); diff --git a/src/plugins/data/public/ui/saved_query_form/index.ts b/src/plugins/data/public/ui/saved_query_form/index.ts index f440294dc6eb75..1590b8fc989277 100644 --- a/src/plugins/data/public/ui/saved_query_form/index.ts +++ b/src/plugins/data/public/ui/saved_query_form/index.ts @@ -7,4 +7,5 @@ */ // @internal -export { SavedQueryMeta, SaveQueryForm } from '../saved_query_form/save_query_form'; +export type { SavedQueryMeta } from '../saved_query_form/save_query_form'; +export { SaveQueryForm } from '../saved_query_form/save_query_form'; diff --git a/src/plugins/data/public/ui/search_bar/index.tsx b/src/plugins/data/public/ui/search_bar/index.tsx index 64dacee4ad3637..fac421dd743d79 100644 --- a/src/plugins/data/public/ui/search_bar/index.tsx +++ b/src/plugins/data/public/ui/search_bar/index.tsx @@ -21,5 +21,5 @@ const WrappedSearchBar = (props: SearchBarProps) => ( ); export const SearchBar = injectI18n(withKibana(WrappedSearchBar)); -export { StatefulSearchBarProps } from './create_search_bar'; +export type { StatefulSearchBarProps } from './create_search_bar'; export type { SearchBarProps, SearchBarOwnProps } from './search_bar'; diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap index 7773f2209bf969..b59756ef1e90e1 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap @@ -1158,7 +1158,7 @@ exports[`Inspector Data View component should render single table without select textOnly={false} > = { deprecations: autocompleteConfigDeprecationProvider, diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index b9affe96ea2ddd..b7087e95c0a2c0 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -10,7 +10,8 @@ export * from './types'; export * from './strategies/es_search'; export * from './strategies/ese_search'; export * from './strategies/eql_search'; -export { usageProvider, SearchUsage, searchUsageObserver } from './collectors'; +export type { SearchUsage } from './collectors'; +export { usageProvider, searchUsageObserver } from './collectors'; export * from './aggs'; export * from './session'; export * from './errors/no_search_id_in_session'; diff --git a/src/plugins/data/server/search/strategies/es_search/index.ts b/src/plugins/data/server/search/strategies/es_search/index.ts index d43fab0a86e69a..53a791455e64e5 100644 --- a/src/plugins/data/server/search/strategies/es_search/index.ts +++ b/src/plugins/data/server/search/strategies/es_search/index.ts @@ -9,4 +9,5 @@ export { esSearchStrategyProvider } from './es_search_strategy'; export * from './request_utils'; export * from './response_utils'; -export { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from '../../../../common'; +export type { IEsSearchRequest, IEsSearchResponse } from '../../../../common'; +export { ES_SEARCH_STRATEGY } from '../../../../common'; diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts index b057a1ba84174e..b3123df2597cce 100644 --- a/src/plugins/data_views/common/index.ts +++ b/src/plugins/data_views/common/index.ts @@ -55,13 +55,10 @@ export type { SourceFilter, } from './types'; export { DataViewType, IndexPatternType } from './types'; -export { - IndexPatternsService, - IndexPatternsContract, - DataViewsService, - DataViewsContract, -} from './data_views'; -export { IndexPattern, IndexPatternListItem, DataView, DataViewListItem } from './data_views'; +export type { IndexPatternsContract, DataViewsContract } from './data_views'; +export { IndexPatternsService, DataViewsService } from './data_views'; +export type { IndexPatternListItem, DataViewListItem } from './data_views'; +export { IndexPattern, DataView } from './data_views'; export { DuplicateDataViewError, DataViewSavedObjectConflictError } from './errors'; export type { IndexPatternExpressionType, diff --git a/src/plugins/data_views/public/data_views/data_views_api_client.ts b/src/plugins/data_views/public/data_views/data_views_api_client.ts index d4da9a55c25d1a..90cc82f2a1ec02 100644 --- a/src/plugins/data_views/public/data_views/data_views_api_client.ts +++ b/src/plugins/data_views/public/data_views/data_views_api_client.ts @@ -19,9 +19,9 @@ export class DataViewsApiClient implements IDataViewsApiClient { this.http = http; } - private _request(url: string, query?: any) { + private _request(url: string, query?: any) { return this.http - .fetch(url, { + .fetch(url, { query, }) .catch((resp: any) => { @@ -60,7 +60,9 @@ export class DataViewsApiClient implements IDataViewsApiClient { } async hasUserIndexPattern(): Promise { - const response = await this._request(this._getUrl(['has_user_index_pattern'])); + const response = await this._request<{ result: boolean }>( + this._getUrl(['has_user_index_pattern']) + ); return response.result; } } diff --git a/src/plugins/data_views/public/index.ts b/src/plugins/data_views/public/index.ts index 3a6b5ccb237f29..650d2132212f8f 100644 --- a/src/plugins/data_views/public/index.ts +++ b/src/plugins/data_views/public/index.ts @@ -15,15 +15,15 @@ export { } from '../common/lib'; export { onRedirectNoIndexPattern } from './data_views'; -export { IndexPatternField, IIndexPatternFieldList, TypeMeta } from '../common'; +export type { IIndexPatternFieldList, TypeMeta } from '../common'; +export { IndexPatternField } from '../common'; +export type { IndexPatternsContract, DataViewsContract } from './data_views'; export { IndexPatternsService, - IndexPatternsContract, IndexPattern, DataViewsApiClient, DataViewsService, - DataViewsContract, DataView, } from './data_views'; export { UiSettingsPublicToCommon } from './ui_settings_wrapper'; diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts index a65d4d551cf7ca..1a8b7054802585 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts @@ -5,7 +5,6 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import { IndexPatternsFetcher } from '.'; import { ElasticsearchClient } from 'kibana/server'; import * as indexNotFoundException from './index_not_found_exception.json'; @@ -15,36 +14,36 @@ describe('Index Pattern Fetcher - server', () => { let esClient: ElasticsearchClient; const emptyResponse = { body: { - count: 0, + indices: [], }, }; const response = { body: { - count: 1115, + indices: ['b'], + fields: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }], }, }; const patternList = ['a', 'b', 'c']; beforeEach(() => { + jest.clearAllMocks(); esClient = { - count: jest.fn().mockResolvedValueOnce(emptyResponse).mockResolvedValue(response), + fieldCaps: jest.fn().mockResolvedValueOnce(emptyResponse).mockResolvedValue(response), } as unknown as ElasticsearchClient; indexPatterns = new IndexPatternsFetcher(esClient); }); - it('Removes pattern without matching indices', async () => { const result = await indexPatterns.validatePatternListActive(patternList); expect(result).toEqual(['b', 'c']); }); - it('Returns all patterns when all match indices', async () => { esClient = { - count: jest.fn().mockResolvedValue(response), + fieldCaps: jest.fn().mockResolvedValue(response), } as unknown as ElasticsearchClient; indexPatterns = new IndexPatternsFetcher(esClient); const result = await indexPatterns.validatePatternListActive(patternList); expect(result).toEqual(patternList); }); - it('Removes pattern when "index_not_found_exception" error is thrown', async () => { + it('Removes pattern when error is thrown', async () => { class ServerError extends Error { public body?: Record; constructor( @@ -56,9 +55,8 @@ describe('Index Pattern Fetcher - server', () => { this.body = errBody; } } - esClient = { - count: jest + fieldCaps: jest .fn() .mockResolvedValueOnce(response) .mockRejectedValue( @@ -69,4 +67,22 @@ describe('Index Pattern Fetcher - server', () => { const result = await indexPatterns.validatePatternListActive(patternList); expect(result).toEqual([patternList[0]]); }); + it('When allowNoIndices is false, run validatePatternListActive', async () => { + const fieldCapsMock = jest.fn(); + esClient = { + fieldCaps: fieldCapsMock.mockResolvedValue(response), + } as unknown as ElasticsearchClient; + indexPatterns = new IndexPatternsFetcher(esClient); + await indexPatterns.getFieldsForWildcard({ pattern: patternList }); + expect(fieldCapsMock.mock.calls).toHaveLength(4); + }); + it('When allowNoIndices is true, do not run validatePatternListActive', async () => { + const fieldCapsMock = jest.fn(); + esClient = { + fieldCaps: fieldCapsMock.mockResolvedValue(response), + } as unknown as ElasticsearchClient; + indexPatterns = new IndexPatternsFetcher(esClient, true); + await indexPatterns.getFieldsForWildcard({ pattern: patternList }); + expect(fieldCapsMock.mock.calls).toHaveLength(1); + }); }); diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts index 7dae85c920ebff..c054d547e956ff 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts @@ -36,12 +36,10 @@ interface FieldSubType { export class IndexPatternsFetcher { private elasticsearchClient: ElasticsearchClient; private allowNoIndices: boolean; - constructor(elasticsearchClient: ElasticsearchClient, allowNoIndices: boolean = false) { this.elasticsearchClient = elasticsearchClient; this.allowNoIndices = allowNoIndices; } - /** * Get a list of field objects for an index pattern that may contain wildcards * @@ -60,23 +58,22 @@ export class IndexPatternsFetcher { }): Promise { const { pattern, metaFields, fieldCapsOptions, type, rollupIndex } = options; const patternList = Array.isArray(pattern) ? pattern : pattern.split(','); + const allowNoIndices = fieldCapsOptions + ? fieldCapsOptions.allow_no_indices + : this.allowNoIndices; let patternListActive: string[] = patternList; // if only one pattern, don't bother with validation. We let getFieldCapabilities fail if the single pattern is bad regardless - if (patternList.length > 1) { + if (patternList.length > 1 && !allowNoIndices) { patternListActive = await this.validatePatternListActive(patternList); } const fieldCapsResponse = await getFieldCapabilities( this.elasticsearchClient, - // if none of the patterns are active, pass the original list to get an error - patternListActive.length > 0 ? patternListActive : patternList, + patternListActive, metaFields, { - allow_no_indices: fieldCapsOptions - ? fieldCapsOptions.allow_no_indices - : this.allowNoIndices, + allow_no_indices: allowNoIndices, } ); - if (type === 'rollup' && rollupIndex) { const rollupFields: FieldDescriptor[] = []; const rollupIndexCapabilities = getCapabilitiesForRollupIndices( @@ -87,13 +84,11 @@ export class IndexPatternsFetcher { ).body )[rollupIndex].aggs; const fieldCapsResponseObj = keyBy(fieldCapsResponse, 'name'); - // Keep meta fields metaFields!.forEach( (field: string) => fieldCapsResponseObj[field] && rollupFields.push(fieldCapsResponseObj[field]) ); - return mergeCapabilitiesWithFields( rollupIndexCapabilities, fieldCapsResponseObj, @@ -137,23 +132,20 @@ export class IndexPatternsFetcher { async validatePatternListActive(patternList: string[]) { const result = await Promise.all( patternList - .map((pattern) => - this.elasticsearchClient.count({ - index: pattern, - }) - ) - .map((p) => - p.catch((e) => { - if (e.body.error.type === 'index_not_found_exception') { - return { body: { count: 0 } }; - } - throw e; - }) - ) + .map(async (index) => { + const searchResponse = await this.elasticsearchClient.fieldCaps({ + index, + fields: '_id', + ignore_unavailable: true, + allow_no_indices: false, + }); + return searchResponse.body.indices.length > 0; + }) + .map((p) => p.catch(() => false)) ); return result.reduce( - (acc: string[], { body: { count } }, patternListIndex) => - count > 0 ? [...acc, patternList[patternListIndex]] : acc, + (acc: string[], isValid, patternListIndex) => + isValid ? [...acc, patternList[patternListIndex]] : acc, [] ); } diff --git a/src/plugins/data_views/server/index.ts b/src/plugins/data_views/server/index.ts index 1c7eeb073bbe21..7a4df9518b435a 100644 --- a/src/plugins/data_views/server/index.ts +++ b/src/plugins/data_views/server/index.ts @@ -7,14 +7,14 @@ */ export { getFieldByName, findIndexPatternById } from './utils'; +export type { FieldDescriptor } from './fetcher'; export { IndexPatternsFetcher, - FieldDescriptor, shouldReadFieldFromDocValues, mergeCapabilitiesWithFields, getCapabilitiesForRollupIndices, } from './fetcher'; -export { IndexPatternsServiceStart } from './types'; +export type { IndexPatternsServiceStart } from './types'; import { PluginInitializerContext } from 'src/core/server'; import { DataViewsServerPlugin } from './plugin'; @@ -30,8 +30,8 @@ export function plugin(initializerContext: PluginInitializerContext) { return new DataViewsServerPlugin(initializerContext); } -export { - DataViewsServerPlugin as Plugin, +export type { DataViewsServerPluginSetup as PluginSetup, DataViewsServerPluginStart as PluginStart, }; +export { DataViewsServerPlugin as Plugin }; diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index ef8357a5b2a568..510dabf3b32d41 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -12,6 +12,7 @@ import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom' import { EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { ApplicationStart, ChromeStart, ScopedHistory } from 'src/core/public'; @@ -43,21 +44,22 @@ function DevToolsWrapper({ devTools, activeDevTool, updateRoute }: DevToolsWrapp return (
- + {devTools.map((currentDevTool) => ( - - { - if (!currentDevTool.isDisabled()) { - updateRoute(`/${currentDevTool.id}`); - } - }} - > - {currentDevTool.title} - - + { + if (!currentDevTool.isDisabled()) { + updateRoute(`/${currentDevTool.id}`); + } + }} + > + + {currentDevTool.title} + + ))}
{ fetchedState.anchor._id, ]); - const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useDataGridColumns({ + const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useColumns({ capabilities, config: uiSettings, indexPattern, diff --git a/src/plugins/discover/public/application/apps/context/context_app_content.tsx b/src/plugins/discover/public/application/apps/context/context_app_content.tsx index 2d4d3cab250f3b..c8f3cfe0a568f2 100644 --- a/src/plugins/discover/public/application/apps/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/apps/context/context_app_content.tsx @@ -28,7 +28,7 @@ export interface ContextAppContentProps { columns: string[]; onAddColumn: (columnsName: string) => void; onRemoveColumn: (columnsName: string) => void; - onSetColumns: (columnsNames: string[]) => void; + onSetColumns: (columnsNames: string[], hideTimeColumn: boolean) => void; services: DiscoverServices; indexPattern: IndexPattern; predecessorCount: number; @@ -141,7 +141,7 @@ export function ContextAppContent({ dataTestSubj="contextDocTable" /> )} - {!isLegacy && rows && rows.length && ( + {!isLegacy && (
{ @@ -184,46 +189,47 @@ export function DiscoverHistogram({ const xAxisFormatter = services.data.fieldFormats.deserialize(chartData.yAxisFormat); const useLegacyTimeAxis = uiSettings.get(LEGACY_TIME_AXIS, false); - const gridLineStyle: RecursivePartial = useLegacyTimeAxis - ? {} - : { strokeWidth: 0.1, stroke: isDarkMode ? 'white' : 'black' }; - const verticalAxisStyle: RecursivePartial = useLegacyTimeAxis - ? {} - : { - axisLine: { - visible: false, - }, - tickLabel: { - fontSize: 11, - }, - }; - const xAxisStyle: RecursivePartial = useLegacyTimeAxis - ? {} - : { - axisLine: { - stroke: isDarkMode ? 'lightgray' : 'darkgray', - strokeWidth: 1, - }, - tickLine: { - size: 12, - strokeWidth: 0.15, - stroke: isDarkMode ? 'white' : 'black', - padding: -10, - visible: true, - }, - tickLabel: { - fontSize: 11, - padding: 0, - alignment: { - vertical: Position.Bottom, - horizontal: Position.Left, - }, - offset: { - x: 1.5, - y: 0, - }, - }, - }; + + const toolTipTitle = i18n.translate('discover.timeIntervalWithValueWarning', { + defaultMessage: 'Warning', + }); + + const toolTipContent = i18n.translate('discover.bucketIntervalTooltip', { + defaultMessage: + 'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}.', + values: { + bucketsDescription: + bucketInterval!.scale && bucketInterval!.scale > 1 + ? i18n.translate('discover.bucketIntervalTooltip.tooLargeBucketsText', { + defaultMessage: 'buckets that are too large', + }) + : i18n.translate('discover.bucketIntervalTooltip.tooManyBucketsText', { + defaultMessage: 'too many buckets', + }), + bucketIntervalDescription: bucketInterval?.description, + }, + }); + + let timeRange = ( + + {timeRangeText} + + ); + if (bucketInterval?.scaled) { + timeRange = ( + + {timeRange} + + + + + ); + } return ( @@ -244,16 +250,13 @@ export function DiscoverHistogram({ ticks={2} integersOnly tickFormat={(value) => xAxisFormatter.convert(value)} - gridLine={gridLineStyle} - style={verticalAxisStyle} />
- - {timeRangeText} - + {timeRange} ); } diff --git a/src/plugins/discover/public/application/apps/main/components/chart/use_chart_panels.ts b/src/plugins/discover/public/application/apps/main/components/chart/use_chart_panels.ts index 72f921bca5f533..3660173ef761dc 100644 --- a/src/plugins/discover/public/application/apps/main/components/chart/use_chart_panels.ts +++ b/src/plugins/discover/public/application/apps/main/components/chart/use_chart_panels.ts @@ -12,8 +12,7 @@ import type { } from '@elastic/eui'; import { search } from '../../../../../../../data/public'; import { AppState } from '../../services/discover_state'; -import { DataCharts$, DataChartsMessage } from '../../services/use_saved_search'; -import { useDataState } from '../../utils/use_data_state'; +import { DataCharts$ } from '../../services/use_saved_search'; export function useChartPanels( state: AppState, @@ -22,8 +21,6 @@ export function useChartPanels( onChangeInterval: (value: string) => void, closePopover: () => void ) { - const dataState: DataChartsMessage = useDataState(savedSearchDataChart$); - const { bucketInterval } = dataState; const { interval, hideChart } = state; const selectedOptionIdx = search.aggs.intervalOptions.findIndex((opt) => opt.val === interval); const intervalDisplay = @@ -56,29 +53,6 @@ export function useChartPanels( timeInterval: intervalDisplay, }, }), - icon: bucketInterval?.scaled ? 'alert' : '', - toolTipTitle: bucketInterval?.scaled - ? i18n.translate('discover.timeIntervalWithValueWarning', { - defaultMessage: 'Warning', - }) - : '', - toolTipContent: bucketInterval?.scaled - ? i18n.translate('discover.bucketIntervalTooltip', { - defaultMessage: - 'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}.', - values: { - bucketsDescription: - bucketInterval!.scale && bucketInterval!.scale > 1 - ? i18n.translate('discover.bucketIntervalTooltip.tooLargeBucketsText', { - defaultMessage: 'buckets that are too large', - }) - : i18n.translate('discover.bucketIntervalTooltip.tooManyBucketsText', { - defaultMessage: 'too many buckets', - }), - bucketIntervalDescription: bucketInterval?.description, - }, - }) - : '', panel: 1, 'data-test-subj': 'discoverTimeIntervalPanel', }); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss index d19a1fd0420691..164b61d42df191 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss @@ -103,10 +103,6 @@ text-align: center; } -.truncate-by-height { - overflow: hidden; -} - .table { // Nesting .table { diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts index d0984ff6fa7977..93b38cfc6519c0 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts @@ -64,7 +64,7 @@ describe('Test column actions', () => { sort: [], }); setAppState.mockClear(); - actions.onSetColumns(['first', 'second', 'third']); + actions.onSetColumns(['first', 'second', 'third'], true); expect(setAppState).toHaveBeenCalledWith({ columns: ['first', 'second', 'third'], }); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts index f3ad590ac6ce45..2fc82e25634bd3 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts @@ -102,12 +102,13 @@ export function getStateColumnActions({ setAppState({ columns }); } - function onSetColumns(columns: string[]) { - // remove first element of columns if it's the configured timeFieldName, which is prepended automatically + function onSetColumns(columns: string[], hideTimeColumn: boolean) { + // The next line should gone when classic table will be removed const actualColumns = - indexPattern.timeFieldName && indexPattern.timeFieldName === columns[0] + !hideTimeColumn && indexPattern.timeFieldName && indexPattern.timeFieldName === columns[0] ? columns.slice(1) : columns; + setAppState({ columns: actualColumns }); } return { diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx index 0bf4a36555d169..515782ce23f450 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx @@ -88,7 +88,7 @@ export const TableRow = ({ return ( // formatFieldValue always returns sanitized HTML // eslint-disable-next-line react/no-danger -
+
); }; const inlineFilter = useCallback( diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.tsx index a73bc3f175be12..d5660a091f0aa0 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.tsx @@ -20,7 +20,7 @@ interface Props { } const TemplateComponent = ({ defPairs }: Props) => { return ( -
+
{defPairs.map((pair, idx) => (
{pair[0]}:
diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx index d6ede9aa7fe5fe..64d5e08f25d73e 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx @@ -23,7 +23,7 @@ import { SAMPLE_SIZE_SETTING, SEARCH_FIELDS_FROM_SOURCE, } from '../../../../../../common'; -import { useDataGridColumns } from '../../../../helpers/use_data_grid_columns'; +import { useColumns } from '../../../../helpers/use_data_grid_columns'; import { IndexPattern } from '../../../../../../../data/common'; import { SavedSearch } from '../../../../../saved_searches'; import { DataDocumentsMsg, DataDocuments$ } from '../../services/use_saved_search'; @@ -69,7 +69,7 @@ function DiscoverDocumentsComponent({ const rows = useMemo(() => documentState.result || [], [documentState.result]); - const { columns, onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useDataGridColumns({ + const { columns, onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useColumns({ capabilities, config: uiSettings, indexPattern, diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss index 37ff50e3331241..1d074c002e340c 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss @@ -82,8 +82,8 @@ discover-app { } .dscHistogram { - height: $euiSize * 8; - padding: $euiSizeS $euiSizeS $euiSizeS $euiSizeS; + height: $euiSize * 7; + padding: 0 $euiSizeS $euiSizeS * 2 $euiSizeS; } .dscHistogramTimeRange { diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx index a0799777a39473..76ed7069b294aa 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx @@ -35,7 +35,7 @@ import { getResultState } from '../../utils/get_result_state'; import { InspectorSession } from '../../../../../../../inspector/public'; import { DiscoverUninitialized } from '../uninitialized/uninitialized'; import { DataMainMsg } from '../../services/use_saved_search'; -import { useDataGridColumns } from '../../../../helpers/use_data_grid_columns'; +import { useColumns } from '../../../../helpers/use_data_grid_columns'; import { DiscoverDocuments } from './discover_documents'; import { FetchStatus } from '../../../../types'; import { useDataState } from '../../utils/use_data_state'; @@ -141,7 +141,7 @@ export function DiscoverLayout({ }; }, [inspectorSession]); - const { columns, onAddColumn, onRemoveColumn } = useDataGridColumns({ + const { columns, onAddColumn, onRemoveColumn } = useColumns({ capabilities, config: uiSettings, indexPattern, diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index ca403c813010b8..6f96f21c9b8c8a 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -36,7 +36,11 @@ import { import { defaultPageSize, gridStyle, pageSizeArr, toolbarVisibility } from './constants'; import { DiscoverServices } from '../../../build_services'; import { getDisplayedColumns } from '../../helpers/columns'; -import { MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS } from '../../../../common'; +import { + DOC_HIDE_TIME_COLUMN_SETTING, + MAX_DOC_FIELDS_DISPLAYED, + SHOW_MULTIFIELDS, +} from '../../../../common'; import { DiscoverGridDocumentToolbarBtn, getDocId } from './discover_grid_document_selection'; import { SortPairArr } from '../../apps/main/components/doc_table/lib/get_sort'; import { getFieldsToShow } from '../../helpers/get_fields_to_show'; @@ -91,7 +95,7 @@ export interface DiscoverGridProps { /** * Function to set all columns */ - onSetColumns: (columns: string[]) => void; + onSetColumns: (columns: string[], hideTimeColumn: boolean) => void; /** * function to change sorting of the documents, skipped when isSortEnabled is set to false */ @@ -302,15 +306,19 @@ export const DiscoverGrid = ({ [displayedColumns, indexPattern, showTimeCol, settings, defaultColumns, isSortEnabled] ); + const hideTimeColumn = useMemo( + () => services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), + [services.uiSettings] + ); const schemaDetectors = useMemo(() => getSchemaDetectors(), []); const columnsVisibility = useMemo( () => ({ visibleColumns: getVisibleColumns(displayedColumns, indexPattern, showTimeCol) as string[], setVisibleColumns: (newColumns: string[]) => { - onSetColumns(newColumns); + onSetColumns(newColumns, hideTimeColumn); }, }), - [displayedColumns, indexPattern, showTimeCol, onSetColumns] + [displayedColumns, indexPattern, showTimeCol, hideTimeColumn, onSetColumns] ); const sorting = useMemo(() => { if (isSortEnabled) { diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index e61333cce11660..176a1961378aa6 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -236,7 +236,7 @@ describe('DocViewTable at Discover Context', () => { const btn = findTestSubject(component, `collapseBtn`); const html = component.html(); - expect(component.html()).toContain('truncate-by-height'); + expect(component.html()).toContain('dscTruncateByHeight'); expect(btn.length).toBe(1); btn.simulate('click'); diff --git a/src/plugins/discover/public/application/components/table/table_cell_value.tsx b/src/plugins/discover/public/application/components/table/table_cell_value.tsx index e006de1cd7aeb1..ebb4ea243fb254 100644 --- a/src/plugins/discover/public/application/components/table/table_cell_value.tsx +++ b/src/plugins/discover/public/application/components/table/table_cell_value.tsx @@ -104,7 +104,7 @@ export const TableFieldValue = ({ const valueClassName = classNames({ // eslint-disable-next-line @typescript-eslint/naming-convention kbnDocViewer__value: true, - 'truncate-by-height': isCollapsible && isCollapsed, + dscTruncateByHeight: isCollapsible && isCollapsed, }); const onToggleCollapse = () => setFieldOpen((fieldOpenPrev) => !fieldOpenPrev); diff --git a/src/plugins/discover/public/application/components/table/table_columns.tsx b/src/plugins/discover/public/application/components/table/table_columns.tsx index 5944f9bede6466..9f502b44919773 100644 --- a/src/plugins/discover/public/application/components/table/table_columns.tsx +++ b/src/plugins/discover/public/application/components/table/table_columns.tsx @@ -52,6 +52,7 @@ export const MAIN_COLUMNS: Array> = [ field: 'field', className: 'kbnDocViewer__tableFieldNameCell', mobileOptions: { header: false }, + width: '30%', name: ( diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index 320ce3e5f644a0..b3fe36358bbd49 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -22,6 +22,7 @@ export const discoverRouter = (services: DiscoverServices, history: History) => services, history, }; + return ( diff --git a/src/plugins/discover/public/application/helpers/truncate_styles.ts b/src/plugins/discover/public/application/helpers/truncate_styles.ts new file mode 100644 index 00000000000000..dbe8b770e1793f --- /dev/null +++ b/src/plugins/discover/public/application/helpers/truncate_styles.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 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 createCache from '@emotion/cache'; +import { cache } from '@emotion/css'; +import { serializeStyles } from '@emotion/serialize'; + +/** + * The following emotion cache management was introduced here + * https://ntsim.uk/posts/how-to-update-or-remove-global-styles-in-emotion/ + */ +const TRUNCATE_GRADIENT_HEIGHT = 15; +const globalThemeCache = createCache({ key: 'truncation' }); + +const buildStylesheet = (maxHeight: number) => { + return [ + ` + .dscTruncateByHeight { + overflow: hidden; + max-height: ${maxHeight}px !important; + display: inline-block; + } + .dscTruncateByHeight:before { + top: ${maxHeight - TRUNCATE_GRADIENT_HEIGHT}px; + } + `, + ]; +}; + +const flushThemedGlobals = () => { + globalThemeCache.sheet.flush(); + globalThemeCache.inserted = {}; + globalThemeCache.registered = {}; +}; + +export const injectTruncateStyles = (maxHeight: number) => { + if (maxHeight <= 0) { + flushThemedGlobals(); + return; + } + + const serialized = serializeStyles(buildStylesheet(maxHeight), cache.registered); + if (!globalThemeCache.inserted[serialized.name]) { + globalThemeCache.insert('', serialized, globalThemeCache.sheet, true); + } +}; diff --git a/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx b/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx index ccee271b73e324..abb2138e882b15 100644 --- a/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx +++ b/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx @@ -7,14 +7,14 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { useDataGridColumns } from './use_data_grid_columns'; +import { useColumns } from './use_data_grid_columns'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { configMock } from '../../__mocks__/config'; import { indexPatternsMock } from '../../__mocks__/index_patterns'; import { AppState } from '../apps/context/services/context_state'; import { Capabilities } from '../../../../../core/types'; -describe('useDataGridColumns', () => { +describe('useColumns', () => { const defaultProps = { capabilities: { discover: { save: true } } as unknown as Capabilities, config: configMock, @@ -29,7 +29,7 @@ describe('useDataGridColumns', () => { test('should return valid result', () => { const { result } = renderHook(() => { - return useDataGridColumns(defaultProps); + return useColumns(defaultProps); }); expect(result.current.columns).toEqual(['Time', 'message']); @@ -41,7 +41,7 @@ describe('useDataGridColumns', () => { test('should skip _source column when useNewFieldsApi is set to true', () => { const { result } = renderHook(() => { - return useDataGridColumns({ + return useColumns({ ...defaultProps, state: { columns: ['Time', '_source'], @@ -55,7 +55,7 @@ describe('useDataGridColumns', () => { test('should return empty columns array', () => { const { result } = renderHook(() => { - return useDataGridColumns({ + return useColumns({ ...defaultProps, state: { columns: [], diff --git a/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts b/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts index 4dbe14017dc6e7..888d67e2aaff31 100644 --- a/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts +++ b/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts @@ -20,7 +20,7 @@ import { } from '../apps/context/services/context_state'; import { getStateColumnActions } from '../apps/main/components/doc_table/actions/columns'; -interface UseDataGridColumnsProps { +interface UseColumnsProps { capabilities: Capabilities; config: IUiSettingsClient; indexPattern: IndexPattern; @@ -30,7 +30,7 @@ interface UseDataGridColumnsProps { state: DiscoverState | ContextState; } -export const useDataGridColumns = ({ +export const useColumns = ({ capabilities, config, indexPattern, @@ -38,7 +38,7 @@ export const useDataGridColumns = ({ setAppState, state, useNewFieldsApi, -}: UseDataGridColumnsProps) => { +}: UseColumnsProps) => { const { onAddColumn, onRemoveColumn, onSetColumns, onMoveColumn } = useMemo( () => getStateColumnActions({ diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 21f1c31e774ee5..cb7b29afe3f9ac 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -9,22 +9,24 @@ import { PluginInitializerContext } from 'kibana/public'; import { DiscoverPlugin } from './plugin'; +export type { SavedSearch } from './saved_searches'; export { getSavedSearch, getSavedSearchFullPathUrl, getSavedSearchUrl, getSavedSearchUrlConflictMessage, throwErrorOnSavedSearchUrlConflict, - SavedSearch, } from './saved_searches'; -export { DiscoverSetup, DiscoverStart } from './plugin'; +export type { DiscoverSetup, DiscoverStart } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new DiscoverPlugin(initializerContext); } -export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable'; +export type { ISearchEmbeddable, SearchInput } from './application/embeddable'; +export { SEARCH_EMBEDDABLE_TYPE } from './application/embeddable'; export { loadSharingDataHelpers } from './shared'; -export { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from './url_generator'; -export { DiscoverAppLocator, DiscoverAppLocatorParams } from './locator'; +export type { DiscoverUrlGeneratorState } from './url_generator'; +export { DISCOVER_APP_URL_GENERATOR } from './url_generator'; +export type { DiscoverAppLocator, DiscoverAppLocatorParams } from './locator'; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index c91bcf3897e145..62a5a7972a2782 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -62,6 +62,8 @@ import { DeferredSpinner } from './shared'; import { ViewSavedSearchAction } from './application/embeddable/view_saved_search_action'; import type { SpacesPluginStart } from '../../../../x-pack/plugins/spaces/public'; import { FieldFormatsStart } from '../../field_formats/public'; +import { injectTruncateStyles } from './application/helpers/truncate_styles'; +import { TRUNCATE_MAX_HEIGHT } from '../common'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -413,6 +415,8 @@ export class DiscoverPlugin const services = buildServices(core, plugins, this.initializerContext); setServices(services); + injectTruncateStyles(services.uiSettings.get(TRUNCATE_MAX_HEIGHT)); + return { urlGenerator: this.urlGenerator, locator: this.locator, diff --git a/src/plugins/discover/public/saved_searches/index.ts b/src/plugins/discover/public/saved_searches/index.ts index da9dd047fb5ac3..c41c92df300846 100644 --- a/src/plugins/discover/public/saved_searches/index.ts +++ b/src/plugins/discover/public/saved_searches/index.ts @@ -15,6 +15,7 @@ export { } from './saved_searches_utils'; export { useSavedSearchAliasMatchRedirect } from './saved_search_alias_match_redirect'; export { SavedSearchURLConflictCallout } from './saved_search_url_conflict_callout'; -export { saveSavedSearch, SaveSavedSearchOptions } from './save_saved_searches'; +export type { SaveSavedSearchOptions } from './save_saved_searches'; +export { saveSavedSearch } from './save_saved_searches'; export { SAVED_SEARCH_TYPE } from './constants'; export type { SavedSearch, SortOrder } from './types'; diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index 529ba0d1beef12..df06260d45d212 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -26,6 +26,7 @@ import { SEARCH_FIELDS_FROM_SOURCE, MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS, + TRUNCATE_MAX_HEIGHT, SHOW_FIELD_STATISTICS, } from '../common'; @@ -241,4 +242,16 @@ export const getUiSettings: () => Record = () => ({ category: ['discover'], schema: schema.boolean(), }, + [TRUNCATE_MAX_HEIGHT]: { + name: i18n.translate('discover.advancedSettings.params.maxCellHeightTitle', { + defaultMessage: 'Maximum table cell height', + }), + value: 115, + category: ['discover'], + description: i18n.translate('discover.advancedSettings.params.maxCellHeightText', { + defaultMessage: + 'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation', + }), + schema: schema.number({ min: 0 }), + }, }); diff --git a/src/plugins/embeddable/README.asciidoc b/src/plugins/embeddable/README.asciidoc index 5dd74393e0dc16..a5b54fabc7af16 100644 --- a/src/plugins/embeddable/README.asciidoc +++ b/src/plugins/embeddable/README.asciidoc @@ -9,7 +9,7 @@ Containers are a special type of embeddable that can contain nested embeddables. === Examples -Multiple embeddable examples are implemented and registered https://github.com/elastic/kibana/tree/master/examples/embeddable_examples[here]. They can be played around with and explored https://github.com/elastic/kibana/tree/master/examples/embeddable_explorer[in the Embeddable Explorer example plugin]. Just run kibana with +Multiple embeddable examples are implemented and registered https://github.com/elastic/kibana/tree/main/examples/embeddable_examples[here]. They can be played around with and explored https://github.com/elastic/kibana/tree/main/examples/embeddable_explorer[in the Embeddable Explorer example plugin]. Just run kibana with [source,sh] -- @@ -18,20 +18,20 @@ yarn start --run-examples and navigate to the Embeddable explorer app. -There is also an example of rendering dashboard container outside of dashboard app https://github.com/elastic/kibana/tree/master/examples/dashboard_embeddable_examples[here]. +There is also an example of rendering dashboard container outside of dashboard app https://github.com/elastic/kibana/tree/main/examples/dashboard_embeddable_examples[here]. === Docs -link:https://github.com/elastic/kibana/blob/master/src/plugins/embeddable/docs/README.md[Embeddable docs, guides & caveats] +link:https://github.com/elastic/kibana/blob/main/src/plugins/embeddable/docs/README.md[Embeddable docs, guides & caveats] === API docs ==== Browser API -https://github.com/elastic/kibana/blob/master/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetup.md[Browser Setup contract] -https://github.com/elastic/kibana/blob/master/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md[Browser Start contract] +https://github.com/elastic/kibana/blob/main/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetup.md[Browser Setup contract] +https://github.com/elastic/kibana/blob/main/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md[Browser Start contract] ==== Server API -https://github.com/elastic/kibana/blob/master/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.md[Server Setup contract] +https://github.com/elastic/kibana/blob/main/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.md[Server Setup contract] === Testing diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 80171e1ad2faba..6a6b5b2df2ddde 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -11,76 +11,78 @@ import './index.scss'; import { PluginInitializerContext } from 'src/core/public'; import { EmbeddablePublicPlugin } from './plugin'; -export { - ACTION_ADD_PANEL, - ACTION_EDIT_PANEL, +export type { Adapters, - AddPanelAction, ReferenceOrValueEmbeddable, - isReferenceOrValueEmbeddable, ChartActionContext, - Container, ContainerInput, ContainerOutput, - CONTEXT_MENU_TRIGGER, - contextMenuTrigger, - defaultEmbeddableFactoryProvider, - EditPanelAction, - Embeddable, - EmbeddableChildPanel, EmbeddableChildPanelProps, EmbeddableContext, EmbeddableFactory, EmbeddableFactoryDefinition, - EmbeddableFactoryNotFoundError, EmbeddableInput, EmbeddableInstanceConfiguration, EmbeddableOutput, - EmbeddablePanel, - EmbeddableRoot, ValueClickContext, RangeSelectContext, - ErrorEmbeddable, IContainer, IEmbeddable, + OutputSpec, + PanelState, + PropertySpec, + SavedObjectEmbeddableInput, + EmbeddableEditorState, + EmbeddablePackageState, + EmbeddableRendererProps, +} from './lib'; +export { + ACTION_ADD_PANEL, + ACTION_EDIT_PANEL, + AddPanelAction, + isReferenceOrValueEmbeddable, + Container, + CONTEXT_MENU_TRIGGER, + contextMenuTrigger, + defaultEmbeddableFactoryProvider, + EditPanelAction, + Embeddable, + EmbeddableChildPanel, + EmbeddableFactoryNotFoundError, + EmbeddablePanel, + EmbeddableRoot, + ErrorEmbeddable, isEmbeddable, isErrorEmbeddable, openAddPanelFlyout, - OutputSpec, PANEL_BADGE_TRIGGER, panelBadgeTrigger, PANEL_NOTIFICATION_TRIGGER, panelNotificationTrigger, PanelNotFoundError, - PanelState, - PropertySpec, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, ViewMode, withEmbeddableSubscription, - SavedObjectEmbeddableInput, isSavedObjectEmbeddableInput, isRangeSelectTriggerContext, isValueClickTriggerContext, isRowClickTriggerContext, isContextMenuTriggerContext, EmbeddableStateTransfer, - EmbeddableEditorState, - EmbeddablePackageState, EmbeddableRenderer, - EmbeddableRendererProps, useEmbeddableFactory, } from './lib'; export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './lib/attribute_service'; -export { EnhancementRegistryDefinition } from './types'; +export type { EnhancementRegistryDefinition } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new EmbeddablePublicPlugin(initializerContext); } -export { +export type { EmbeddableSetup, EmbeddableStart, EmbeddableSetupDependencies, diff --git a/src/plugins/embeddable/public/lib/containers/i_container.ts b/src/plugins/embeddable/public/lib/containers/i_container.ts index f68a3ada33c07d..c4593cac4969a1 100644 --- a/src/plugins/embeddable/public/lib/containers/i_container.ts +++ b/src/plugins/embeddable/public/lib/containers/i_container.ts @@ -15,7 +15,7 @@ import { } from '../embeddables'; import { PanelState } from '../../../common/types'; -export { PanelState }; +export type { PanelState }; export interface ContainerOutput extends EmbeddableOutput { embeddableLoaded: { [key: string]: boolean }; diff --git a/src/plugins/embeddable/public/lib/containers/index.ts b/src/plugins/embeddable/public/lib/containers/index.ts index cc2c0bfa58223b..041923188e1759 100644 --- a/src/plugins/embeddable/public/lib/containers/index.ts +++ b/src/plugins/embeddable/public/lib/containers/index.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export { IContainer, PanelState, ContainerInput, ContainerOutput } from './i_container'; +export type { IContainer, PanelState, ContainerInput, ContainerOutput } from './i_container'; export { Container } from './container'; export * from './embeddable_child_panel'; diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index bbac617d415903..b53f036024259d 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -16,7 +16,7 @@ export interface EmbeddableError { message: string; } -export { EmbeddableInput }; +export type { EmbeddableInput }; export interface EmbeddableOutput { // Whether the embeddable is actively loading. diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts index eede745f317941..1745c64c73bf57 100644 --- a/src/plugins/embeddable/public/lib/embeddables/index.ts +++ b/src/plugins/embeddable/public/lib/embeddables/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { EmbeddableOutput, EmbeddableInput, IEmbeddable } from './i_embeddable'; +export type { EmbeddableOutput, EmbeddableInput, IEmbeddable } from './i_embeddable'; export { isEmbeddable } from './is_embeddable'; export { Embeddable } from './embeddable'; export * from './embeddable_factory'; @@ -16,8 +16,5 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable'; export { withEmbeddableSubscription } from './with_subscription'; export { EmbeddableRoot } from './embeddable_root'; export * from '../../../common/lib/saved_object_embeddable'; -export { - EmbeddableRenderer, - EmbeddableRendererProps, - useEmbeddableFactory, -} from './embeddable_renderer'; +export type { EmbeddableRendererProps } from './embeddable_renderer'; +export { EmbeddableRenderer, useEmbeddableFactory } from './embeddable_renderer'; diff --git a/src/plugins/embeddable/public/lib/reference_or_value_embeddable/index.ts b/src/plugins/embeddable/public/lib/reference_or_value_embeddable/index.ts index a448eb1319ab48..41554e8efce1dc 100644 --- a/src/plugins/embeddable/public/lib/reference_or_value_embeddable/index.ts +++ b/src/plugins/embeddable/public/lib/reference_or_value_embeddable/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { ReferenceOrValueEmbeddable, isReferenceOrValueEmbeddable } from './types'; +export type { ReferenceOrValueEmbeddable } from './types'; +export { isReferenceOrValueEmbeddable } from './types'; diff --git a/src/plugins/embeddable/public/lib/state_transfer/index.ts b/src/plugins/embeddable/public/lib/state_transfer/index.ts index 6643f048d6425d..009b2994f9caa7 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/index.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/index.ts @@ -7,4 +7,4 @@ */ export { EmbeddableStateTransfer } from './embeddable_state_transfer'; -export { EmbeddableEditorState, EmbeddablePackageState } from './types'; +export type { EmbeddableEditorState, EmbeddablePackageState } from './types'; diff --git a/src/plugins/embeddable/public/lib/types.ts b/src/plugins/embeddable/public/lib/types.ts index 2def03c742e7b7..d22bcbd12bef90 100644 --- a/src/plugins/embeddable/public/lib/types.ts +++ b/src/plugins/embeddable/public/lib/types.ts @@ -22,4 +22,4 @@ export interface PropertySpec { value?: string; } export { ViewMode } from '../../common/types'; -export { Adapters }; +export type { Adapters }; diff --git a/src/plugins/embeddable/server/index.ts b/src/plugins/embeddable/server/index.ts index aac081f9467b60..8d88f35a4be224 100644 --- a/src/plugins/embeddable/server/index.ts +++ b/src/plugins/embeddable/server/index.ts @@ -8,8 +8,8 @@ import { EmbeddableServerPlugin, EmbeddableSetup, EmbeddableStart } from './plugin'; -export { EmbeddableSetup, EmbeddableStart }; +export type { EmbeddableSetup, EmbeddableStart }; -export { EnhancementRegistryDefinition, EmbeddableRegistryDefinition } from './types'; +export type { EnhancementRegistryDefinition, EmbeddableRegistryDefinition } from './types'; export const plugin = () => new EmbeddableServerPlugin(); diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts index 75d79a204f141b..a43e7c1b31e45a 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ +export type { Authorization } from './authorization_provider'; export { AuthorizationProvider, AuthorizationContext, useAuthorizationContext, - Authorization, } from './authorization_provider'; export { WithPrivileges } from './with_privileges'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts index 9ccbc5a5cd3dfa..57d5cd88d3e4ad 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +export type { Authorization } from './components'; export { WithPrivileges, NotAuthorizedSection, @@ -14,7 +15,6 @@ export { SectionError, PageError, useAuthorizationContext, - Authorization, } from './components'; -export { Privileges, MissingPrivileges, Error } from './types'; +export type { Privileges, MissingPrivileges, Error } from './types'; diff --git a/src/plugins/es_ui_shared/common/index.ts b/src/plugins/es_ui_shared/common/index.ts index 1c2955b8e5e28a..4f0185ba110d92 100644 --- a/src/plugins/es_ui_shared/common/index.ts +++ b/src/plugins/es_ui_shared/common/index.ts @@ -6,4 +6,7 @@ * Side Public License, v 1. */ -export { Privileges, MissingPrivileges } from '../__packages_do_not_import__/authorization/types'; +export type { + Privileges, + MissingPrivileges, +} from '../__packages_do_not_import__/authorization/types'; diff --git a/src/plugins/es_ui_shared/public/authorization/index.ts b/src/plugins/es_ui_shared/public/authorization/index.ts index b8fb2f45794ee8..8175d6b80d2986 100644 --- a/src/plugins/es_ui_shared/public/authorization/index.ts +++ b/src/plugins/es_ui_shared/public/authorization/index.ts @@ -6,16 +6,18 @@ * Side Public License, v 1. */ +export type { + Error, + MissingPrivileges, + Privileges, + Authorization, +} from '../../__packages_do_not_import__/authorization'; export { AuthorizationContext, AuthorizationProvider, - Error, - MissingPrivileges, NotAuthorizedSection, - Privileges, SectionError, PageError, useAuthorizationContext, WithPrivileges, - Authorization, } from '../../__packages_do_not_import__/authorization'; diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/index.ts b/src/plugins/es_ui_shared/public/components/cron_editor/index.ts index 88176c429fcf3b..bee0bc412fb559 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/index.ts +++ b/src/plugins/es_ui_shared/public/components/cron_editor/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { Frequency } from './types'; +export type { Frequency } from './types'; export { CronEditor } from './cron_editor'; diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/services/index.ts b/src/plugins/es_ui_shared/public/components/cron_editor/services/index.ts index 85d650994b33c0..8988c640cd09ee 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/services/index.ts +++ b/src/plugins/es_ui_shared/public/components/cron_editor/services/index.ts @@ -7,10 +7,5 @@ */ export { cronExpressionToParts, cronPartsToExpression } from './cron'; -export { - getOrdinalValue, - getDayName, - getMonthName, - DayOrdinal, - MonthOrdinal, -} from './humanized_numbers'; +export type { DayOrdinal, MonthOrdinal } from './humanized_numbers'; +export { getOrdinalValue, getDayName, getMonthName } from './humanized_numbers'; diff --git a/src/plugins/es_ui_shared/public/components/json_editor/index.ts b/src/plugins/es_ui_shared/public/components/json_editor/index.ts index bf7697523d3fe0..f31f586f1033e7 100644 --- a/src/plugins/es_ui_shared/public/components/json_editor/index.ts +++ b/src/plugins/es_ui_shared/public/components/json_editor/index.ts @@ -8,4 +8,4 @@ export * from './json_editor'; -export { OnJsonEditorUpdateHandler, JsonEditorState } from './use_json'; +export type { OnJsonEditorUpdateHandler, JsonEditorState } from './use_json'; diff --git a/src/plugins/es_ui_shared/public/forms/form_wizard/index.ts b/src/plugins/es_ui_shared/public/forms/form_wizard/index.ts index 6c4912f7e4b333..0f868712553c72 100644 --- a/src/plugins/es_ui_shared/public/forms/form_wizard/index.ts +++ b/src/plugins/es_ui_shared/public/forms/form_wizard/index.ts @@ -10,12 +10,12 @@ export { FormWizard } from './form_wizard'; export { FormWizardStep } from './form_wizard_step'; +export type { Step, Steps } from './form_wizard_context'; export { FormWizardProvider, FormWizardConsumer, useFormWizardContext, - Step, - Steps, } from './form_wizard_context'; -export { FormWizardNav, NavTexts } from './form_wizard_nav'; +export type { NavTexts } from './form_wizard_nav'; +export { FormWizardNav } from './form_wizard_nav'; diff --git a/src/plugins/es_ui_shared/public/forms/multi_content/index.ts b/src/plugins/es_ui_shared/public/forms/multi_content/index.ts index 76ff8a9ff30662..bae46ad0abfd82 100644 --- a/src/plugins/es_ui_shared/public/forms/multi_content/index.ts +++ b/src/plugins/es_ui_shared/public/forms/multi_content/index.ts @@ -13,6 +13,7 @@ export { useContent, } from './multi_content_context'; -export { useMultiContent, HookProps, Content, MultiContent } from './use_multi_content'; +export type { HookProps, Content, MultiContent } from './use_multi_content'; +export { useMultiContent } from './use_multi_content'; export { WithMultiContent } from './with_multi_content'; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 2dc50536ca6311..c21587c9a60408 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -15,37 +15,36 @@ import * as ace from './ace'; import * as GlobalFlyout from './global_flyout'; import * as XJson from './xjson'; -export { JsonEditor, OnJsonEditorUpdateHandler, JsonEditorState } from './components/json_editor'; +export type { OnJsonEditorUpdateHandler, JsonEditorState } from './components/json_editor'; +export { JsonEditor } from './components/json_editor'; export { PageLoading } from './components/page_loading'; export { SectionLoading } from './components/section_loading'; -export { EuiCodeEditor, EuiCodeEditorProps } from './components/code_editor'; -export { Frequency, CronEditor } from './components/cron_editor'; +export type { EuiCodeEditorProps } from './components/code_editor'; +export { EuiCodeEditor } from './components/code_editor'; +export type { Frequency } from './components/cron_editor'; +export { CronEditor } from './components/cron_editor'; -export { +export type { SendRequestConfig, SendRequestResponse, UseRequestConfig, UseRequestResponse, - sendRequest, - useRequest, } from './request'; +export { sendRequest, useRequest } from './request'; export { indices } from './indices'; +export type { Privileges, MissingPrivileges, Error, Authorization } from './authorization'; export { AuthorizationContext, AuthorizationProvider, NotAuthorizedSection, WithPrivileges, - Privileges, - MissingPrivileges, SectionError, PageError, - Error, useAuthorizationContext, - Authorization, } from './authorization'; export { Forms, ace, GlobalFlyout, XJson }; diff --git a/src/plugins/es_ui_shared/public/request/index.ts b/src/plugins/es_ui_shared/public/request/index.ts index da407d00130379..5cfcc77f8e117a 100644 --- a/src/plugins/es_ui_shared/public/request/index.ts +++ b/src/plugins/es_ui_shared/public/request/index.ts @@ -6,5 +6,7 @@ * Side Public License, v 1. */ -export { SendRequestConfig, SendRequestResponse, sendRequest } from './send_request'; -export { UseRequestConfig, UseRequestResponse, useRequest } from './use_request'; +export type { SendRequestConfig, SendRequestResponse } from './send_request'; +export { sendRequest } from './send_request'; +export type { UseRequestConfig, UseRequestResponse } from './use_request'; +export { useRequest } from './use_request'; diff --git a/src/plugins/es_ui_shared/public/request/send_request.ts b/src/plugins/es_ui_shared/public/request/send_request.ts index 11ab99cfb69786..bdc6386ad15c6d 100644 --- a/src/plugins/es_ui_shared/public/request/send_request.ts +++ b/src/plugins/es_ui_shared/public/request/send_request.ts @@ -31,7 +31,7 @@ export const sendRequest = async ( ): Promise> => { try { const stringifiedBody = typeof body === 'string' ? body : JSON.stringify(body); - const response = await httpClient[method](path, { + const response = await httpClient[method]<{ data?: D } & D>(path, { body: stringifiedBody, query, asSystemRequest, diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts index 8438e5de871bd9..6f2dc768508ec1 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -export { useField, InternalFieldConfig } from './use_field'; +export type { InternalFieldConfig } from './use_field'; +export { useField } from './use_field'; export { useForm } from './use_form'; export { useFormData } from './use_form_data'; export { useFormIsModified } from './use_form_is_modified'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts index 79d1067cd36b2a..0bbaedcf2e90e1 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ -export { Subject, Subscription } from './subject'; +export type { Subscription } from './subject'; +export { Subject } from './subject'; export * from './utils'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts index c57333d788ef5f..cd9bb97cab8d91 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts @@ -7,7 +7,9 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -export { registerTestBed, TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +// eslint-disable-next-line import/no-extraneous-dependencies +export { registerTestBed } from '@kbn/test/jest'; // eslint-disable-next-line import/no-extraneous-dependencies export { getRandomString } from '@kbn/test/jest'; diff --git a/src/plugins/expression_error/README.md b/src/plugins/expression_error/README.md index 5e22d8fc652c79..1f53f9a560164b 100755 --- a/src/plugins/expression_error/README.md +++ b/src/plugins/expression_error/README.md @@ -6,4 +6,4 @@ Expression Error plugin adds an `error` renderer to the expression plugin. The r ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_image/README.md b/src/plugins/expression_image/README.md index b02c9fd39b3d21..9171510bb6ea28 100755 --- a/src/plugins/expression_image/README.md +++ b/src/plugins/expression_image/README.md @@ -6,4 +6,4 @@ Expression Image plugin adds an `image` renderer to the expression plugin. The r ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_metric/README.md b/src/plugins/expression_metric/README.md index ae02ebd51256ed..e186c86b6c8b80 100755 --- a/src/plugins/expression_metric/README.md +++ b/src/plugins/expression_metric/README.md @@ -6,4 +6,4 @@ Expression Metric plugin adds a `metric` renderer and function to the expression ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_repeat_image/README.md b/src/plugins/expression_repeat_image/README.md index 11f4f9847c39aa..bfcb787db8fe28 100755 --- a/src/plugins/expression_repeat_image/README.md +++ b/src/plugins/expression_repeat_image/README.md @@ -6,4 +6,4 @@ Expression Repeat Image plugin adds a `repeatImage` function to the expression p ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_reveal_image/README.md b/src/plugins/expression_reveal_image/README.md index 21c27a6eee05b5..78212a9bc20269 100755 --- a/src/plugins/expression_reveal_image/README.md +++ b/src/plugins/expression_reveal_image/README.md @@ -6,4 +6,4 @@ Expression Reveal Image plugin adds a `revealImage` function to the expression p ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx index d20bbdc1bf1916..1f09ff06b4b2ae 100644 --- a/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx +++ b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx @@ -118,11 +118,11 @@ function RevealImageComponent({ return imgStyles; } - const additionaAlignerStyles: AlignerStyles = {}; + const additionalAlignerStyles: AlignerStyles = {}; if (isValidUrl(emptyImage ?? '')) { // only use empty image if one is provided - additionaAlignerStyles.backgroundImage = `url(${emptyImage})`; + additionalAlignerStyles.backgroundImage = `url(${emptyImage})`; } let additionalImgStyles: ImageStyles = {}; @@ -136,10 +136,10 @@ function RevealImageComponent({ return (
; -export { FieldFormatsRegistry, IFieldFormatsRegistry }; +export type { IFieldFormatsRegistry }; +export { FieldFormatsRegistry }; export { FieldFormat } from './field_format'; export { baseFormatters } from './constants/base_formatters'; export { @@ -24,7 +25,6 @@ export { NumberFormat, PercentFormat, RelativeDateFormat, - SourceFormat, StaticLookupFormat, UrlFormat, StringFormat, @@ -39,7 +39,7 @@ export { FORMATS_UI_SETTINGS } from './constants/ui_settings'; export { FIELD_FORMAT_IDS } from './types'; export { HTML_CONTEXT_TYPE, TEXT_CONTEXT_TYPE } from './content_types'; -export { +export type { FieldFormatsGetConfigFn, FieldFormatsContentType, FieldFormatConfig, diff --git a/src/plugins/field_formats/public/index.ts b/src/plugins/field_formats/public/index.ts index f765513fb4c4c4..f4f7019fe772d9 100755 --- a/src/plugins/field_formats/public/index.ts +++ b/src/plugins/field_formats/public/index.ts @@ -12,4 +12,4 @@ export { DateFormat, DateNanosFormat } from './lib/converters'; export function plugin() { return new FieldFormatsPlugin(); } -export { FieldFormatsSetup, FieldFormatsStart } from './plugin'; +export type { FieldFormatsSetup, FieldFormatsStart } from './plugin'; diff --git a/src/plugins/field_formats/server/index.ts b/src/plugins/field_formats/server/index.ts index 44de8fde558ec4..d3cac3bfb00c23 100755 --- a/src/plugins/field_formats/server/index.ts +++ b/src/plugins/field_formats/server/index.ts @@ -14,4 +14,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new FieldFormatsPlugin(initializerContext); } -export { FieldFormatsSetup, FieldFormatsStart } from './types'; +export type { FieldFormatsSetup, FieldFormatsStart } from './types'; diff --git a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap index 348f618805858f..17f7d2520e8621 100644 --- a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap +++ b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap @@ -358,3 +358,88 @@ exports[`should render a Welcome screen with the telemetry disclaimer when optIn
`; + +exports[`should render a Welcome screen without the opt in/out link when user cannot change optIn status 1`] = ` + +
+
+
+ + + + + +

+ +

+
+ +
+
+
+ + + + + + + + + + + + + +
+
+
+`; diff --git a/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap b/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap index de6beab31247a3..fcc4b3e8f1d0f9 100644 --- a/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap +++ b/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap @@ -83,6 +83,21 @@ exports[`AddData render 1`] = ` /> + + + + + diff --git a/src/plugins/home/public/application/components/add_data/add_data.tsx b/src/plugins/home/public/application/components/add_data/add_data.tsx index 50d6079dd8df3e..ee38b2e6e5d20e 100644 --- a/src/plugins/home/public/application/components/add_data/add_data.tsx +++ b/src/plugins/home/public/application/components/add_data/add_data.tsx @@ -96,6 +96,19 @@ export const AddData: FC = ({ addBasePath, application, isDarkMode }) => /> + + + + + + diff --git a/src/plugins/home/public/application/components/sample_data_set_cards.js b/src/plugins/home/public/application/components/sample_data_set_cards.js index 7f5882f108fc88..3c5eeb4d840261 100644 --- a/src/plugins/home/public/application/components/sample_data_set_cards.js +++ b/src/plugins/home/public/application/components/sample_data_set_cards.js @@ -67,7 +67,6 @@ export class SampleDataSetCards extends React.Component { sampleDataSets: sampleDataSets.sort((a, b) => { return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); }), - processingStatus: {}, }); }; @@ -82,6 +81,7 @@ export class SampleDataSetCards extends React.Component { try { await installSampleDataSet(id, targetSampleDataSet.defaultIndex); + await this.loadSampleDataSets(); // reload the list of sample data sets } catch (fetchError) { if (this._isMounted) { this.setState((prevState) => ({ diff --git a/src/plugins/home/public/application/components/welcome.test.tsx b/src/plugins/home/public/application/components/welcome.test.tsx index 440af1e0093b52..b042a91e58c9d2 100644 --- a/src/plugins/home/public/application/components/welcome.test.tsx +++ b/src/plugins/home/public/application/components/welcome.test.tsx @@ -47,6 +47,14 @@ test('should render a Welcome screen with no telemetry disclaimer', () => { expect(component).toMatchSnapshot(); }); +test('should render a Welcome screen without the opt in/out link when user cannot change optIn status', () => { + const telemetry = telemetryPluginMock.createStartContract(); + telemetry.telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(false); + const component = shallow( {}} telemetry={telemetry} />); + + expect(component).toMatchSnapshot(); +}); + test('fires opt-in seen when mounted', () => { const telemetry = telemetryPluginMock.createStartContract(); const mockSetOptedInNoticeSeen = jest.fn(); diff --git a/src/plugins/home/public/application/components/welcome.tsx b/src/plugins/home/public/application/components/welcome.tsx index 03dff22c7b33f1..21e0e03bb2bf74 100644 --- a/src/plugins/home/public/application/components/welcome.tsx +++ b/src/plugins/home/public/application/components/welcome.tsx @@ -76,7 +76,11 @@ export class Welcome extends React.Component { private renderTelemetryEnabledOrDisabledText = () => { const { telemetry } = this.props; - if (!telemetry || !telemetry.telemetryService.userCanChangeSettings) { + if ( + !telemetry || + !telemetry.telemetryService.userCanChangeSettings || + !telemetry.telemetryService.getCanChangeOptInStatus() + ) { return null; } diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts index 7abaf5d19f008d..009382eee0009a 100644 --- a/src/plugins/home/public/index.ts +++ b/src/plugins/home/public/index.ts @@ -19,7 +19,9 @@ export type { export { FeatureCatalogueCategory } from './services'; export type { + AddDataTab, FeatureCatalogueEntry, + FeatureCatalogueRegistry, FeatureCatalogueSolution, Environment, TutorialVariables, diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts index c75ce4e83921cb..feffc2f2e003c0 100644 --- a/src/plugins/home/server/index.ts +++ b/src/plugins/home/server/index.ts @@ -7,8 +7,21 @@ */ export type { HomeServerPluginSetup, HomeServerPluginStart } from './plugin'; -export type { TutorialProvider } from './services'; -export type { SampleDatasetProvider, SampleDataRegistrySetup } from './services'; +export { EmbeddableTypes, TutorialsCategory } from './services'; +export type { + AppLinkData, + ArtifactsSchema, + TutorialProvider, + TutorialSchema, + InstructionSetSchema, + InstructionsSchema, + TutorialContext, + SampleDatasetProvider, + SampleDataRegistrySetup, + SampleDatasetDashboardPanel, + SampleObject, + ScopedTutorialContextFactory, +} from './services'; import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server'; import { HomeServerPlugin } from './plugin'; import { configSchema, ConfigSchema } from '../config'; @@ -23,10 +36,3 @@ export const config: PluginConfigDescriptor = { export const plugin = (initContext: PluginInitializerContext) => new HomeServerPlugin(initContext); export { INSTRUCTION_VARIANT } from '../common/instruction_variant'; -export { TutorialsCategory } from './services/tutorials'; -export type { - ArtifactsSchema, - TutorialSchema, - InstructionSetSchema, - InstructionsSchema, -} from './services/tutorials'; diff --git a/src/plugins/home/server/services/index.ts b/src/plugins/home/server/services/index.ts index 5674a3501f0644..28086fc86d52ca 100644 --- a/src/plugins/home/server/services/index.ts +++ b/src/plugins/home/server/services/index.ts @@ -9,10 +9,9 @@ // provided to other plugins as APIs // should model the plugin lifecycle -export { TutorialsRegistry } from './tutorials'; -export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials'; +export { TutorialsRegistry, TutorialsCategory } from './tutorials'; -export { TutorialsCategory } from './tutorials'; +export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials'; export type { InstructionSetSchema, @@ -22,12 +21,19 @@ export type { ArtifactsSchema, TutorialSchema, TutorialProvider, + TutorialContext, TutorialContextFactory, ScopedTutorialContextFactory, } from './tutorials'; -export { SampleDataRegistry } from './sample_data'; +export { EmbeddableTypes, SampleDataRegistry } from './sample_data'; -export type { SampleDataRegistrySetup, SampleDataRegistryStart } from './sample_data'; - -export type { SampleDatasetSchema, SampleDatasetProvider } from './sample_data'; +export type { + AppLinkData, + SampleDataRegistrySetup, + SampleDataRegistryStart, + SampleDatasetDashboardPanel, + SampleDatasetProvider, + SampleDatasetSchema, + SampleObject, +} from './sample_data'; diff --git a/src/plugins/home/server/services/new_instance_status.test.ts b/src/plugins/home/server/services/new_instance_status.test.ts deleted file mode 100644 index 9ce8f8571f5a15..00000000000000 --- a/src/plugins/home/server/services/new_instance_status.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { isNewInstance } from './new_instance_status'; -import { elasticsearchServiceMock, savedObjectsClientMock } from '../../../../core/server/mocks'; - -describe('isNewInstance', () => { - const esClient = elasticsearchServiceMock.createScopedClusterClient(); - const soClient = savedObjectsClientMock.create(); - - beforeEach(() => jest.resetAllMocks()); - - it('returns true when there are no index patterns', async () => { - soClient.find.mockResolvedValue({ - page: 1, - per_page: 100, - total: 0, - saved_objects: [], - }); - expect(await isNewInstance({ esClient, soClient })).toEqual(true); - }); - - it('returns false when there are any index patterns other than metrics-* or logs-*', async () => { - soClient.find.mockResolvedValue({ - page: 1, - per_page: 100, - total: 1, - saved_objects: [ - { - id: '1', - references: [], - type: 'index-pattern', - score: 99, - attributes: { title: 'my-pattern-*' }, - }, - ], - }); - expect(await isNewInstance({ esClient, soClient })).toEqual(false); - }); - - describe('when only metrics-* and logs-* index patterns exist', () => { - beforeEach(() => { - soClient.find.mockResolvedValue({ - page: 1, - per_page: 100, - total: 2, - saved_objects: [ - { - id: '1', - references: [], - type: 'index-pattern', - score: 99, - attributes: { title: 'metrics-*' }, - }, - { - id: '2', - references: [], - type: 'index-pattern', - score: 99, - attributes: { title: 'logs-*' }, - }, - ], - }); - }); - - it('calls /_cat/indices for the index patterns', async () => { - await isNewInstance({ esClient, soClient }); - expect(esClient.asCurrentUser.cat.indices).toHaveBeenCalledWith({ - index: 'logs-*,metrics-*', - format: 'json', - }); - }); - - it('returns true if no logs or metrics indices exist', async () => { - esClient.asCurrentUser.cat.indices.mockReturnValue( - elasticsearchServiceMock.createSuccessTransportRequestPromise([]) - ); - expect(await isNewInstance({ esClient, soClient })).toEqual(true); - }); - - it('returns true if no logs or metrics indices contain data', async () => { - esClient.asCurrentUser.cat.indices.mockReturnValue( - elasticsearchServiceMock.createSuccessTransportRequestPromise([ - { index: '.ds-metrics-foo', 'docs.count': '0' }, - ]) - ); - expect(await isNewInstance({ esClient, soClient })).toEqual(true); - }); - - it('returns true if only metrics-elastic_agent index contains data', async () => { - esClient.asCurrentUser.cat.indices.mockReturnValue( - elasticsearchServiceMock.createSuccessTransportRequestPromise([ - { index: '.ds-metrics-elastic_agent', 'docs.count': '100' }, - ]) - ); - expect(await isNewInstance({ esClient, soClient })).toEqual(true); - }); - - it('returns true if only logs-elastic_agent index contains data', async () => { - esClient.asCurrentUser.cat.indices.mockReturnValue( - elasticsearchServiceMock.createSuccessTransportRequestPromise([ - { index: '.ds-logs-elastic_agent', 'docs.count': '100' }, - ]) - ); - expect(await isNewInstance({ esClient, soClient })).toEqual(true); - }); - - it('returns false if any other logs or metrics indices contain data', async () => { - esClient.asCurrentUser.cat.indices.mockReturnValue( - elasticsearchServiceMock.createSuccessTransportRequestPromise([ - { index: '.ds-metrics-foo', 'docs.count': '100' }, - ]) - ); - expect(await isNewInstance({ esClient, soClient })).toEqual(false); - }); - - it('returns false if an authentication error is thrown', async () => { - esClient.asCurrentUser.cat.indices.mockReturnValue( - elasticsearchServiceMock.createErrorTransportRequestPromise({}) - ); - expect(await isNewInstance({ esClient, soClient })).toEqual(false); - }); - }); -}); diff --git a/src/plugins/home/server/services/new_instance_status.ts b/src/plugins/home/server/services/new_instance_status.ts deleted file mode 100644 index e19380e9288224..00000000000000 --- a/src/plugins/home/server/services/new_instance_status.ts +++ /dev/null @@ -1,66 +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 { IScopedClusterClient, SavedObjectsClientContract } from '../../../../core/server'; - -const LOGS_INDEX_PATTERN = 'logs-*'; -const METRICS_INDEX_PATTERN = 'metrics-*'; - -const INDEX_PREFIXES_TO_IGNORE = [ - '.ds-metrics-elastic_agent', // ignore index created by Fleet server itself - '.ds-logs-elastic_agent', // ignore index created by Fleet server itself -]; - -interface Deps { - esClient: IScopedClusterClient; - soClient: SavedObjectsClientContract; -} - -export const isNewInstance = async ({ esClient, soClient }: Deps): Promise => { - const indexPatterns = await soClient.find<{ title: string }>({ - type: 'index-pattern', - fields: ['title'], - search: `*`, - searchFields: ['title'], - perPage: 100, - }); - - // If there are no index patterns, assume this is a new instance - if (indexPatterns.total === 0) { - return true; - } - - // If there are any index patterns that are not the default metrics-* and logs-* ones created by Fleet, assume this - // is not a new instance - if ( - indexPatterns.saved_objects.some( - (ip) => - ip.attributes.title !== LOGS_INDEX_PATTERN && ip.attributes.title !== METRICS_INDEX_PATTERN - ) - ) { - return false; - } - - try { - const logsAndMetricsIndices = await esClient.asCurrentUser.cat.indices({ - index: `${LOGS_INDEX_PATTERN},${METRICS_INDEX_PATTERN}`, - format: 'json', - }); - - const anyIndicesContainerUserData = logsAndMetricsIndices.body - // Ignore some data that is shipped by default - .filter(({ index }) => !INDEX_PREFIXES_TO_IGNORE.some((prefix) => index?.startsWith(prefix))) - // If any other logs and metrics indices have data, return false - .some((catResult) => (catResult['docs.count'] ?? '0') !== '0'); - - return !anyIndicesContainerUserData; - } catch (e) { - // If any errors are encountered return false to be safe - return false; - } -}; diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts index cc328f3edbf71d..08747f08fc923d 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/index.ts @@ -10,7 +10,7 @@ import path from 'path'; import { i18n } from '@kbn/i18n'; import { getSavedObjects } from './saved_objects'; import { fieldMappings } from './field_mappings'; -import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types'; +import { SampleDatasetSchema } from '../../lib/sample_dataset_registry_types'; const ecommerceName = i18n.translate('home.sampleData.ecommerceSpecTitle', { defaultMessage: 'Sample eCommerce orders', @@ -18,7 +18,6 @@ const ecommerceName = i18n.translate('home.sampleData.ecommerceSpecTitle', { const ecommerceDescription = i18n.translate('home.sampleData.ecommerceSpecDescription', { defaultMessage: 'Sample data, visualizations, and dashboards for tracking eCommerce orders.', }); -const initialAppLinks = [] as AppLinkSchema[]; export const ecommerceSpecProvider = function (): SampleDatasetSchema { return { @@ -28,7 +27,6 @@ export const ecommerceSpecProvider = function (): SampleDatasetSchema { previewImagePath: '/plugins/home/assets/sample_data_resources/ecommerce/dashboard.png', darkPreviewImagePath: '/plugins/home/assets/sample_data_resources/ecommerce/dashboard_dark.png', overviewDashboard: '722b74f0-b882-11e8-a6d9-e546fe2bba5f', - appLinks: initialAppLinks, defaultIndex: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', savedObjects: getSavedObjects(), dataIndices: [ diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 9559aa6b930a31..25923f247ca8b4 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -140,9 +140,10 @@ export const getSavedObjects = (): SavedObject[] => [ { id: '9c6f83f0-bb4d-11e8-9c84-77068524bcab', type: 'visualization', - updated_at: '2018-10-01T15:13:03.270Z', + updated_at: '2021-10-28T15:07:24.077Z', version: '1', - migrationVersion: {}, + coreMigrationVersion: '8.0.0', + migrationVersion: { visualization: '8.0.0' }, attributes: { title: i18n.translate('home.sampleData.ecommerceSpec.salesCountMapTitle', { defaultMessage: '[eCommerce] Sales Count Map', @@ -154,10 +155,16 @@ export const getSavedObjects = (): SavedObject[] => [ version: 1, kibanaSavedObjectMeta: { searchSourceJSON: - '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', + '{"query":{"query":"","language":"kuery"},"filter":[],"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}', }, }, - references: [], + references: [ + { + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + }, + ], }, { attributes: { diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts index 29260b99674000..ac0987559eb796 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts @@ -10,7 +10,7 @@ import path from 'path'; import { i18n } from '@kbn/i18n'; import { getSavedObjects } from './saved_objects'; import { fieldMappings } from './field_mappings'; -import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types'; +import { SampleDatasetSchema } from '../../lib/sample_dataset_registry_types'; const flightsName = i18n.translate('home.sampleData.flightsSpecTitle', { defaultMessage: 'Sample flight data', @@ -18,7 +18,6 @@ const flightsName = i18n.translate('home.sampleData.flightsSpecTitle', { const flightsDescription = i18n.translate('home.sampleData.flightsSpecDescription', { defaultMessage: 'Sample data, visualizations, and dashboards for monitoring flight routes.', }); -const initialAppLinks = [] as AppLinkSchema[]; export const flightsSpecProvider = function (): SampleDatasetSchema { return { @@ -28,7 +27,6 @@ export const flightsSpecProvider = function (): SampleDatasetSchema { previewImagePath: '/plugins/home/assets/sample_data_resources/flights/dashboard.png', darkPreviewImagePath: '/plugins/home/assets/sample_data_resources/flights/dashboard_dark.png', overviewDashboard: '7adfa750-4c81-11e8-b3d7-01146121b73d', - appLinks: initialAppLinks, defaultIndex: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', savedObjects: getSavedObjects(), dataIndices: [ diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts index 43d42c25574311..77d5f868ad285c 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts @@ -10,7 +10,7 @@ import path from 'path'; import { i18n } from '@kbn/i18n'; import { getSavedObjects } from './saved_objects'; import { fieldMappings } from './field_mappings'; -import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types'; +import { SampleDatasetSchema } from '../../lib/sample_dataset_registry_types'; const logsName = i18n.translate('home.sampleData.logsSpecTitle', { defaultMessage: 'Sample web logs', @@ -18,7 +18,6 @@ const logsName = i18n.translate('home.sampleData.logsSpecTitle', { const logsDescription = i18n.translate('home.sampleData.logsSpecDescription', { defaultMessage: 'Sample data, visualizations, and dashboards for monitoring web logs.', }); -const initialAppLinks = [] as AppLinkSchema[]; export const GLOBE_ICON_PATH = '/plugins/home/assets/sample_data_resources/logs/icon.svg'; export const logsSpecProvider = function (): SampleDatasetSchema { @@ -29,7 +28,6 @@ export const logsSpecProvider = function (): SampleDatasetSchema { previewImagePath: '/plugins/home/assets/sample_data_resources/logs/dashboard.png', darkPreviewImagePath: '/plugins/home/assets/sample_data_resources/logs/dashboard_dark.png', overviewDashboard: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b', - appLinks: initialAppLinks, defaultIndex: '90943e30-9a47-11e8-b64d-95841ca0b247', savedObjects: getSavedObjects(), dataIndices: [ diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index c60110176b67b6..840fc6e2c175c5 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -14,9 +14,10 @@ export const getSavedObjects = (): SavedObject[] => [ { id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', type: 'visualization', - updated_at: '2018-08-29T13:22:17.617Z', + updated_at: '2021-10-28T15:07:36.622Z', version: '1', - migrationVersion: {}, + coreMigrationVersion: '8.0.0', + migrationVersion: { visualization: '8.0.0' }, attributes: { title: i18n.translate('home.sampleData.logsSpec.visitorsMapTitle', { defaultMessage: '[Logs] Visitors Map', @@ -28,10 +29,16 @@ export const getSavedObjects = (): SavedObject[] => [ version: 1, kibanaSavedObjectMeta: { searchSourceJSON: - '{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}', + '{"filter":[],"query":{"query":"","language":"kuery"},"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}', }, }, - references: [], + references: [ + { + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + }, + ], }, { id: 'cb099a20-ea66-11eb-9425-113343a037e3', @@ -88,25 +95,32 @@ export const getSavedObjects = (): SavedObject[] => [ { id: '69a34b00-9ee8-11e7-8711-e7a007dcef99', type: 'visualization', - updated_at: '2018-08-29T13:24:46.136Z', + updated_at: '2021-10-28T14:38:21.435Z', version: '2', - migrationVersion: {}, + coreMigrationVersion: '8.0.0', + migrationVersion: { visualization: '8.0.0' }, attributes: { title: i18n.translate('home.sampleData.logsSpec.goalsTitle', { defaultMessage: '[Logs] Goals', }), visState: - '{"title":"[Logs] Goals","type":"gauge","params":{"type":"gauge","addTooltip":true,"addLegend":false,"gauge":{"verticalSplit":false,"extendRange":true,"percentageMode":false,"gaugeType":"Arc","gaugeStyle":"Full","backStyle":"Full","orientation":"vertical","colorSchema":"Green to Red","gaugeColorMode":"Labels","colorsRange":[{"from":0,"to":500},{"from":500,"to":1000},{"from":1000,"to":1500}],"invertColors":true,"labels":{"show":false,"color":"black"},"scale":{"show":true,"labels":false,"color":"#333"},"type":"meter","style":{"bgWidth":0.9,"width":0.9,"mask":false,"bgMask":false,"maskBars":50,"bgFill":"#eee","bgColor":false,"subText":"visitors","fontSize":60,"labelColor":true}},"isDisplayWarning":false},"aggs":[{"id":"1","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}}]}', + '{"title":"[Logs] Goals","type":"gauge","params":{"type":"gauge","addTooltip":true,"addLegend":false,"gauge":{"extendRange":true,"percentageMode":false,"gaugeType":"Arc","gaugeStyle":"Full","backStyle":"Full","orientation":"vertical","colorSchema":"Green to Red","gaugeColorMode":"Labels","colorsRange":[{"from":0,"to":500},{"from":500,"to":1000},{"from":1000,"to":1500}],"invertColors":true,"labels":{"show":false,"color":"black"},"scale":{"show":true,"labels":false,"color":"#333"},"type":"meter","style":{"bgWidth":0.9,"width":0.9,"mask":false,"bgMask":false,"maskBars":50,"bgFill":"#eee","bgColor":false,"subText":"visitors","fontSize":60,"labelColor":true},"alignment":"horizontal"},"isDisplayWarning":false},"aggs":[{"id":"1","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}}]}', uiStateJSON: '{"vis":{"defaultColors":{"0 - 500":"rgb(165,0,38)","500 - 1000":"rgb(255,255,190)","1000 - 1500":"rgb(0,104,55)"},"colors":{"75 - 100":"#629E51","50 - 75":"#EAB839","0 - 50":"#E24D42","0 - 100":"#E24D42","200 - 300":"#7EB26D","500 - 1000":"#E5AC0E","0 - 500":"#E24D42","1000 - 1500":"#7EB26D"},"legendOpen":true}}', description: '', version: 1, kibanaSavedObjectMeta: { searchSourceJSON: - '{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}', + '{"filter":[],"query":{"query":"","language":"kuery"},"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}', }, }, - references: [], + references: [ + { + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + }, + ], }, { id: '7cbd2350-2223-11e8-b802-5bcf64c2cfb4', @@ -366,13 +380,13 @@ export const getSavedObjects = (): SavedObject[] => [ { id: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b', type: 'dashboard', - updated_at: '2021-07-21T21:43:43.870Z', + updated_at: '2021-10-28T15:07:36.622Z', version: '3', references: [ { id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', name: '4:panel_4', - type: 'map', + type: 'visualization', }, { id: '4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b', diff --git a/src/plugins/home/server/services/sample_data/index.ts b/src/plugins/home/server/services/sample_data/index.ts index 30384dad8951d1..9af76ac3c765c3 100644 --- a/src/plugins/home/server/services/sample_data/index.ts +++ b/src/plugins/home/server/services/sample_data/index.ts @@ -10,7 +10,12 @@ export { SampleDataRegistry } from './sample_data_registry'; export type { SampleDataRegistrySetup, SampleDataRegistryStart } from './sample_data_registry'; +export { EmbeddableTypes } from './lib/sample_dataset_registry_types'; + export type { - SampleDatasetSchema, + AppLinkData, + SampleDatasetDashboardPanel, SampleDatasetProvider, + SampleDatasetSchema, + SampleObject, } from './lib/sample_dataset_registry_types'; diff --git a/src/plugins/home/server/services/sample_data/lib/find_sample_objects.test.mock.ts b/src/plugins/home/server/services/sample_data/lib/find_sample_objects.test.mock.ts new file mode 100644 index 00000000000000..0d40ea23942e8f --- /dev/null +++ b/src/plugins/home/server/services/sample_data/lib/find_sample_objects.test.mock.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. + */ + +export const mockBuildNode = jest.fn(); + +jest.mock('@kbn/es-query', () => { + return { + nodeTypes: { + function: { + buildNode: mockBuildNode, + }, + }, + }; +}); diff --git a/src/plugins/home/server/services/sample_data/lib/find_sample_objects.test.ts b/src/plugins/home/server/services/sample_data/lib/find_sample_objects.test.ts new file mode 100644 index 00000000000000..93a3e8c1d5f9c1 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/lib/find_sample_objects.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { mockBuildNode } from './find_sample_objects.test.mock'; + +import type { SavedObject, SavedObjectsFindResponse } from 'src/core/server'; +import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks'; +import { findSampleObjects } from './find_sample_objects'; + +describe('findSampleObjects', () => { + function setup() { + const mockClient = savedObjectsClientMock.create(); + const mockLogger = loggingSystemMock.createLogger(); + return { + client: mockClient, + logger: mockLogger, + }; + } + + beforeEach(() => { + mockBuildNode.mockReset(); + }); + + it('searches for objects and returns expected results', async () => { + const { client, logger } = setup(); + const obj1 = { type: 'obj-type-1', id: 'obj-id-1' }; + const obj2 = { type: 'obj-type-2', id: 'obj-id-2' }; + const obj3 = { type: 'obj-type-3', id: 'obj-id-3' }; + const obj4 = { type: 'obj-type-3', id: 'obj-id-4' }; + const objects = [obj1, obj2, obj3, obj4]; + const params = { client, logger, objects }; + + client.bulkGet.mockResolvedValue({ + saved_objects: [ + obj1, // bulkGet success for obj1 + { ...obj2, error: { statusCode: 403 } }, // bulkGet failure - will not attempt to find by originId since the error is not 404 + { ...obj3, error: { statusCode: 404 } }, // bulkGet failure - will attempt to find by originId since the error is 404 + { ...obj4, error: { statusCode: 404 } }, // bulkGet failure - will attempt to find by originId since the error is 404 + ] as SavedObject[], + }); + client.find.mockResolvedValue({ + saved_objects: [{ type: obj4.type, id: 'obj-id-x', originId: obj4.id }], // find success for obj4 + total: 1, + } as SavedObjectsFindResponse); + const result = await findSampleObjects(params); + + expect(result).toEqual([ + { ...obj1, foundObjectId: obj1.id }, + { ...obj2, foundObjectId: undefined }, + { ...obj3, foundObjectId: undefined }, + { ...obj4, foundObjectId: 'obj-id-x' }, + ]); + expect(client.bulkGet).toHaveBeenCalledWith(objects); + expect(mockBuildNode).toHaveBeenCalledTimes(3); + expect(mockBuildNode).toHaveBeenNthCalledWith(1, 'is', `${obj3.type}.originId`, obj3.id); + expect(mockBuildNode).toHaveBeenNthCalledWith(2, 'is', `${obj4.type}.originId`, obj4.id); + expect(mockBuildNode).toHaveBeenNthCalledWith(3, 'or', expect.any(Array)); + expect(client.find).toHaveBeenCalledWith(expect.objectContaining({ type: ['obj-type-3'] })); // obj3 and obj4 have the same type; the type param is deduplicated + expect(logger.warn).not.toHaveBeenCalled(); + }); + + it('skips find if there are no objects left to search for', async () => { + const { client, logger } = setup(); + const obj1 = { type: 'obj-type-1', id: 'obj-id-1' }; + const obj2 = { type: 'obj-type-2', id: 'obj-id-2' }; + const objects = [obj1, obj2]; + const params = { client, logger, objects }; + + client.bulkGet.mockResolvedValue({ + saved_objects: [ + obj1, // bulkGet success for obj1 + { ...obj2, error: { statusCode: 403 } }, // bulkGet failure - will not attempt to find by originId since the error is not 404 + ] as SavedObject[], + }); + const result = await findSampleObjects(params); + + expect(result).toEqual([ + { ...obj1, foundObjectId: obj1.id }, + { ...obj2, foundObjectId: undefined }, + ]); + expect(client.bulkGet).toHaveBeenCalledWith(objects); + expect(mockBuildNode).not.toHaveBeenCalled(); + expect(client.find).not.toHaveBeenCalled(); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + it('logs expected warnings', async () => { + const { client, logger } = setup(); + const obj1 = { type: 'obj-type-1', id: 'obj-id-1' }; + const objects = [obj1]; + const params = { client, logger, objects }; + + client.bulkGet.mockResolvedValue({ + saved_objects: [ + { ...obj1, error: { statusCode: 404 } }, // bulkGet failure - will attempt to find by originId since the error is 404 + ] as SavedObject[], + }); + client.find.mockResolvedValue({ + saved_objects: [ + { type: obj1.type, id: 'obj-id-x', originId: obj1.id }, // find success for obj4 + { type: obj1.type, id: 'obj-id-y', originId: obj1.id }, // find success for obj4 + ], + total: 10001, + } as SavedObjectsFindResponse); + const result = await findSampleObjects(params); + expect(result).toEqual([{ ...obj1, foundObjectId: 'obj-id-x' }]); // obj-id-y is ignored + expect(client.bulkGet).toHaveBeenCalledWith(objects); + expect(mockBuildNode).toHaveBeenCalledTimes(2); + expect(mockBuildNode).toHaveBeenNthCalledWith(1, 'is', `${obj1.type}.originId`, obj1.id); + expect(mockBuildNode).toHaveBeenNthCalledWith(2, 'or', expect.any(Array)); + expect(client.find).toHaveBeenCalledWith(expect.objectContaining({ type: ['obj-type-1'] })); + expect(logger.warn).toHaveBeenCalledTimes(2); + expect(logger.warn).toHaveBeenCalledWith( + 'findSampleObjects got 10001 results, only using the first 10000' + ); + expect(logger.warn).toHaveBeenCalledWith( + 'Found two sample objects with the same origin "obj-id-1" (previously found "obj-id-x", ignoring "obj-id-y")' + ); + }); +}); diff --git a/src/plugins/home/server/services/sample_data/lib/find_sample_objects.ts b/src/plugins/home/server/services/sample_data/lib/find_sample_objects.ts new file mode 100644 index 00000000000000..904470acc2c847 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/lib/find_sample_objects.ts @@ -0,0 +1,104 @@ +/* + * 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 esKuery from '@kbn/es-query'; +import type { Logger, SavedObjectsClientContract } from 'src/core/server'; + +const MAX_OBJECTS_TO_FIND = 10000; // we only expect up to a few dozen, search for 10k to be safe; anything over this is ignored + +export interface FindSampleObjectsParams { + client: SavedObjectsClientContract; + logger: Logger; + objects: SampleObject[]; +} + +export interface SampleObject { + type: string; + id: string; +} + +export interface FindSampleObjectsResponseObject { + type: string; + id: string; + /** Contains a string if this sample data object was found, or undefined if it was not. */ + foundObjectId: string | undefined; +} + +/** + * Given an array of objects in a sample dataset, this function attempts to find if those objects exist in the current space. + * It attempts to find objects with an origin of the sample data (e.g., matching `id` or `originId`). + */ +export async function findSampleObjects({ client, logger, objects }: FindSampleObjectsParams) { + const bulkGetResponse = await client.bulkGet(objects); + + let resultsMap = new Map(); + const objectsToFind: SampleObject[] = []; + objects.forEach((object, i) => { + const bulkGetResult = bulkGetResponse.saved_objects[i]; + if (!bulkGetResult.error) { + const { type, id } = object; + const key = getObjKey(type, id); + resultsMap.set(key, id); + } else if (bulkGetResult.error.statusCode === 404) { + objectsToFind.push(object); + } + }); + + if (objectsToFind.length > 0) { + const options = { + type: getUniqueTypes(objectsToFind), + filter: createKueryFilter(objectsToFind), + fields: ['title'], // we don't want to return all source fields, so we have to specify at least one source field + perPage: MAX_OBJECTS_TO_FIND, + }; + const findResponse = await client.find(options); + if (findResponse.total > MAX_OBJECTS_TO_FIND) { + // As of this writing, it is not possible to encounter this scenario when using Kibana import or copy-to-space, because at most one + // object can exist in a given space. However, as of today, when objects are shareable you _could_ get Kibana into a state where + // multiple objects of the same origin exist in the same space. + // #116677 describes solutions to fully mitigate this edge case in the future. + logger.warn( + `findSampleObjects got ${findResponse.total} results, only using the first ${MAX_OBJECTS_TO_FIND}` + ); + } + resultsMap = findResponse.saved_objects.reduce((acc, { type, id, originId }) => { + const key = getObjKey(type, originId!); + const existing = acc.get(key); + if (existing) { + // As of this writing, it is not possible to encounter this scenario when using Kibana import or copy-to-space, because at most one + // object can exist in a given space. However, as of today, when objects are shareable you _could_ get Kibana into a state where + // multiple objects of the same origin exist in the same space. + // #116677 describes solutions to fully mitigate this edge case in the future. + logger.warn( + `Found two sample objects with the same origin "${originId}" (previously found "${existing}", ignoring "${id}")` + ); + return acc; + } + return acc.set(key, id); + }, resultsMap); + } + + return objects.map(({ type, id }) => { + const key = getObjKey(type, id); + return { type, id, foundObjectId: resultsMap.get(key) }; + }); +} + +function getUniqueTypes(objects: SampleObject[]) { + return [...new Set(objects.map(({ type }) => type))]; +} + +function createKueryFilter(objects: SampleObject[]) { + const { buildNode } = esKuery.nodeTypes.function; + const kueryNodes = objects.map(({ type, id }) => buildNode('is', `${type}.originId`, id)); // the repository converts this node into "and (type is ..., originId is ...)" + return buildNode('or', kueryNodes); +} + +function getObjKey(type: string, id: string) { + return `${type}:${id}`; +} diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts index 09af7728f74d22..8d26d08460b5b2 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts @@ -7,7 +7,7 @@ */ import type { SampleDatasetSchema } from './sample_dataset_schema'; -export type { SampleDatasetSchema, AppLinkSchema, DataIndexSchema } from './sample_dataset_schema'; +export type { SampleDatasetSchema, DataIndexSchema } from './sample_dataset_schema'; export enum DatasetStatusTypes { NOT_INSTALLED = 'not_installed', @@ -28,3 +28,34 @@ export enum EmbeddableTypes { VISUALIZE_EMBEDDABLE_TYPE = 'visualization', } export type SampleDatasetProvider = () => SampleDatasetSchema; + +/** This type is used to identify an object in a sample dataset. */ +export interface SampleObject { + /** The type of the sample object. */ + type: string; + /** The ID of the sample object. */ + id: string; +} + +/** + * This type is used by consumers to register a new app link for a sample dataset. + */ +export interface AppLinkData { + /** + * The sample object that is used for this app link's path; if the path does not use an object ID, set this to null. + */ + sampleObject: SampleObject | null; + /** + * Function that returns the path for this app link. Note that the `objectId` can be different than the given `sampleObject.id`, depending + * on how the sample data was installed. If the `sampleObject` is null, the `objectId` argument will be an empty string. + */ + getPath: (objectId: string) => string; + /** + * The label for this app link. + */ + label: string; + /** + * The icon for this app link. + */ + icon: string; +} diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts index 87b042aebcc1f5..66f69abca3f18f 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts @@ -48,13 +48,6 @@ const dataIndexSchema = schema.object({ export type DataIndexSchema = TypeOf; -const appLinkSchema = schema.object({ - path: schema.string(), - label: schema.string(), - icon: schema.string(), -}); -export type AppLinkSchema = TypeOf; - export const sampleDataSchema = schema.object({ id: schema.string({ validate(value: string) { @@ -71,7 +64,6 @@ export const sampleDataSchema = schema.object({ // saved object id of main dashboard for sample data set overviewDashboard: schema.string(), - appLinks: schema.arrayOf(appLinkSchema, { defaultValue: [] }), // saved object id of default index-pattern for sample data set defaultIndex: schema.string(), diff --git a/typings/accept.d.ts b/src/plugins/home/server/services/sample_data/lib/utils.ts similarity index 62% rename from typings/accept.d.ts rename to src/plugins/home/server/services/sample_data/lib/utils.ts index e868063c7f7c0a..153fe6923583d8 100644 --- a/typings/accept.d.ts +++ b/src/plugins/home/server/services/sample_data/lib/utils.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -declare module 'accept' { - // @types/accept does not include the `preferences` argument so we override the type to include it - export function encodings(encodingHeader?: string, preferences?: string[]): string[]; +import type { SampleObject } from './sample_dataset_registry_types'; + +export function getUniqueObjectTypes(objects: SampleObject[]) { + return [...new Set(objects.map(({ type }) => type))]; } diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts index d0457f0a6d301c..17d35c6cb4b7ee 100644 --- a/src/plugins/home/server/services/sample_data/routes/install.ts +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -6,13 +6,9 @@ * Side Public License, v 1. */ +import { Readable } from 'stream'; import { schema } from '@kbn/config-schema'; -import type { - IRouter, - Logger, - IScopedClusterClient, - SavedObjectsBulkCreateObject, -} from 'src/core/server'; +import { IRouter, Logger, IScopedClusterClient } from 'src/core/server'; import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; import { createIndexName } from '../lib/create_index_name'; import { @@ -22,6 +18,8 @@ import { } from '../lib/translate_timestamp'; import { loadData } from '../lib/load_data'; import { SampleDataUsageTracker } from '../usage/usage'; +import { getSavedObjectsClient } from './utils'; +import { getUniqueObjectTypes } from '../lib/utils'; const insertDataIntoIndex = ( dataIndexConfig: any, @@ -143,35 +141,31 @@ export function createInstallRoute( } } - let createResults; - try { - const { getClient, typeRegistry } = context.core.savedObjects; - - const includedHiddenTypes = sampleDataset.savedObjects - .map((object) => object.type) - .filter((supportedType) => typeRegistry.isHidden(supportedType)); + const { getImporter } = context.core.savedObjects; + const objectTypes = getUniqueObjectTypes(sampleDataset.savedObjects); + const savedObjectsClient = getSavedObjectsClient(context, objectTypes); + const importer = getImporter(savedObjectsClient); - const client = getClient({ includedHiddenTypes }); + const savedObjects = sampleDataset.savedObjects.map(({ version, ...obj }) => obj); + const readStream = Readable.from(savedObjects); - const savedObjects = sampleDataset.savedObjects as SavedObjectsBulkCreateObject[]; - createResults = await client.bulkCreate( - savedObjects.map(({ version, ...savedObject }) => savedObject), - { overwrite: true } - ); + try { + const { errors = [] } = await importer.import({ + readStream, + overwrite: true, + createNewCopies: false, + }); + if (errors.length > 0) { + const errMsg = `sample_data install errors while loading saved objects. Errors: ${JSON.stringify( + errors.map(({ type, id, error }) => ({ type, id, error })) // discard other fields + )}`; + logger.warn(errMsg); + return res.customError({ body: errMsg, statusCode: 500 }); + } } catch (err) { - const errMsg = `bulkCreate failed, error: ${err.message}`; + const errMsg = `import failed, error: ${err.message}`; throw new Error(errMsg); } - const errors = createResults.saved_objects.filter((savedObjectCreateResult) => { - return Boolean(savedObjectCreateResult.error); - }); - if (errors.length > 0) { - const errMsg = `sample_data install errors while loading saved objects. Errors: ${JSON.stringify( - errors - )}`; - logger.warn(errMsg); - return res.customError({ body: errMsg, statusCode: 403 }); - } usageTracker.addInstall(params.id); // FINALLY diff --git a/src/plugins/home/server/services/sample_data/routes/list.ts b/src/plugins/home/server/services/sample_data/routes/list.ts index e3e213196889c2..a7ca32341f1f59 100644 --- a/src/plugins/home/server/services/sample_data/routes/list.ts +++ b/src/plugins/home/server/services/sample_data/routes/list.ts @@ -6,75 +6,122 @@ * Side Public License, v 1. */ -import { IRouter } from 'src/core/server'; -import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; +import type { IRouter, Logger, RequestHandlerContext } from 'src/core/server'; +import type { AppLinkData, SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; import { createIndexName } from '../lib/create_index_name'; +import type { FindSampleObjectsResponseObject } from '../lib/find_sample_objects'; +import { findSampleObjects } from '../lib/find_sample_objects'; +import { getUniqueObjectTypes } from '../lib/utils'; +import { getSavedObjectsClient } from './utils'; const NOT_INSTALLED = 'not_installed'; const INSTALLED = 'installed'; const UNKNOWN = 'unknown'; -export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSchema[]) => { - router.get({ path: '/api/sample_data', validate: false }, async (context, req, res) => { - const registeredSampleDatasets = sampleDatasets.map((sampleDataset) => { - return { - id: sampleDataset.id, - name: sampleDataset.name, - description: sampleDataset.description, - previewImagePath: sampleDataset.previewImagePath, - darkPreviewImagePath: sampleDataset.darkPreviewImagePath, - overviewDashboard: sampleDataset.overviewDashboard, - appLinks: sampleDataset.appLinks, - defaultIndex: sampleDataset.defaultIndex, - dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })), - status: sampleDataset.status, - statusMsg: sampleDataset.statusMsg, - }; - }); - const isInstalledPromises = registeredSampleDatasets.map(async (sampleDataset) => { - for (let i = 0; i < sampleDataset.dataIndices.length; i++) { - const dataIndexConfig = sampleDataset.dataIndices[i]; - const index = createIndexName(sampleDataset.id, dataIndexConfig.id); - try { - const { body: indexExists } = - await context.core.elasticsearch.client.asCurrentUser.indices.exists({ - index, - }); - if (!indexExists) { - sampleDataset.status = NOT_INSTALLED; - return; - } +export const createListRoute = ( + router: IRouter, + sampleDatasets: SampleDatasetSchema[], + appLinksMap: Map, + logger: Logger +) => { + router.get({ path: '/api/sample_data', validate: false }, async (context, _req, res) => { + const allExistingObjects = await findExistingSampleObjects(context, logger, sampleDatasets); - const { body: count } = await context.core.elasticsearch.client.asCurrentUser.count({ - index, - }); - if (count.count === 0) { - sampleDataset.status = NOT_INSTALLED; - return; - } - } catch (err) { - sampleDataset.status = UNKNOWN; - sampleDataset.statusMsg = err.message; - return; - } - } - try { - await context.core.savedObjects.client.get('dashboard', sampleDataset.overviewDashboard); - } catch (err) { - if (context.core.savedObjects.client.errors.isNotFoundError(err)) { - sampleDataset.status = NOT_INSTALLED; - return; - } + const registeredSampleDatasets = await Promise.all( + sampleDatasets.map(async (sampleDataset) => { + const existingObjects = allExistingObjects.get(sampleDataset.id)!; + const findObjectId = (type: string, id: string) => + existingObjects.find((object) => object.type === type && object.id === id) + ?.foundObjectId ?? id; - sampleDataset.status = UNKNOWN; - sampleDataset.statusMsg = err.message; - return; - } + const appLinks = (appLinksMap.get(sampleDataset.id) ?? []).map((data) => { + const { sampleObject, getPath, label, icon } = data; + if (sampleObject === null) { + return { path: getPath(''), label, icon }; + } + const objectId = findObjectId(sampleObject.type, sampleObject.id); + return { path: getPath(objectId), label, icon }; + }); + const sampleDataStatus = await getSampleDatasetStatus( + context, + allExistingObjects, + sampleDataset + ); - sampleDataset.status = INSTALLED; - }); + return { + id: sampleDataset.id, + name: sampleDataset.name, + description: sampleDataset.description, + previewImagePath: sampleDataset.previewImagePath, + darkPreviewImagePath: sampleDataset.darkPreviewImagePath, + overviewDashboard: findObjectId('dashboard', sampleDataset.overviewDashboard), + appLinks, + defaultIndex: findObjectId('index-pattern', sampleDataset.defaultIndex), + dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })), + ...sampleDataStatus, + }; + }) + ); - await Promise.all(isInstalledPromises); return res.ok({ body: registeredSampleDatasets }); }); }; + +type ExistingSampleObjects = Map; +async function findExistingSampleObjects( + context: RequestHandlerContext, + logger: Logger, + sampleDatasets: SampleDatasetSchema[] +) { + const objects = sampleDatasets + .map(({ savedObjects }) => savedObjects.map(({ type, id }) => ({ type, id }))) + .flat(); + const objectTypes = getUniqueObjectTypes(objects); + const client = getSavedObjectsClient(context, objectTypes); + const findSampleObjectsResult = await findSampleObjects({ client, logger, objects }); + + let objectCounter = 0; + return sampleDatasets.reduce((acc, { id, savedObjects }) => { + const datasetResults = savedObjects.map(() => findSampleObjectsResult[objectCounter++]); + return acc.set(id, datasetResults); + }, new Map()); +} + +// TODO: introduce PARTIALLY_INSTALLED status (#116677) +async function getSampleDatasetStatus( + context: RequestHandlerContext, + existingSampleObjects: ExistingSampleObjects, + sampleDataset: SampleDatasetSchema +): Promise<{ status: string; statusMsg?: string }> { + const dashboard = existingSampleObjects + .get(sampleDataset.id)! + .find(({ type, id }) => type === 'dashboard' && id === sampleDataset.overviewDashboard); + if (!dashboard?.foundObjectId) { + return { status: NOT_INSTALLED }; + } + + for (let i = 0; i < sampleDataset.dataIndices.length; i++) { + const dataIndexConfig = sampleDataset.dataIndices[i]; + const index = createIndexName(sampleDataset.id, dataIndexConfig.id); + try { + const { body: indexExists } = + await context.core.elasticsearch.client.asCurrentUser.indices.exists({ + index, + }); + if (!indexExists) { + return { status: NOT_INSTALLED }; + } + + const { body: count } = await context.core.elasticsearch.client.asCurrentUser.count({ + index, + }); + if (count.count === 0) { + return { status: NOT_INSTALLED }; + } + } catch (err) { + return { status: UNKNOWN, statusMsg: err.message }; + } + } + + return { status: INSTALLED }; +} diff --git a/src/plugins/home/server/services/sample_data/routes/uninstall.ts b/src/plugins/home/server/services/sample_data/routes/uninstall.ts index 3108c06492dd80..b0e8e6f102f1ea 100644 --- a/src/plugins/home/server/services/sample_data/routes/uninstall.ts +++ b/src/plugins/home/server/services/sample_data/routes/uninstall.ts @@ -6,16 +6,20 @@ * Side Public License, v 1. */ +import { isBoom } from '@hapi/boom'; import { schema } from '@kbn/config-schema'; -import _ from 'lodash'; -import { IRouter } from 'src/core/server'; +import type { IRouter, Logger } from 'src/core/server'; import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; import { createIndexName } from '../lib/create_index_name'; import { SampleDataUsageTracker } from '../usage/usage'; +import { findSampleObjects } from '../lib/find_sample_objects'; +import { getUniqueObjectTypes } from '../lib/utils'; +import { getSavedObjectsClient } from './utils'; export function createUninstallRoute( router: IRouter, sampleDatasets: SampleDatasetSchema[], + logger: Logger, usageTracker: SampleDataUsageTracker ): void { router.delete( @@ -25,16 +29,7 @@ export function createUninstallRoute( params: schema.object({ id: schema.string() }), }, }, - async ( - { - core: { - elasticsearch: { client: esClient }, - savedObjects: { getClient: getSavedObjectsClient, typeRegistry }, - }, - }, - request, - response - ) => { + async (context, request, response) => { const sampleDataset = sampleDatasets.find(({ id }) => id === request.params.id); if (!sampleDataset) { @@ -46,41 +41,46 @@ export function createUninstallRoute( const index = createIndexName(sampleDataset.id, dataIndexConfig.id); try { - await esClient.asCurrentUser.indices.delete({ - index, - }); + // TODO: don't delete the index if sample data exists in other spaces (#116677) + await context.core.elasticsearch.client.asCurrentUser.indices.delete({ index }); } catch (err) { - return response.customError({ - statusCode: err.status, - body: { - message: `Unable to delete sample data index "${index}", error: ${err.message}`, - }, - }); + // if the index doesn't exist, ignore the error and proceed + if (err.body.status !== 404) { + return response.customError({ + statusCode: err.body.status, + body: { + message: `Unable to delete sample data index "${index}", error: ${err.body.error.type}`, + }, + }); + } } } - const includedHiddenTypes = sampleDataset.savedObjects - .map((object) => object.type) - .filter((supportedType) => typeRegistry.isHidden(supportedType)); - - const savedObjectsClient = getSavedObjectsClient({ includedHiddenTypes }); + const objects = sampleDataset.savedObjects.map(({ type, id }) => ({ type, id })); + const objectTypes = getUniqueObjectTypes(objects); + const client = getSavedObjectsClient(context, objectTypes); + const findSampleObjectsResult = await findSampleObjects({ client, logger, objects }); - const deletePromises = sampleDataset.savedObjects.map(({ type, id }) => - savedObjectsClient.delete(type, id) + const objectsToDelete = findSampleObjectsResult.filter(({ foundObjectId }) => foundObjectId); + const deletePromises = objectsToDelete.map(({ type, foundObjectId }) => + client.delete(type, foundObjectId!).catch((err) => { + // if the object doesn't exist, ignore the error and proceed + if (isBoom(err) && err.output.statusCode === 404) { + return; + } + throw err; + }) ); try { await Promise.all(deletePromises); } catch (err) { - // ignore 404s since users could have deleted some of the saved objects via the UI - if (_.get(err, 'output.statusCode') !== 404) { - return response.customError({ - statusCode: err.status, - body: { - message: `Unable to delete sample dataset saved objects, error: ${err.message}`, - }, - }); - } + return response.customError({ + statusCode: err.body.status, + body: { + message: `Unable to delete sample dataset saved objects, error: ${err.body.error.type}`, + }, + }); } // track the usage operation in a non-blocking way diff --git a/src/plugins/home/server/services/sample_data/routes/utils.ts b/src/plugins/home/server/services/sample_data/routes/utils.ts new file mode 100644 index 00000000000000..6bab00895440ab --- /dev/null +++ b/src/plugins/home/server/services/sample_data/routes/utils.ts @@ -0,0 +1,17 @@ +/* + * 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 { RequestHandlerContext } from 'src/core/server'; + +export function getSavedObjectsClient(context: RequestHandlerContext, objectTypes: string[]) { + const { getClient, typeRegistry } = context.core.savedObjects; + const includedHiddenTypes = objectTypes.filter((supportedType) => + typeRegistry.isHidden(supportedType) + ); + return getClient({ includedHiddenTypes }); +} diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts index ef453592d9790f..f8dd12746832b0 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -11,8 +11,8 @@ import { SavedObject } from 'src/core/public'; import { SampleDatasetProvider, SampleDatasetSchema, - AppLinkSchema, SampleDatasetDashboardPanel, + AppLinkData, } from './lib/sample_dataset_registry_types'; import { sampleDataSchema } from './lib/sample_dataset_schema'; @@ -27,6 +27,7 @@ import { registerSampleDatasetWithIntegration } from './lib/register_with_integr export class SampleDataRegistry { constructor(private readonly initContext: PluginInitializerContext) {} private readonly sampleDatasets: SampleDatasetSchema[] = []; + private readonly appLinksMap = new Map(); private registerSampleDataSet(specProvider: SampleDatasetProvider) { let value: SampleDatasetSchema; @@ -69,14 +70,10 @@ export class SampleDataRegistry { this.initContext.logger.get('sample_data', 'usage') ); const router = core.http.createRouter(); - createListRoute(router, this.sampleDatasets); - createInstallRoute( - router, - this.sampleDatasets, - this.initContext.logger.get('sampleData'), - usageTracker - ); - createUninstallRoute(router, this.sampleDatasets, usageTracker); + const logger = this.initContext.logger.get('sampleData'); + createListRoute(router, this.sampleDatasets, this.appLinksMap, logger); + createInstallRoute(router, this.sampleDatasets, logger, usageTracker); + createUninstallRoute(router, this.sampleDatasets, logger, usageTracker); this.registerSampleDataSet(flightsSpecProvider); this.registerSampleDataSet(logsSpecProvider); @@ -100,7 +97,7 @@ export class SampleDataRegistry { sampleDataset.savedObjects = sampleDataset.savedObjects.concat(savedObjects); }, - addAppLinksToSampleDataset: (id: string, appLinks: AppLinkSchema[]) => { + addAppLinksToSampleDataset: (id: string, appLinks: AppLinkData[]) => { const sampleDataset = this.sampleDatasets.find((dataset) => { return dataset.id === id; }); @@ -109,9 +106,8 @@ export class SampleDataRegistry { throw new Error(`Unable to find sample dataset with id: ${id}`); } - sampleDataset.appLinks = sampleDataset.appLinks - ? sampleDataset.appLinks.concat(appLinks) - : []; + const existingAppLinks = this.appLinksMap.get(id) ?? []; + this.appLinksMap.set(id, [...existingAppLinks, ...appLinks]); }, replacePanelInSampleDatasetDashboard: ({ diff --git a/src/plugins/home/server/services/tutorials/index.ts b/src/plugins/home/server/services/tutorials/index.ts index f745d0190efd57..36e780b2382967 100644 --- a/src/plugins/home/server/services/tutorials/index.ts +++ b/src/plugins/home/server/services/tutorials/index.ts @@ -19,6 +19,7 @@ export type { ArtifactsSchema, TutorialSchema, TutorialProvider, + TutorialContext, TutorialContextFactory, ScopedTutorialContextFactory, } from './lib/tutorials_registry_types'; diff --git a/src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx b/src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx index e034ccd131349d..dd9d4f71174530 100644 --- a/src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx +++ b/src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx @@ -105,6 +105,13 @@ export const TimestampField = ({ const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); const isDisabled = !optionsAsComboBoxOptions.length; + if (!value && !isDisabled) { + const val = optionsAsComboBoxOptions.filter((el) => el.value === '@timestamp'); + if (val.length) { + setValue(val[0]); + } + } + return ( <> { const getRollups = async () => { try { - const response = await http.get('/api/rollup/indices'); + const response = await http.get('/api/rollup/indices'); if (response) { setRollupIndicesCapabilities(response); } diff --git a/src/plugins/index_pattern_editor/public/shared_imports.ts b/src/plugins/index_pattern_editor/public/shared_imports.ts index c99d32e0c8a28a..7edb7dfd0f599d 100644 --- a/src/plugins/index_pattern_editor/public/shared_imports.ts +++ b/src/plugins/index_pattern_editor/public/shared_imports.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -export { - IndexPattern, - IndexPatternField, +export type { DataPublicPluginStart, IndexPatternSpec, GetFieldsOptions, IndexPatternAggRestrictions, } from '../../data/public'; +export { IndexPattern, IndexPatternField } from '../../data/public'; export { createKibanaReactContext, @@ -22,18 +21,20 @@ export { useKibana, } from '../../kibana_react/public'; +export type { + FormSchema, + FormHook, + ValidationFunc, + FieldConfig, + ValidationConfig, +} from '../../es_ui_shared/static/forms/hook_form_lib'; export { useForm, useFormData, useFormContext, Form, - FormSchema, UseField, - FormHook, - ValidationFunc, - FieldConfig, getFieldValidityAndErrorMessage, - ValidationConfig, } from '../../es_ui_shared/static/forms/hook_form_lib'; export { fieldValidators } from '../../es_ui_shared/static/forms/helpers'; @@ -47,4 +48,4 @@ export { SuperSelectField, } from '../../es_ui_shared/static/forms/components'; -export { HttpStart } from '../../../core/public'; +export type { HttpStart } from '../../../core/public'; diff --git a/src/plugins/index_pattern_editor/public/test_utils/test_utils.ts b/src/plugins/index_pattern_editor/public/test_utils/test_utils.ts index c8e4aedc264716..311d93d31b5935 100644 --- a/src/plugins/index_pattern_editor/public/test_utils/test_utils.ts +++ b/src/plugins/index_pattern_editor/public/test_utils/test_utils.ts @@ -8,4 +8,5 @@ export { getRandomString } from '@kbn/test/jest'; -export { registerTestBed, TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +export { registerTestBed } from '@kbn/test/jest'; diff --git a/src/plugins/index_pattern_field_editor/__jest__/client_integration/helpers/index.ts b/src/plugins/index_pattern_field_editor/__jest__/client_integration/helpers/index.ts index 6a1f1aa74036a1..e8ff7eb7538f20 100644 --- a/src/plugins/index_pattern_field_editor/__jest__/client_integration/helpers/index.ts +++ b/src/plugins/index_pattern_field_editor/__jest__/client_integration/helpers/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -export { findTestSubject, TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +export { findTestSubject } from '@kbn/test/jest'; export { setupEnvironment, diff --git a/src/plugins/index_pattern_field_editor/kibana.json b/src/plugins/index_pattern_field_editor/kibana.json index 898e7c564e57f6..df09fd56136c3a 100644 --- a/src/plugins/index_pattern_field_editor/kibana.json +++ b/src/plugins/index_pattern_field_editor/kibana.json @@ -5,7 +5,7 @@ "ui": true, "requiredPlugins": ["data"], "optionalPlugins": ["usageCollection"], - "requiredBundles": ["kibanaReact", "esUiShared", "usageCollection", "fieldFormats"], + "requiredBundles": ["kibanaReact", "esUiShared", "fieldFormats"], "owner": { "name": "App Services", "githubTeam": "kibana-app-services" diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts index e958e1362bb054..693709729ed92a 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts @@ -12,6 +12,7 @@ export { CustomLabelField } from './custom_label_field'; export { PopularityField } from './popularity_field'; -export { ScriptField, ScriptSyntaxError } from './script_field'; +export type { ScriptSyntaxError } from './script_field'; +export { ScriptField } from './script_field'; export { FormatField } from './format_field'; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/index.ts index 31d7e95897090a..4cadb6e837620f 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/index.ts +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/index.ts @@ -9,7 +9,8 @@ import { FieldFormatEditorFactory } from '../types'; import { formatId } from './constants'; -export { defaultState, FormatEditorState } from './default'; +export type { FormatEditorState } from './default'; +export { defaultState } from './default'; export type { FormatEditorProps } from '../types'; export type { DefaultFormatEditor } from './default'; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap index 3890d6c2b9ddbe..f2f3666d4528df 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap @@ -341,7 +341,7 @@ exports[`UrlFormatEditor should render normally 1`] = ` class="euiTableRow" >
=> { return http - .post('/internal/index-pattern-management/preview_scripted_field', { + .post<{ + statusCode: ExecuteScriptResult['status']; + body: { hits: ExecuteScriptResult['hits'] }; + }>('/internal/index-pattern-management/preview_scripted_field', { body: JSON.stringify({ index: indexPatternTitle, name, diff --git a/src/plugins/index_pattern_management/public/index.ts b/src/plugins/index_pattern_management/public/index.ts index 45a2f0b5a468b4..65b71ee6053e3b 100644 --- a/src/plugins/index_pattern_management/public/index.ts +++ b/src/plugins/index_pattern_management/public/index.ts @@ -19,7 +19,7 @@ */ import { PluginInitializerContext } from 'src/core/public'; import { IndexPatternManagementPlugin } from './plugin'; -export { IndexPatternManagementSetup, IndexPatternManagementStart } from './plugin'; +export type { IndexPatternManagementSetup, IndexPatternManagementStart } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new IndexPatternManagementPlugin(initializerContext); diff --git a/src/plugins/index_pattern_management/public/scripting_languages/index.ts b/src/plugins/index_pattern_management/public/scripting_languages/index.ts index 46bb0a359ae89a..f01802786ea573 100644 --- a/src/plugins/index_pattern_management/public/scripting_languages/index.ts +++ b/src/plugins/index_pattern_management/public/scripting_languages/index.ts @@ -21,12 +21,12 @@ export const getEnabledScriptingLanguages = ( http: HttpStart, toasts: NotificationsStart['toasts'] ) => - http.get('/api/kibana/scripts/languages').catch(() => { + http.get('/api/kibana/scripts/languages').catch(() => { toasts.addDanger( i18n.translate('indexPatternManagement.scriptingLanguages.errorFetchingToastDescription', { defaultMessage: 'Error getting available scripting languages from Elasticsearch', }) ); - return []; + return [] as estypes.ScriptLanguage[]; }); diff --git a/src/plugins/inspector/common/adapters/request/index.ts b/src/plugins/inspector/common/adapters/request/index.ts index 807f11569ba2cf..d1654ea66b93d5 100644 --- a/src/plugins/inspector/common/adapters/request/index.ts +++ b/src/plugins/inspector/common/adapters/request/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ -export { Request, RequestStatistic, RequestStatistics, RequestStatus } from './types'; +export type { Request, RequestStatistic, RequestStatistics } from './types'; +export { RequestStatus } from './types'; export { RequestAdapter } from './request_adapter'; export { RequestResponder } from './request_responder'; diff --git a/src/plugins/inspector/common/index.ts b/src/plugins/inspector/common/index.ts index e92c9b670475a3..995846fadd921f 100644 --- a/src/plugins/inspector/common/index.ts +++ b/src/plugins/inspector/common/index.ts @@ -6,12 +6,5 @@ * Side Public License, v 1. */ -export { - Adapters, - Request, - RequestAdapter, - RequestStatistic, - RequestStatistics, - RequestStatus, - RequestResponder, -} from './adapters'; +export type { Adapters, Request, RequestStatistic, RequestStatistics } from './adapters'; +export { RequestAdapter, RequestStatus, RequestResponder } from './adapters'; diff --git a/src/plugins/inspector/public/index.ts b/src/plugins/inspector/public/index.ts index c611d13c06ca20..5ad7898550974b 100644 --- a/src/plugins/inspector/public/index.ts +++ b/src/plugins/inspector/public/index.ts @@ -18,6 +18,7 @@ export function plugin(initializerContext: PluginInitializerContext) { return new InspectorPublicPlugin(initializerContext); } -export { InspectorPublicPlugin as Plugin, Setup, Start } from './plugin'; +export type { Setup, Start } from './plugin'; +export { InspectorPublicPlugin as Plugin } from './plugin'; export * from './types'; export * from '../common/adapters'; diff --git a/src/plugins/interactive_setup/public/progress_indicator.tsx b/src/plugins/interactive_setup/public/progress_indicator.tsx index 73f757246af530..21bdcd2f786884 100644 --- a/src/plugins/interactive_setup/public/progress_indicator.tsx +++ b/src/plugins/interactive_setup/public/progress_indicator.tsx @@ -51,7 +51,7 @@ export const ProgressIndicator: FunctionComponent = ({ o } catch (error) { const { response, body = {} } = error as IHttpFetchError; isAvailable = response ? response.status < 500 : undefined; - isPastPreboot = isKibanaPastPreboot(response, body); + isPastPreboot = isKibanaPastPreboot(response, body as StatusResponse); } return isAvailable === true && isPastPreboot ? 'complete' diff --git a/src/plugins/interactive_setup/public/submit_error_callout.tsx b/src/plugins/interactive_setup/public/submit_error_callout.tsx index 728bbeff559de6..9622d08d48b867 100644 --- a/src/plugins/interactive_setup/public/submit_error_callout.tsx +++ b/src/plugins/interactive_setup/public/submit_error_callout.tsx @@ -11,7 +11,7 @@ import type { FunctionComponent } from 'react'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import type { IHttpFetchError } from 'kibana/public'; +import type { IHttpFetchError, ResponseErrorBody } from 'kibana/public'; import { ERROR_CONFIGURE_FAILURE, @@ -29,7 +29,7 @@ export interface SubmitErrorCalloutProps { } export const SubmitErrorCallout: FunctionComponent = (props) => { - const error = props.error as IHttpFetchError; + const error = props.error as IHttpFetchError; if ( error.body?.statusCode === 404 || diff --git a/src/plugins/interactive_setup/public/verification_code_form.tsx b/src/plugins/interactive_setup/public/verification_code_form.tsx index 0f2676a80364e7..9ff86c63aca7fa 100644 --- a/src/plugins/interactive_setup/public/verification_code_form.tsx +++ b/src/plugins/interactive_setup/public/verification_code_form.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import type { IHttpFetchError } from 'kibana/public'; +import type { IHttpFetchError, ResponseErrorBody } from 'kibana/public'; import { VERIFICATION_CODE_LENGTH } from '../common'; import { SingleCharsField } from './single_chars_field'; @@ -71,7 +71,7 @@ export const VerificationCodeForm: FunctionComponent }); } catch (error) { if ((error as IHttpFetchError).response?.status === 403) { - form.setError('code', (error as IHttpFetchError).body?.message); + form.setError('code', (error as IHttpFetchError).body?.message || ''); return; } else { throw error; diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts index ac78e8cac4f074..a154770bbfffda 100644 --- a/src/plugins/kibana_legacy/public/plugin.ts +++ b/src/plugins/kibana_legacy/public/plugin.ts @@ -6,16 +6,14 @@ * Side Public License, v 1. */ -import { CoreStart, CoreSetup } from 'kibana/public'; -import { injectHeaderStyle } from './utils/inject_header_style'; +import type { CoreSetup } from 'kibana/public'; export class KibanaLegacyPlugin { public setup(core: CoreSetup<{}, KibanaLegacyStart>) { return {}; } - public start({ uiSettings }: CoreStart) { - injectHeaderStyle(uiSettings); + public start() { return { /** * Loads the font-awesome icon font. Should be removed once the last consumer has migrated to EUI diff --git a/src/plugins/kibana_legacy/public/utils/inject_header_style.ts b/src/plugins/kibana_legacy/public/utils/inject_header_style.ts deleted file mode 100644 index 967aa2232838ee..00000000000000 --- a/src/plugins/kibana_legacy/public/utils/inject_header_style.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { IUiSettingsClient } from 'kibana/public'; - -export function buildCSS(maxHeight = 0, truncateGradientHeight = 15) { - return ` -.truncate-by-height { - max-height: ${maxHeight > 0 ? `${maxHeight}px !important` : 'none'}; - display: inline-block; -} -.truncate-by-height:before { - top: ${maxHeight > 0 ? maxHeight - truncateGradientHeight : truncateGradientHeight * -1}px; -} -`; -} - -export function injectHeaderStyle(uiSettings: IUiSettingsClient) { - const style = document.createElement('style'); - style.setAttribute('id', 'style-compile'); - document.getElementsByTagName('head')[0].appendChild(style); - - uiSettings.get$('truncate:maxHeight').subscribe((value: number) => { - // eslint-disable-next-line no-unsanitized/property - style.innerHTML = buildCSS(value); - }); -} diff --git a/src/plugins/kibana_overview/README.md b/src/plugins/kibana_overview/README.md index ad0cbfdf7013b7..4681a3453fecdc 100644 --- a/src/plugins/kibana_overview/README.md +++ b/src/plugins/kibana_overview/README.md @@ -6,4 +6,4 @@ ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/kibana_overview/public/index.ts b/src/plugins/kibana_overview/public/index.ts index eae86edf1d426a..aef16587e3de56 100644 --- a/src/plugins/kibana_overview/public/index.ts +++ b/src/plugins/kibana_overview/public/index.ts @@ -15,4 +15,4 @@ import { KibanaOverviewPlugin } from './plugin'; export function plugin() { return new KibanaOverviewPlugin(); } -export { KibanaOverviewPluginSetup, KibanaOverviewPluginStart } from './types'; +export type { KibanaOverviewPluginSetup, KibanaOverviewPluginStart } from './types'; diff --git a/src/plugins/kibana_react/public/context/index.ts b/src/plugins/kibana_react/public/context/index.ts index b34951b298836e..8647a1414b9dd7 100644 --- a/src/plugins/kibana_react/public/context/index.ts +++ b/src/plugins/kibana_react/public/context/index.ts @@ -13,4 +13,4 @@ export { useKibana, withKibana, } from './context'; -export { KibanaReactContext, KibanaReactContextValue, KibanaServices } from './types'; +export type { KibanaReactContext, KibanaReactContextValue, KibanaServices } from './types'; diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx index d2d7cbc2f570c9..16466f5c0f6a24 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { ExitFullScreenButton, ExitFullScreenButtonProps } from './exit_full_screen_button'; +export type { ExitFullScreenButtonProps } from './exit_full_screen_button'; +export { ExitFullScreenButton } from './exit_full_screen_button'; diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 6fccb804c357fe..03e2bb5f9c272c 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -23,7 +23,8 @@ export * from './toolbar_button'; export * from './split_panel'; export * from './react_router_navigate'; export * from './page_template'; -export { ValidatedDualRange, Value } from './validated_range'; +export type { Value } from './validated_range'; +export { ValidatedDualRange } from './validated_range'; export * from './notifications'; export { Markdown, MarkdownSimple } from './markdown'; export { reactToUiComponent, uiToReactComponent } from './adapters'; diff --git a/src/plugins/kibana_react/public/page_template/index.ts b/src/plugins/kibana_react/public/page_template/index.ts index 193dc8cd07eee7..41eeaab01ef39c 100644 --- a/src/plugins/kibana_react/public/page_template/index.ts +++ b/src/plugins/kibana_react/public/page_template/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ -export { KibanaPageTemplate, KibanaPageTemplateProps } from './page_template'; +export type { KibanaPageTemplateProps } from './page_template'; +export { KibanaPageTemplate } from './page_template'; export { KibanaPageTemplateSolutionNavAvatar } from './solution_nav'; export * from './no_data_page'; diff --git a/src/plugins/kibana_react/public/page_template/solution_nav/index.ts b/src/plugins/kibana_react/public/page_template/solution_nav/index.ts index 3dace6524fef50..81c2033a7ce7cf 100644 --- a/src/plugins/kibana_react/public/page_template/solution_nav/index.ts +++ b/src/plugins/kibana_react/public/page_template/solution_nav/index.ts @@ -6,12 +6,9 @@ * Side Public License, v 1. */ -export { KibanaPageTemplateSolutionNav, KibanaPageTemplateSolutionNavProps } from './solution_nav'; -export { - KibanaPageTemplateSolutionNavAvatar, - KibanaPageTemplateSolutionNavAvatarProps, -} from './solution_nav_avatar'; -export { - KibanaPageTemplateSolutionNavCollapseButton, - KibanaPageTemplateSolutionNavCollapseButtonProps, -} from './solution_nav_collapse_button'; +export type { KibanaPageTemplateSolutionNavProps } from './solution_nav'; +export { KibanaPageTemplateSolutionNav } from './solution_nav'; +export type { KibanaPageTemplateSolutionNavAvatarProps } from './solution_nav_avatar'; +export { KibanaPageTemplateSolutionNavAvatar } from './solution_nav_avatar'; +export type { KibanaPageTemplateSolutionNavCollapseButtonProps } from './solution_nav_collapse_button'; +export { KibanaPageTemplateSolutionNavCollapseButton } from './solution_nav_collapse_button'; diff --git a/src/plugins/kibana_react/public/toolbar_button/toolbar_button.scss b/src/plugins/kibana_react/public/toolbar_button/toolbar_button.scss index 0b5152bd99bbfe..f5cc1e53f24f90 100644 --- a/src/plugins/kibana_react/public/toolbar_button/toolbar_button.scss +++ b/src/plugins/kibana_react/public/toolbar_button/toolbar_button.scss @@ -5,10 +5,8 @@ // todo: once issue https://github.com/elastic/eui/issues/4730 is merged, this code might be safe to remove // Some toolbar buttons are just icons, but EuiButton comes with margin and min-width that need to be removed min-width: 0; - @include kbnThemeStyle('v8') { - border-width: $euiBorderWidthThin; - border-style: solid; - } + border-width: $euiBorderWidthThin; + border-style: solid; &[class*='--text'] { // Lighten the border color for all states diff --git a/src/plugins/kibana_react/public/validated_range/index.ts b/src/plugins/kibana_react/public/validated_range/index.ts index d5ecfd23382b8f..14f4333cde2400 100644 --- a/src/plugins/kibana_react/public/validated_range/index.ts +++ b/src/plugins/kibana_react/public/validated_range/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { ValidatedDualRange, Value } from './validated_dual_range'; +export type { Value } from './validated_dual_range'; +export { ValidatedDualRange } from './validated_dual_range'; diff --git a/src/plugins/kibana_usage_collection/kibana.json b/src/plugins/kibana_usage_collection/kibana.json index 60607c31af36b9..39b55e5c6dd946 100644 --- a/src/plugins/kibana_usage_collection/kibana.json +++ b/src/plugins/kibana_usage_collection/kibana.json @@ -1,7 +1,7 @@ { "id": "kibanaUsageCollection", "owner": { - "name": "Kibana Telemtry", + "name": "Kibana Telemetry", "githubTeam": "kibana-telemetry" }, "version": "kibana", diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index be00a13715fc79..365ff52ac9d4b3 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -16,7 +16,8 @@ export * from './ui'; export * from './state_containers'; export * from './errors'; export { AbortError, abortSignalToPromise } from './abort_utils'; -export { createGetterSetter, Get, Set } from './create_getter_setter'; +export type { Get, Set } from './create_getter_setter'; +export { createGetterSetter } from './create_getter_setter'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; export { url } from './url'; export { now } from './now'; diff --git a/src/plugins/kibana_utils/common/state_containers/README.md b/src/plugins/kibana_utils/common/state_containers/README.md index c623e8b3064387..1e9f60e41c729a 100644 --- a/src/plugins/kibana_utils/common/state_containers/README.md +++ b/src/plugins/kibana_utils/common/state_containers/README.md @@ -1,2 +1,2 @@ * [docs](../../docs/state_containers) -* [api reference](https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers) \ No newline at end of file +* [api reference](https://github.com/elastic/kibana/tree/main/src/plugins/kibana_utils/docs/state_containers) \ No newline at end of file diff --git a/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts index 841cf8b87a5652..7de28363c970dd 100644 --- a/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts +++ b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts @@ -63,7 +63,7 @@ export const useContainerSelector = , /** * Creates helpers for using {@link StateContainer | State Containers} with react - * Refer to {@link https://github.com/elastic/kibana/blob/master/src/plugins/kibana_utils/docs/state_containers/react.md | guide} for details + * Refer to {@link https://github.com/elastic/kibana/blob/main/src/plugins/kibana_utils/docs/state_containers/react.md | guide} for details * @public */ export const createStateContainerReactHelpers = >() => { diff --git a/src/plugins/kibana_utils/common/state_containers/index.ts b/src/plugins/kibana_utils/common/state_containers/index.ts index 34032147cc171a..8c4d3f87a0681b 100644 --- a/src/plugins/kibana_utils/common/state_containers/index.ts +++ b/src/plugins/kibana_utils/common/state_containers/index.ts @@ -8,12 +8,12 @@ /** * State containers are Redux-store-like objects meant to help you manage state in your services or apps. - * Refer to {@link https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers | guides and examples} for more info + * Refer to {@link https://github.com/elastic/kibana/tree/main/src/plugins/kibana_utils/docs/state_containers | guides and examples} for more info * * @packageDocumentation */ -export { +export type { BaseState, BaseStateContainer, TransitionDescription, @@ -37,7 +37,8 @@ export { PureTransition, Transition, } from './types'; -export { createStateContainer, CreateStateContainerOptions } from './create_state_container'; +export type { CreateStateContainerOptions } from './create_state_container'; +export { createStateContainer } from './create_state_container'; export { createStateContainerReactHelpers, useContainerSelector, diff --git a/src/plugins/kibana_utils/index.ts b/src/plugins/kibana_utils/index.ts index ecffc0544c7db7..9b7e91b747db82 100644 --- a/src/plugins/kibana_utils/index.ts +++ b/src/plugins/kibana_utils/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { createStateContainer, StateContainer, of } from './common'; +export type { StateContainer } from './common'; +export { createStateContainer, of } from './common'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 0ac4c61f4a7119..090c33e1213706 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -12,6 +12,7 @@ import { KibanaUtilsPublicPlugin } from './plugin'; // TODO: https://github.com/elastic/kibana/issues/109893 /* eslint-disable @kbn/eslint/no_export_all */ +export type { Get, Set, UiComponent, UiComponentInstance } from '../common'; export { AbortError, abortSignalToPromise, @@ -20,11 +21,7 @@ export { Defer, fieldWildcardFilter, fieldWildcardMatcher, - Get, of, - Set, - UiComponent, - UiComponentInstance, url, createGetterSetter, } from '../common'; @@ -56,11 +53,7 @@ export { replaceUrlQuery, replaceUrlHashQuery, } from './state_management/url'; -export { - syncState, - syncStates, - createKbnUrlStateStorage, - createSessionStorageStateStorage, +export type { IStateSyncConfig, ISyncStateRef, IKbnUrlStateStorage, @@ -69,7 +62,13 @@ export { StartSyncStateFnType, StopSyncStateFnType, } from './state_sync'; -export { Configurable, CollectConfigProps } from './ui'; +export { + syncState, + syncStates, + createKbnUrlStateStorage, + createSessionStorageStateStorage, +} from './state_sync'; +export type { Configurable, CollectConfigProps } from './ui'; export { removeQueryParam, redirectWhenMissing, @@ -79,9 +78,10 @@ export { createQueryParamObservable, } from './history'; export { applyDiff } from './state_management/utils/diff_object'; -export { createStartServicesGetter, StartServicesGetter } from './core/create_start_service_getter'; +export type { StartServicesGetter } from './core/create_start_service_getter'; +export { createStartServicesGetter } from './core/create_start_service_getter'; -export { KibanaUtilsSetup } from './plugin'; +export type { KibanaUtilsSetup } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new KibanaUtilsPublicPlugin(initializerContext); diff --git a/src/plugins/kibana_utils/public/state_management/url/index.ts b/src/plugins/kibana_utils/public/state_management/url/index.ts index b180232b215263..7f39e9ac1b6982 100644 --- a/src/plugins/kibana_utils/public/state_management/url/index.ts +++ b/src/plugins/kibana_utils/public/state_management/url/index.ts @@ -7,12 +7,12 @@ */ export { hashUrl, hashQuery, unhashUrl, unhashQuery } from './hash_unhash_url'; +export type { IKbnUrlControls } from './kbn_url_storage'; export { createKbnUrlControls, setStateToKbnUrl, getStateFromKbnUrl, getStatesFromKbnUrl, - IKbnUrlControls, } from './kbn_url_storage'; export { createKbnUrlTracker } from './kbn_url_tracker'; export { createUrlTracker } from './url_tracker'; diff --git a/src/plugins/kibana_utils/public/state_sync/README.md b/src/plugins/kibana_utils/public/state_sync/README.md index eb5f6e60958fc4..5003246c128573 100644 --- a/src/plugins/kibana_utils/public/state_sync/README.md +++ b/src/plugins/kibana_utils/public/state_sync/README.md @@ -1,3 +1,3 @@ - [docs](../../docs/state_sync) - [demo plugins](../../../../../examples/state_containers_examples): run Kibana with `--run-examples` flag. -- [api reference](https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_sync) +- [api reference](https://github.com/elastic/kibana/tree/main/src/plugins/kibana_utils/docs/state_sync) diff --git a/src/plugins/kibana_utils/public/state_sync/index.ts b/src/plugins/kibana_utils/public/state_sync/index.ts index 28a774bf341bf1..603e7582c7b169 100644 --- a/src/plugins/kibana_utils/public/state_sync/index.ts +++ b/src/plugins/kibana_utils/public/state_sync/index.ts @@ -10,7 +10,7 @@ * State syncing utilities are a set of helpers for syncing your application state * with browser URL or browser storage. * - * They are designed to work together with {@link https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers | state containers}. But state containers are not required. + * They are designed to work together with {@link https://github.com/elastic/kibana/tree/main/src/plugins/kibana_utils/docs/state_containers | state containers}. But state containers are not required. * * State syncing utilities include: * @@ -22,22 +22,19 @@ * Listens for state updates in the URL and pushes them back to state. * * {@link ISessionStorageStateStorage} - Serializes state and persists it to browser storage. * - * Refer {@link https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_sync | here} for a complete guide and examples. + * Refer {@link https://github.com/elastic/kibana/tree/main/src/plugins/kibana_utils/docs/state_sync | here} for a complete guide and examples. * @packageDocumentation */ -export { - createSessionStorageStateStorage, - createKbnUrlStateStorage, +export type { IKbnUrlStateStorage, ISessionStorageStateStorage, IStateStorage, } from './state_sync_state_storage'; -export { IStateSyncConfig, INullableBaseStateContainer } from './types'; export { - syncState, - syncStates, - StopSyncStateFnType, - StartSyncStateFnType, - ISyncStateRef, -} from './state_sync'; + createSessionStorageStateStorage, + createKbnUrlStateStorage, +} from './state_sync_state_storage'; +export type { IStateSyncConfig, INullableBaseStateContainer } from './types'; +export type { StopSyncStateFnType, StartSyncStateFnType, ISyncStateRef } from './state_sync'; +export { syncState, syncStates } from './state_sync'; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.ts index 8cfe0fe0fe9671..2c3141e76d712e 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.ts @@ -42,7 +42,7 @@ export interface ISyncStateRef + {upperFirst(props.label || props.id!)} ) : ( diff --git a/src/plugins/newsfeed/public/index.ts b/src/plugins/newsfeed/public/index.ts index 324306f889d15a..f9d59ca6d99dd3 100644 --- a/src/plugins/newsfeed/public/index.ts +++ b/src/plugins/newsfeed/public/index.ts @@ -15,13 +15,8 @@ import { import { FetchResult, NewsfeedItem } from './types'; import { NewsfeedApiEndpoint } from './lib/api'; -export { - NewsfeedPublicPluginSetup, - NewsfeedPublicPluginStart, - FetchResult, - NewsfeedItem, - NewsfeedApiEndpoint, -}; +export type { NewsfeedPublicPluginSetup, NewsfeedPublicPluginStart, FetchResult, NewsfeedItem }; +export { NewsfeedApiEndpoint }; export function plugin(initializerContext: PluginInitializerContext) { return new NewsfeedPublicPlugin(initializerContext); diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_component.tsx b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_component.tsx index 16ae4c1858660a..22b97b6f6b0dad 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_component.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_component.tsx @@ -108,11 +108,15 @@ export const ControlGroup = () => { return null; } + let panelBg: 'subdued' | 'primary' | 'success' = 'subdued'; + if (emptyState) panelBg = 'primary'; + if (draggingId) panelBg = 'success'; + return ( { {idsInOrder.map( diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx index 3ec553e5aa7d10..0ef9c4b7f115af 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx @@ -116,11 +116,15 @@ export const ControlClone = ({ draggingId }: { draggingId: string }) => { })} > {controlStyle === 'twoLine' ? {title} : undefined} - + - {controlStyle === 'oneLine' ? {title} : undefined} + {controlStyle === 'oneLine' ? ( + + + + ) : undefined} ); diff --git a/src/plugins/presentation_util/public/components/controls/control_group/control_group.scss b/src/plugins/presentation_util/public/components/controls/control_group/control_group.scss index c69674df296160..e595bc245e31d9 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/control_group.scss +++ b/src/plugins/presentation_util/public/components/controls/control_group/control_group.scss @@ -4,7 +4,6 @@ $largeControl: $euiSize * 50; $controlMinWidth: $euiSize * 14; .controlGroup { - overflow-x: clip; // sometimes when using auto width, removing a control can cause a horizontal scrollbar to appear. min-height: $euiSize * 4; } @@ -40,10 +39,6 @@ $controlMinWidth: $euiSize * 14; .controlFrameCloneWrapper { width: max-content; - .euiFormLabel { - padding-bottom: $euiSizeXS; - } - &--small { width: $smallControl; } @@ -60,7 +55,7 @@ $controlMinWidth: $euiSize * 14; margin-top: -$euiSize * 1.25; } - .euiFormLabel, div { + &__label { cursor: grabbing !important; // prevents cursor flickering while dragging the clone } @@ -69,20 +64,14 @@ $controlMinWidth: $euiSize * 14; height: $euiButtonHeight; align-items: center; border-radius: $euiBorderRadius; - @include euiFontSizeS; font-weight: $euiFontWeightSemiBold; @include euiFormControlDefaultShadow; background-color: $euiFormInputGroupLabelBackground; min-width: $controlMinWidth; + @include euiFontSizeXS; } .controlFrame__formControlLayout, .controlFrame__draggable { - &-clone { - box-shadow: 0 0 0 1px $euiShadowColor, - 0 1px 6px 0 $euiShadowColor; - cursor: grabbing !important; - } - .controlFrame__dragHandle { cursor: grabbing; } @@ -92,7 +81,6 @@ $controlMinWidth: $euiSize * 14; .controlFrameWrapper { flex-basis: auto; position: relative; - display: block; .controlFrame__formControlLayout { width: 100%; @@ -102,6 +90,7 @@ $controlMinWidth: $euiSize * 14; &Label { @include euiTextTruncate; max-width: 50%; + border-radius: $euiBorderRadius; } &:not(.controlFrame__formControlLayout-clone) { @@ -148,19 +137,20 @@ $controlMinWidth: $euiSize * 14; border-radius: $euiBorderRadius; top: 0; bottom: 0; - width: 2px; + width: $euiSizeXS * .5; } } &--insertBefore { .controlFrame__formControlLayout:after { - left: -$euiSizeS; + left: -$euiSizeXS - 1; + } } &--insertAfter { .controlFrame__formControlLayout:after { - right: -$euiSizeS; + right: -$euiSizeXS - 1; } } @@ -202,7 +192,7 @@ $controlMinWidth: $euiSize * 14; opacity: 0; } .controlFrame__formControlLayout { - background-color: $euiColorEmptyShade !important; + background-color: tintOrShade($euiColorSuccess, 90%, 70%); color: transparent !important; box-shadow: none; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/control_editor.tsx b/src/plugins/presentation_util/public/components/controls/control_group/editor/control_editor.tsx index 0fdcba570c9414..c31a8397fa2330 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/editor/control_editor.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_group/editor/control_editor.tsx @@ -88,19 +88,6 @@ export const ControlEditor = ({ - - { - setCurrentWidth(newWidth as ControlWidth); - updateWidth(newWidth as ControlWidth); - }} - /> - - {ControlTypeEditor && ( + + { + setCurrentWidth(newWidth as ControlWidth); + updateWidth(newWidth as ControlWidth); + }} + /> + {removeControl && ( setIsPopoverOpen(false)} panelPaddingSize="none" anchorPosition="downCenter" diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_editor.tsx b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_editor.tsx index 86623980cfc410..86f4f85b3b0b25 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_editor.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_editor.tsx @@ -70,7 +70,7 @@ export const OptionsListEditor = ({ const { dataView, fieldName } = state; return ( <> - + (); const dispatch = useEmbeddableDispatch(); - const { selectedOptions, singleSelect } = useEmbeddableSelector((state) => state); + const { selectedOptions, singleSelect, title } = useEmbeddableSelector((state) => state); // track selectedOptions in a set for more efficient lookup const selectedOptionsSet = useMemo(() => new Set(selectedOptions), [selectedOptions]); @@ -53,7 +53,8 @@ export const OptionsListPopover = ({ return ( <> - + {title} +
@@ -100,9 +101,8 @@ export const OptionsListPopover = ({ - - -
+
+
{!showOnlySelected && ( <> {availableOptions?.map((availableOption, index) => ( diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_strings.ts b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_strings.ts index 52b5dc6d449103..dee0d4e7e18073 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_strings.ts +++ b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_strings.ts @@ -24,6 +24,10 @@ export const OptionsListStrings = { i18n.translate('presentationUtil.controls.optionsList.editor.indexPatternTitle', { defaultMessage: 'Index pattern', }), + getDataViewTitle: () => + i18n.translate('presentationUtil.controls.optionsList.editor.dataViewTitle', { + defaultMessage: 'Data view', + }), getNoDataViewTitle: () => i18n.translate('presentationUtil.controls.optionsList.editor.noDataViewTitle', { defaultMessage: 'Select data view', diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx index 237a9666deb30f..7b285944840c88 100644 --- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx +++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx @@ -67,7 +67,7 @@ export function DataViewPicker({ panelPaddingSize="s" ownFocus > -
+
{i18n.translate('presentationUtil.dataViewPicker.changeDataViewTitle', { defaultMessage: 'Data view', diff --git a/src/plugins/presentation_util/public/components/field_picker/field_picker.scss b/src/plugins/presentation_util/public/components/field_picker/field_picker.scss index eac1979fd003ab..a06c469e713bf9 100644 --- a/src/plugins/presentation_util/public/components/field_picker/field_picker.scss +++ b/src/plugins/presentation_util/public/components/field_picker/field_picker.scss @@ -1,7 +1,5 @@ .presFieldPicker__fieldButton { - box-shadow: 0 .8px .8px rgba(0,0,0,.04),0 2.3px 2px rgba(0,0,0,.03); - background: #FFF; - border: 1px dashed transparent; + background: $euiColorEmptyShade; } .presFieldPickerFieldButtonActive { diff --git a/src/plugins/presentation_util/public/components/field_picker/field_search.tsx b/src/plugins/presentation_util/public/components/field_picker/field_search.tsx index d3c6c728b3d08e..f3988167c1317e 100644 --- a/src/plugins/presentation_util/public/components/field_picker/field_search.tsx +++ b/src/plugins/presentation_util/public/components/field_picker/field_search.tsx @@ -19,6 +19,7 @@ import { EuiOutsideClickDetector, EuiFilterButton, EuiSpacer, + EuiPopoverTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { FieldIcon } from '../../../../kibana_react/public'; @@ -87,7 +88,6 @@ export function FieldSearch({ { @@ -95,6 +95,11 @@ export function FieldSearch({ }} button={buttonContent} > + + {i18n.translate('presentationUtil.fieldSearch.filterByTypeLabel', { + defaultMessage: 'Filter by type', + })} + ( @@ -110,10 +115,12 @@ export function FieldSearch({ } }} > - - - {type} - + + + + + {type} + ))} /> diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss index a1e5b4e1417652..4283813f1d0b7c 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss @@ -5,10 +5,8 @@ // Lighten the border color for all states border-color: $euiBorderColor !important; // sass-lint:disable-line no-important - @include kbnThemeStyle('v8') { - &[class*='--text'] { - border-width: $euiBorderWidthThin; - border-style: solid; - } + &[class*='--text'] { + border-width: $euiBorderWidthThin; + border-style: solid; } } diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/index.ts b/src/plugins/presentation_util/public/components/solution_toolbar/items/index.ts index 654831e86d3f68..6076dbf8cf1235 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/index.ts +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/index.ts @@ -9,6 +9,7 @@ export { SolutionToolbarButton } from './button'; export { SolutionToolbarPopover } from './popover'; export { AddFromLibraryButton } from './add_from_library'; -export { QuickButtonProps, QuickButtonGroup } from './quick_group'; +export type { QuickButtonProps } from './quick_group'; +export { QuickButtonGroup } from './quick_group'; export { PrimaryActionButton } from './primary_button'; export { PrimaryActionPopover } from './primary_popover'; diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss index 535570a51d777c..434f06b69a6847 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss @@ -1,11 +1,9 @@ .quickButtonGroup { .quickButtonGroup__button { background-color: $euiColorEmptyShade; - @include kbnThemeStyle('v8') { - // sass-lint:disable-block no-important - border-width: $euiBorderWidthThin !important; - border-style: solid !important; - border-color: $euiBorderColor !important; - } + // sass-lint:disable-block no-important + border-width: $euiBorderWidthThin !important; + border-style: solid !important; + border-color: $euiBorderColor !important; } } diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index 478e8a7cda032b..91f1e3a937f62f 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -11,26 +11,25 @@ import { PresentationUtilPlugin } from './plugin'; -export { +export type { PresentationCapabilitiesService, PresentationDashboardsService, PresentationLabsService, - getStubPluginServices, } from './services'; +export { getStubPluginServices } from './services'; -export { +export type { KibanaPluginServiceFactory, PluginServiceFactory, - PluginServices, PluginServiceProviders, - PluginServiceProvider, - PluginServiceRegistry, KibanaPluginServiceParams, } from './services/create'; +export { PluginServices, PluginServiceProvider, PluginServiceRegistry } from './services/create'; -export { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types'; -export { SaveModalDashboardProps } from './components/types'; -export { projectIDs, ProjectID, Project } from '../common/labs'; +export type { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types'; +export type { SaveModalDashboardProps } from './components/types'; +export type { ProjectID, Project } from '../common/labs'; +export { projectIDs } from '../common/labs'; export * from '../common/lib'; export { @@ -43,12 +42,12 @@ export { export * from './components/types'; +export type { QuickButtonProps } from './components/solution_toolbar'; export { AddFromLibraryButton, PrimaryActionButton, PrimaryActionPopover, QuickButtonGroup, - QuickButtonProps, SolutionToolbar, SolutionToolbarButton, SolutionToolbarPopover, diff --git a/src/plugins/presentation_util/public/services/create/index.ts b/src/plugins/presentation_util/public/services/create/index.ts index 163e25e26babfa..d616d7bee20c89 100644 --- a/src/plugins/presentation_util/public/services/create/index.ts +++ b/src/plugins/presentation_util/public/services/create/index.ts @@ -9,8 +9,9 @@ import { PluginServiceRegistry } from './registry'; export { PluginServiceRegistry } from './registry'; -export { PluginServiceProvider, PluginServiceProviders } from './provider'; -export { +export type { PluginServiceProviders } from './provider'; +export { PluginServiceProvider } from './provider'; +export type { PluginServiceFactory, KibanaPluginServiceFactory, KibanaPluginServiceParams, diff --git a/src/plugins/presentation_util/public/services/index.ts b/src/plugins/presentation_util/public/services/index.ts index c7d8d2617888a2..cafb01594bd3c7 100644 --- a/src/plugins/presentation_util/public/services/index.ts +++ b/src/plugins/presentation_util/public/services/index.ts @@ -17,9 +17,9 @@ import { PresentationControlsService } from './controls'; import { PresentationDataViewsService } from './data_views'; import { PresentationDataService } from './data'; -export { PresentationCapabilitiesService } from './capabilities'; -export { PresentationDashboardsService } from './dashboards'; -export { PresentationLabsService } from './labs'; +export type { PresentationCapabilitiesService } from './capabilities'; +export type { PresentationDashboardsService } from './dashboards'; +export type { PresentationLabsService } from './labs'; export interface PresentationUtilServices { dashboards: PresentationDashboardsService; dataViews: PresentationDataViewsService; diff --git a/src/plugins/presentation_util/public/services/storybook/index.ts b/src/plugins/presentation_util/public/services/storybook/index.ts index 1639316a1fe19f..a2d729f6d730a4 100644 --- a/src/plugins/presentation_util/public/services/storybook/index.ts +++ b/src/plugins/presentation_util/public/services/storybook/index.ts @@ -21,8 +21,9 @@ import { controlsServiceFactory } from './controls'; import { dataViewsServiceFactory } from './data_views'; import { dataServiceFactory } from './data'; -export { PluginServiceProviders, PluginServiceProvider, PluginServiceRegistry } from '../create'; -export { PresentationUtilServices } from '..'; +export type { PluginServiceProviders } from '../create'; +export { PluginServiceProvider, PluginServiceRegistry } from '../create'; +export type { PresentationUtilServices } from '..'; export interface StorybookParams { canAccessDashboards?: boolean; diff --git a/src/plugins/presentation_util/storybook/manager.ts b/src/plugins/presentation_util/storybook/manager.ts index 0c7774fd00762a..e38cbf418bc185 100644 --- a/src/plugins/presentation_util/storybook/manager.ts +++ b/src/plugins/presentation_util/storybook/manager.ts @@ -14,7 +14,7 @@ addons.setConfig({ theme: create({ base: 'light', brandTitle: 'Kibana Presentation Utility Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/master/src/plugins/presentation_util', + brandUrl: 'https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util', }), showPanel: true.valueOf, selectedPanel: PANEL_ID, diff --git a/src/plugins/saved_objects/public/finder/index.ts b/src/plugins/saved_objects/public/finder/index.ts index de6a54795fce51..aaf6259daca1d6 100644 --- a/src/plugins/saved_objects/public/finder/index.ts +++ b/src/plugins/saved_objects/public/finder/index.ts @@ -6,9 +6,5 @@ * Side Public License, v 1. */ -export { - SavedObjectMetaData, - SavedObjectFinderUi, - SavedObjectFinderUiProps, - getSavedObjectFinder, -} from './saved_object_finder'; +export type { SavedObjectMetaData, SavedObjectFinderUiProps } from './saved_object_finder'; +export { SavedObjectFinderUi, getSavedObjectFinder } from './saved_object_finder'; diff --git a/src/plugins/saved_objects/public/index.ts b/src/plugins/saved_objects/public/index.ts index bc84298a63717f..d63e20f5f56735 100644 --- a/src/plugins/saved_objects/public/index.ts +++ b/src/plugins/saved_objects/public/index.ts @@ -8,33 +8,24 @@ import { SavedObjectsPublicPlugin } from './plugin'; -export { - OnSaveProps, - SavedObjectSaveModal, - SavedObjectSaveModalOrigin, - OriginSaveModalProps, - SaveModalState, - SaveResult, - showSaveModal, -} from './save_modal'; -export { - getSavedObjectFinder, - SavedObjectFinderUi, - SavedObjectFinderUiProps, - SavedObjectMetaData, -} from './finder'; +export type { OnSaveProps, OriginSaveModalProps, SaveModalState, SaveResult } from './save_modal'; +export { SavedObjectSaveModal, SavedObjectSaveModalOrigin, showSaveModal } from './save_modal'; +export type { SavedObjectFinderUiProps, SavedObjectMetaData } from './finder'; +export { getSavedObjectFinder, SavedObjectFinderUi } from './finder'; +export type { + SavedObjectLoaderFindOptions, + SavedObjectDecorator, + SavedObjectDecoratorFactory, + SavedObjectDecoratorConfig, +} from './saved_object'; export { SavedObjectLoader, - SavedObjectLoaderFindOptions, checkForDuplicateTitle, saveWithConfirmation, isErrorNonFatal, - SavedObjectDecorator, - SavedObjectDecoratorFactory, - SavedObjectDecoratorConfig, } from './saved_object'; -export { SavedObjectSaveOpts, SavedObject, SavedObjectConfig } from './types'; +export type { SavedObjectSaveOpts, SavedObject, SavedObjectConfig } from './types'; export { PER_PAGE_SETTING, LISTING_LIMIT_SETTING } from '../common'; -export { SavedObjectsStart, SavedObjectSetup } from './plugin'; +export type { SavedObjectsStart, SavedObjectSetup } from './plugin'; export const plugin = () => new SavedObjectsPublicPlugin(); diff --git a/src/plugins/saved_objects/public/save_modal/index.ts b/src/plugins/saved_objects/public/save_modal/index.ts index cd10374e573438..8c23b797a05db1 100644 --- a/src/plugins/saved_objects/public/save_modal/index.ts +++ b/src/plugins/saved_objects/public/save_modal/index.ts @@ -6,6 +6,9 @@ * Side Public License, v 1. */ -export { SavedObjectSaveModal, OnSaveProps, SaveModalState } from './saved_object_save_modal'; -export { SavedObjectSaveModalOrigin, OriginSaveModalProps } from './saved_object_save_modal_origin'; -export { showSaveModal, SaveResult } from './show_saved_object_save_modal'; +export type { OnSaveProps, SaveModalState } from './saved_object_save_modal'; +export { SavedObjectSaveModal } from './saved_object_save_modal'; +export type { OriginSaveModalProps } from './saved_object_save_modal_origin'; +export { SavedObjectSaveModalOrigin } from './saved_object_save_modal_origin'; +export type { SaveResult } from './show_saved_object_save_modal'; +export { showSaveModal } from './show_saved_object_save_modal'; diff --git a/src/plugins/saved_objects/public/saved_object/decorators/index.ts b/src/plugins/saved_objects/public/saved_object/decorators/index.ts index d7aed32b5e2e77..1b43ae9808bf75 100644 --- a/src/plugins/saved_objects/public/saved_object/decorators/index.ts +++ b/src/plugins/saved_objects/public/saved_object/decorators/index.ts @@ -6,9 +6,6 @@ * Side Public License, v 1. */ -export { - ISavedObjectDecoratorRegistry, - SavedObjectDecoratorRegistry, - SavedObjectDecoratorConfig, -} from './registry'; -export { SavedObjectDecorator, SavedObjectDecoratorFactory } from './types'; +export type { ISavedObjectDecoratorRegistry, SavedObjectDecoratorConfig } from './registry'; +export { SavedObjectDecoratorRegistry } from './registry'; +export type { SavedObjectDecorator, SavedObjectDecoratorFactory } from './types'; diff --git a/src/plugins/saved_objects/public/saved_object/helpers/field_mapping/index.ts b/src/plugins/saved_objects/public/saved_object/helpers/field_mapping/index.ts index b8a7b7ecc31a1e..9a52f9b0e8458a 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/field_mapping/index.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/field_mapping/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { FieldMappingSpec, MappingObject } from './types'; +export type { FieldMappingSpec, MappingObject } from './types'; export { expandShorthand } from './mapping_setup'; diff --git a/src/plugins/saved_objects/public/saved_object/index.ts b/src/plugins/saved_objects/public/saved_object/index.ts index 116999afb71aed..f30730a1c39ac9 100644 --- a/src/plugins/saved_objects/public/saved_object/index.ts +++ b/src/plugins/saved_objects/public/saved_object/index.ts @@ -7,13 +7,14 @@ */ export { createSavedObjectClass } from './saved_object'; -export { SavedObjectLoader, SavedObjectLoaderFindOptions } from './saved_object_loader'; +export type { SavedObjectLoaderFindOptions } from './saved_object_loader'; +export { SavedObjectLoader } from './saved_object_loader'; export { checkForDuplicateTitle } from './helpers/check_for_duplicate_title'; export { saveWithConfirmation } from './helpers/save_with_confirmation'; export { isErrorNonFatal } from './helpers/save_saved_object'; -export { - SavedObjectDecoratorRegistry, +export type { SavedObjectDecoratorFactory, SavedObjectDecorator, SavedObjectDecoratorConfig, } from './decorators'; +export { SavedObjectDecoratorRegistry } from './decorators'; diff --git a/src/plugins/saved_objects_management/public/index.ts b/src/plugins/saved_objects_management/public/index.ts index 92e01ab903699a..99ffae349bb487 100644 --- a/src/plugins/saved_objects_management/public/index.ts +++ b/src/plugins/saved_objects_management/public/index.ts @@ -9,18 +9,22 @@ import { PluginInitializerContext } from 'kibana/public'; import { SavedObjectsManagementPlugin } from './plugin'; -export { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart } from './plugin'; -export { +export type { + SavedObjectsManagementPluginSetup, + SavedObjectsManagementPluginStart, +} from './plugin'; +export type { SavedObjectsManagementActionServiceSetup, SavedObjectsManagementActionServiceStart, - SavedObjectsManagementAction, SavedObjectsManagementColumnServiceSetup, SavedObjectsManagementColumnServiceStart, SavedObjectsManagementColumn, SavedObjectsManagementRecord, } from './services'; -export { ProcessedImportResponse, processImportResponse, FailedImport } from './lib'; -export { SavedObjectRelation, SavedObjectWithMetadata, SavedObjectMetadata } from './types'; +export { SavedObjectsManagementAction } from './services'; +export type { ProcessedImportResponse, FailedImport } from './lib'; +export { processImportResponse } from './lib'; +export type { SavedObjectRelation, SavedObjectWithMetadata, SavedObjectMetadata } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new SavedObjectsManagementPlugin(); diff --git a/src/plugins/saved_objects_management/public/lib/index.ts b/src/plugins/saved_objects_management/public/lib/index.ts index e317bb5e39f735..258387c39ecd97 100644 --- a/src/plugins/saved_objects_management/public/lib/index.ts +++ b/src/plugins/saved_objects_management/public/lib/index.ts @@ -14,14 +14,12 @@ export { getSavedObjectLabel } from './get_saved_object_label'; export { importFile } from './import_file'; export { parseQuery } from './parse_query'; export { resolveImportErrors } from './resolve_import_errors'; -export { - processImportResponse, - ProcessedImportResponse, - FailedImport, -} from './process_import_response'; +export type { ProcessedImportResponse, FailedImport } from './process_import_response'; +export { processImportResponse } from './process_import_response'; export { getDefaultTitle } from './get_default_title'; export { findObjects } from './find_objects'; export { bulkGetObjects } from './bulk_get_objects'; -export { extractExportDetails, SavedObjectsExportResultDetails } from './extract_export_details'; +export type { SavedObjectsExportResultDetails } from './extract_export_details'; +export { extractExportDetails } from './extract_export_details'; export { getAllowedTypes } from './get_allowed_types'; export { getTagFindReferences } from './get_tag_references'; diff --git a/src/plugins/saved_objects_management/public/services/index.ts b/src/plugins/saved_objects_management/public/services/index.ts index f3c0100d615999..c45c81d3122adf 100644 --- a/src/plugins/saved_objects_management/public/services/index.ts +++ b/src/plugins/saved_objects_management/public/services/index.ts @@ -6,18 +6,15 @@ * Side Public License, v 1. */ -export { - SavedObjectsManagementActionService, +export type { SavedObjectsManagementActionServiceStart, SavedObjectsManagementActionServiceSetup, } from './action_service'; -export { - SavedObjectsManagementColumnService, +export { SavedObjectsManagementActionService } from './action_service'; +export type { SavedObjectsManagementColumnServiceStart, SavedObjectsManagementColumnServiceSetup, } from './column_service'; -export { - SavedObjectsManagementAction, - SavedObjectsManagementColumn, - SavedObjectsManagementRecord, -} from './types'; +export { SavedObjectsManagementColumnService } from './column_service'; +export type { SavedObjectsManagementColumn, SavedObjectsManagementRecord } from './types'; +export { SavedObjectsManagementAction } from './types'; diff --git a/src/plugins/saved_objects_management/public/services/types/index.ts b/src/plugins/saved_objects_management/public/services/types/index.ts index 457ade0a295e7c..82b45f9df33f08 100644 --- a/src/plugins/saved_objects_management/public/services/types/index.ts +++ b/src/plugins/saved_objects_management/public/services/types/index.ts @@ -7,5 +7,5 @@ */ export { SavedObjectsManagementAction } from './action'; -export { SavedObjectsManagementColumn } from './column'; -export { SavedObjectsManagementRecord } from './record'; +export type { SavedObjectsManagementColumn } from './column'; +export type { SavedObjectsManagementRecord } from './record'; diff --git a/src/plugins/saved_objects_management/public/types.ts b/src/plugins/saved_objects_management/public/types.ts index cd6e3e9eaa32c1..61766e1cb8c10b 100644 --- a/src/plugins/saved_objects_management/public/types.ts +++ b/src/plugins/saved_objects_management/public/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { +export type { SavedObjectMetadata, SavedObjectWithMetadata, SavedObjectRelationKind, diff --git a/src/plugins/saved_objects_management/server/index.ts b/src/plugins/saved_objects_management/server/index.ts index b56382113e1eaa..942d7b0734aee1 100644 --- a/src/plugins/saved_objects_management/server/index.ts +++ b/src/plugins/saved_objects_management/server/index.ts @@ -12,7 +12,7 @@ import { SavedObjectsManagementPlugin } from './plugin'; export const plugin = (context: PluginInitializerContext) => new SavedObjectsManagementPlugin(context); -export { +export type { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart, SavedObjectMetadata, diff --git a/src/plugins/saved_objects_management/server/services/index.ts b/src/plugins/saved_objects_management/server/services/index.ts index 7bee337f59a98c..f0f6d9e7fa9787 100644 --- a/src/plugins/saved_objects_management/server/services/index.ts +++ b/src/plugins/saved_objects_management/server/services/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { SavedObjectsManagement, ISavedObjectsManagement } from './management'; +export type { ISavedObjectsManagement } from './management'; +export { SavedObjectsManagement } from './management'; diff --git a/src/plugins/saved_objects_management/server/types.ts b/src/plugins/saved_objects_management/server/types.ts index 5779c6d98e35da..93f6f3d09547ab 100644 --- a/src/plugins/saved_objects_management/server/types.ts +++ b/src/plugins/saved_objects_management/server/types.ts @@ -12,7 +12,7 @@ export interface SavedObjectsManagementPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SavedObjectsManagementPluginStart {} -export { +export type { SavedObjectMetadata, SavedObjectWithMetadata, SavedObjectRelationKind, diff --git a/src/plugins/saved_objects_tagging_oss/common/index.ts b/src/plugins/saved_objects_tagging_oss/common/index.ts index a892f41c69314c..0a8bd890614b75 100644 --- a/src/plugins/saved_objects_tagging_oss/common/index.ts +++ b/src/plugins/saved_objects_tagging_oss/common/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { Tag, TagAttributes, GetAllTagsOptions, ITagsClient } from './types'; +export type { Tag, TagAttributes, GetAllTagsOptions, ITagsClient } from './types'; diff --git a/src/plugins/saved_objects_tagging_oss/public/decorator/index.ts b/src/plugins/saved_objects_tagging_oss/public/decorator/index.ts index 6ea905b1db245e..7ca7c50505f27e 100644 --- a/src/plugins/saved_objects_tagging_oss/public/decorator/index.ts +++ b/src/plugins/saved_objects_tagging_oss/public/decorator/index.ts @@ -10,7 +10,7 @@ import { SavedObjectDecoratorConfig } from '../../../saved_objects/public'; import { tagDecoratorFactory, decoratorId } from './factory'; import { InternalTagDecoratedSavedObject } from './types'; -export { TagDecoratedSavedObject } from './types'; +export type { TagDecoratedSavedObject } from './types'; export const tagDecoratorConfig: SavedObjectDecoratorConfig = { id: decoratorId, diff --git a/src/plugins/saved_objects_tagging_oss/public/index.ts b/src/plugins/saved_objects_tagging_oss/public/index.ts index eb38614f905399..6e9a0bf3d832e5 100644 --- a/src/plugins/saved_objects_tagging_oss/public/index.ts +++ b/src/plugins/saved_objects_tagging_oss/public/index.ts @@ -9,9 +9,9 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { SavedObjectTaggingOssPlugin } from './plugin'; -export { SavedObjectTaggingOssPluginSetup, SavedObjectTaggingOssPluginStart } from './types'; +export type { SavedObjectTaggingOssPluginSetup, SavedObjectTaggingOssPluginStart } from './types'; -export { +export type { SavedObjectsTaggingApi, SavedObjectsTaggingApiUi, SavedObjectsTaggingApiUiComponent, @@ -25,7 +25,7 @@ export { SavedObjectTagDecoratorTypeGuard, } from './api'; -export { TagDecoratedSavedObject } from './decorator'; +export type { TagDecoratedSavedObject } from './decorator'; export const plugin = (initializerContext: PluginInitializerContext) => new SavedObjectTaggingOssPlugin(initializerContext); diff --git a/src/plugins/screenshot_mode/public/index.ts b/src/plugins/screenshot_mode/public/index.ts index 012f57e837f416..591ddbfdf49c5e 100644 --- a/src/plugins/screenshot_mode/public/index.ts +++ b/src/plugins/screenshot_mode/public/index.ts @@ -18,4 +18,4 @@ export { KBN_SCREENSHOT_MODE_ENABLED_KEY, } from '../common'; -export { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './types'; +export type { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './types'; diff --git a/src/plugins/screenshot_mode/public/mocks.ts b/src/plugins/screenshot_mode/public/mocks.ts new file mode 100644 index 00000000000000..7fa93ff0bcea8a --- /dev/null +++ b/src/plugins/screenshot_mode/public/mocks.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 type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import type { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './types'; + +export const screenshotModePluginMock = { + createSetupContract: (): DeeplyMockedKeys => ({ + isScreenshotMode: jest.fn(() => false), + }), + createStartContract: (): DeeplyMockedKeys => ({ + isScreenshotMode: jest.fn(() => false), + }), +}; diff --git a/src/plugins/screenshot_mode/server/index.ts b/src/plugins/screenshot_mode/server/index.ts index b9f19a474ccbe3..cc5d45b7be7327 100644 --- a/src/plugins/screenshot_mode/server/index.ts +++ b/src/plugins/screenshot_mode/server/index.ts @@ -14,7 +14,7 @@ export { KBN_SCREENSHOT_MODE_ENABLED_KEY, } from '../common'; -export { +export type { ScreenshotModeRequestHandlerContext, ScreenshotModePluginSetup, ScreenshotModePluginStart, diff --git a/src/plugins/share/common/index.ts b/src/plugins/share/common/index.ts index 992ec2447855f2..0269dda43b9c5d 100644 --- a/src/plugins/share/common/index.ts +++ b/src/plugins/share/common/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ -export { LocatorDefinition, LocatorPublic, useLocatorUrl, formatSearchParams } from './url_service'; +export type { LocatorDefinition, LocatorPublic } from './url_service'; +export { useLocatorUrl } from './url_service'; export type { AnonymousAccessServiceContract, AnonymousAccessState } from './anonymous_access'; diff --git a/src/plugins/share/common/url_service/locators/locator.ts b/src/plugins/share/common/url_service/locators/locator.ts index 2d33f701df5953..2bacdd67875bd7 100644 --- a/src/plugins/share/common/url_service/locators/locator.ts +++ b/src/plugins/share/common/url_service/locators/locator.ts @@ -118,11 +118,9 @@ export class Locator

implements LocatorPublic

{ }); } - /* eslint-disable react-hooks/rules-of-hooks */ public readonly useUrl = ( params: P, getUrlParams?: LocatorGetUrlParams, deps: DependencyList = [] ): string => useLocatorUrl

(this, params, getUrlParams, deps); - /* eslint-enable react-hooks/rules-of-hooks */ } diff --git a/src/plugins/share/public/index.ts b/src/plugins/share/public/index.ts index 73b685e2cab5ac..2e57a9347b0572 100644 --- a/src/plugins/share/public/index.ts +++ b/src/plugins/share/public/index.ts @@ -10,13 +10,13 @@ import type { PluginInitializerContext } from 'src/core/public'; export { CSV_QUOTE_VALUES_SETTING, CSV_SEPARATOR_SETTING } from '../common/constants'; -export { LocatorDefinition, LocatorPublic, KibanaLocation } from '../common/url_service'; +export type { LocatorDefinition, LocatorPublic, KibanaLocation } from '../common/url_service'; -export { UrlGeneratorStateMapping } from './url_generators/url_generator_definition'; +export type { UrlGeneratorStateMapping } from './url_generators/url_generator_definition'; -export { SharePluginSetup, SharePluginStart } from './plugin'; +export type { SharePluginSetup, SharePluginStart } from './plugin'; -export { +export type { ShareContext, ShareMenuProvider, ShareMenuItem, @@ -24,20 +24,19 @@ export { ShareContextMenuPanelItem, } from './types'; -export { +export type { UrlGeneratorId, UrlGeneratorState, UrlGeneratorsDefinition, UrlGeneratorContract, - UrlGeneratorsService, } from './url_generators'; +export { UrlGeneratorsService } from './url_generators'; -export { RedirectOptions } from '../common/url_service'; +export type { RedirectOptions } from '../common/url_service'; export { useLocatorUrl } from '../common/url_service/locators/use_locator_url'; import { SharePlugin } from './plugin'; -export { KibanaURL } from './kibana_url'; export { downloadMultipleAs, downloadFileAs } from './lib/download_as'; export type { DownloadableContent } from './lib/download_as'; diff --git a/src/plugins/share/public/lib/url_shortener.ts b/src/plugins/share/public/lib/url_shortener.ts index 6d0b7ae91e3418..9a0ae9cfb0bad0 100644 --- a/src/plugins/share/public/lib/url_shortener.ts +++ b/src/plugins/share/public/lib/url_shortener.ts @@ -29,7 +29,7 @@ export async function shortenUrl( params: { url: relativeUrl }, }); - const resp = await post('/api/short_url', { + const resp = await post<{ id: string }>('/api/short_url', { body, }); diff --git a/src/plugins/share/public/url_generators/url_generator_contract.ts b/src/plugins/share/public/url_generators/url_generator_contract.ts index b21e0f2a35c881..22ccae8909a697 100644 --- a/src/plugins/share/public/url_generators/url_generator_contract.ts +++ b/src/plugins/share/public/url_generators/url_generator_contract.ts @@ -12,8 +12,4 @@ export interface UrlGeneratorContract { 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 index 9059a26566f921..7f7dc0f63f87b1 100644 --- a/src/plugins/share/public/url_generators/url_generator_internal.ts +++ b/src/plugins/share/public/url_generators/url_generator_internal.ts @@ -72,17 +72,6 @@ export class UrlGeneratorInternal { return this.spec.createUrl!(state); }, isDeprecated: !!this.spec.isDeprecated, - migrate: async (state: UrlGeneratorStateMapping[Id]['State']) => { - if (!this.spec.isDeprecated) { - throw new Error( - i18n.translate('share.urlGenerators.error.migrateCalledNotDeprecated', { - defaultMessage: 'You cannot call migrate on a non-deprecated generator.', - }) - ); - } - - return this.spec.migrate!(state); - }, }; } } 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 index cb70f1af2c1956..c07aa3f915b2e4 100644 --- a/src/plugins/share/public/url_generators/url_generator_service.test.ts +++ b/src/plugins/share/public/url_generators/url_generator_service.test.ts @@ -29,12 +29,8 @@ test('Registering and retrieving a generator', async () => { "createUrl": [Function], "id": "TEST_GENERATOR", "isDeprecated": false, - "migrate": [Function], } `); - await expect(generator.migrate({})).rejects.toEqual( - new Error('You cannot call migrate on a non-deprecated generator.') - ); expect(await generator.createUrl({})).toBe('myurl'); const retrievedGenerator = start.getUrlGenerator('TEST_GENERATOR'); @@ -43,12 +39,8 @@ test('Registering and retrieving a generator', async () => { "createUrl": [Function], "id": "TEST_GENERATOR", "isDeprecated": false, - "migrate": [Function], } `); - await expect(generator.migrate({})).rejects.toEqual( - new Error('You cannot call migrate on a non-deprecated generator.') - ); expect(await generator.createUrl({})).toBe('myurl'); }); @@ -124,6 +116,5 @@ test('Generator returns migrated url', async () => { const generator = start.getUrlGenerator('v1'); expect(generator.isDeprecated).toBe(true); - expect(await generator.migrate({ bar: 'hi' })).toEqual({ id: 'v2', state: { foo: 'hi' } }); expect(await generator.createUrl({ bar: 'hi' })).toEqual('www.hi.com'); }); diff --git a/src/plugins/share/public/url_service/redirect/redirect_manager.test.ts b/src/plugins/share/public/url_service/redirect/redirect_manager.test.ts index 7296ef6d0c1374..1fde60d4389fa3 100644 --- a/src/plugins/share/public/url_service/redirect/redirect_manager.test.ts +++ b/src/plugins/share/public/url_service/redirect/redirect_manager.test.ts @@ -57,9 +57,12 @@ describe('on page mount', () => { }) )}` ); - expect(spy).toHaveBeenCalledWith({ - foo: 'bar', - }); + expect(spy).toHaveBeenCalledWith( + { + foo: 'bar', + }, + { replace: true } + ); }); test('migrates parameters on-the-fly to the latest version', async () => { @@ -73,9 +76,12 @@ describe('on page mount', () => { }) )}` ); - expect(spy).toHaveBeenCalledWith({ - num: 2, - }); + expect(spy).toHaveBeenCalledWith( + { + num: 2, + }, + { replace: true } + ); }); test('throws if locator does not exist', async () => { diff --git a/src/plugins/share/public/url_service/redirect/redirect_manager.ts b/src/plugins/share/public/url_service/redirect/redirect_manager.ts index 810977659bcf0e..e6f524347e48cf 100644 --- a/src/plugins/share/public/url_service/redirect/redirect_manager.ts +++ b/src/plugins/share/public/url_service/redirect/redirect_manager.ts @@ -66,7 +66,9 @@ export class RedirectManager { }); locator - .navigate(migratedParams) + .navigate(migratedParams, { + replace: true, // We do not want the redirect app URL to appear in browser navigation history + }) .then() .catch((error) => { // eslint-disable-next-line no-console diff --git a/src/plugins/share/server/index.ts b/src/plugins/share/server/index.ts index d820a362131a49..73cd4cc89af70f 100644 --- a/src/plugins/share/server/index.ts +++ b/src/plugins/share/server/index.ts @@ -9,7 +9,7 @@ import { PluginInitializerContext } from '../../../core/server'; import { SharePlugin } from './plugin'; -export { SharePluginSetup, SharePluginStart } from './plugin'; +export type { SharePluginSetup, SharePluginStart } from './plugin'; export { CSV_QUOTE_VALUES_SETTING, CSV_SEPARATOR_SETTING } from '../common/constants'; diff --git a/src/plugins/ui_actions/README.asciidoc b/src/plugins/ui_actions/README.asciidoc index 27b3eae3a52a7b..8fd3d0378ce6e4 100644 --- a/src/plugins/ui_actions/README.asciidoc +++ b/src/plugins/ui_actions/README.asciidoc @@ -69,15 +69,15 @@ action to execute. === Examples -https://github.com/elastic/kibana/blob/master/examples/ui_action_examples/README.md[ui_action examples] +https://github.com/elastic/kibana/blob/main/examples/ui_action_examples/README.md[ui_action examples] === API Docs ==== Server API -https://github.com/elastic/kibana/blob/master/docs/development/plugins/ui_actions/server/kibana-plugin-plugins-ui_actions-server.uiactionssetup.md[Browser Setup contract] -https://github.com/elastic/kibana/blob/master/docs/development/plugins/ui_actions/server/kibana-plugin-plugins-ui_actions-server.uiactionsstart.md[Browser Start contract] +https://github.com/elastic/kibana/blob/main/docs/development/plugins/ui_actions/server/kibana-plugin-plugins-ui_actions-server.uiactionssetup.md[Browser Setup contract] +https://github.com/elastic/kibana/blob/main/docs/development/plugins/ui_actions/server/kibana-plugin-plugins-ui_actions-server.uiactionsstart.md[Browser Start contract] ==== Browser API -https://github.com/elastic/kibana/blob/master/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionssetup.md[Browser Setup contract] -https://github.com/elastic/kibana/blob/master/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsstart.md[Browser Start contract] +https://github.com/elastic/kibana/blob/main/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionssetup.md[Browser Setup contract] +https://github.com/elastic/kibana/blob/main/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsstart.md[Browser Start contract] diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 0804ed43cbe102..8a6b7ed9314909 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -13,33 +13,29 @@ export function plugin(initializerContext: PluginInitializerContext) { return new UiActionsPlugin(initializerContext); } -export { UiActionsSetup, UiActionsStart } from './plugin'; -export { UiActionsServiceParams, UiActionsService } from './service'; -export { - Action, - ActionDefinition as UiActionsActionDefinition, - createAction, - IncompatibleActionError, -} from './actions'; +export type { UiActionsSetup, UiActionsStart } from './plugin'; +export type { UiActionsServiceParams } from './service'; +export { UiActionsService } from './service'; +export type { Action, ActionDefinition as UiActionsActionDefinition } from './actions'; +export { createAction, IncompatibleActionError } from './actions'; export { buildContextMenuForActions } from './context_menu'; -export { +export type { Presentable as UiActionsPresentable, PresentableGrouping as UiActionsPresentableGrouping, } from './util'; +export type { Trigger, RowClickContext } from './triggers'; export { - Trigger, VISUALIZE_FIELD_TRIGGER, visualizeFieldTrigger, VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldTrigger, ROW_CLICK_TRIGGER, rowClickTrigger, - RowClickContext, } from './triggers'; +export type { VisualizeFieldContext } from './types'; export { - VisualizeFieldContext, ACTION_VISUALIZE_FIELD, ACTION_VISUALIZE_GEO_FIELD, ACTION_VISUALIZE_LENS_FIELD, } from './types'; -export { ActionExecutionContext, ActionExecutionMeta } from './actions'; +export type { ActionExecutionContext, ActionExecutionMeta } from './actions'; diff --git a/src/plugins/usage_collection/public/services/create_reporter.ts b/src/plugins/usage_collection/public/services/create_reporter.ts index e5006646fe368c..fb187d1c0c4860 100644 --- a/src/plugins/usage_collection/public/services/create_reporter.ts +++ b/src/plugins/usage_collection/public/services/create_reporter.ts @@ -22,7 +22,8 @@ export function createReporter(config: AnalyicsReporterConfig): Reporter { debug, storage: localStorage, async http(report) { - const response = await fetch.post('/api/ui_counters/_report', { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const response = await fetch.post('/api/ui_counters/_report', { body: JSON.stringify({ report }), asSystemRequest: true, }); diff --git a/src/plugins/vis_default_editor/public/components/options/index.ts b/src/plugins/vis_default_editor/public/components/options/index.ts index 62ce76014f9fcd..4d45da690c3c8b 100644 --- a/src/plugins/vis_default_editor/public/components/options/index.ts +++ b/src/plugins/vis_default_editor/public/components/options/index.ts @@ -9,8 +9,10 @@ export { BasicOptions } from './basic_options'; export { SwitchOption } from './switch'; export { SelectOption } from './select'; -export { ColorRanges, SetColorRangeValue } from './color_ranges'; -export { ColorSchemaOptions, SetColorSchemaOptionsValue } from './color_schema'; +export type { SetColorRangeValue } from './color_ranges'; +export { ColorRanges } from './color_ranges'; +export type { SetColorSchemaOptionsValue } from './color_schema'; +export { ColorSchemaOptions } from './color_schema'; export { NumberInputOption } from './number_input'; export { RangeOption } from './range'; export { RequiredNumberInputOption } from './required_number_input'; diff --git a/src/plugins/vis_default_editor/public/index.ts b/src/plugins/vis_default_editor/public/index.ts index ec1f514b9f2ff0..d89a898467b98e 100644 --- a/src/plugins/vis_default_editor/public/index.ts +++ b/src/plugins/vis_default_editor/public/index.ts @@ -17,7 +17,8 @@ export { DefaultEditorController }; export { useValidation } from './components/controls/utils'; export { PalettePicker } from './components/controls/palette_picker'; export * from './components/options'; -export { RangesParamEditor, RangeValues } from './components/controls/ranges'; +export type { RangeValues } from './components/controls/ranges'; +export { RangesParamEditor } from './components/controls/ranges'; export * from './editor_size'; export * from './utils'; diff --git a/src/plugins/vis_types/pie/public/index.ts b/src/plugins/vis_types/pie/public/index.ts index adf8b2d073f390..d7c5e9a2dfb2ad 100644 --- a/src/plugins/vis_types/pie/public/index.ts +++ b/src/plugins/vis_types/pie/public/index.ts @@ -9,6 +9,6 @@ import { VisTypePiePlugin } from './plugin'; export { pieVisType } from './vis_type'; -export { Dimensions, Dimension } from './types'; +export type { Dimensions, Dimension } from './types'; export const plugin = () => new VisTypePiePlugin(); diff --git a/src/plugins/vis_types/table/common/index.ts b/src/plugins/vis_types/table/common/index.ts index ad8e27c95a5ecb..c5737c58fdbb33 100644 --- a/src/plugins/vis_types/table/common/index.ts +++ b/src/plugins/vis_types/table/common/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { AggTypes, TableVisParams, VIS_TYPE_TABLE } from './types'; +export type { TableVisParams } from './types'; +export { AggTypes, VIS_TYPE_TABLE } from './types'; diff --git a/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap b/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap index 38e3dcbb7097c6..85cf9422630d68 100644 --- a/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap +++ b/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap @@ -17,7 +17,6 @@ exports[`TableVisBasic should init data grid 1`] = ` "header": "underline", } } - key="0" minSizeForControls={1} onColumnResize={[Function]} renderCellValue={[Function]} @@ -56,7 +55,6 @@ exports[`TableVisBasic should init data grid with title provided - for split mod "header": "underline", } } - key="0" minSizeForControls={1} onColumnResize={[Function]} renderCellValue={[Function]} @@ -88,7 +86,6 @@ exports[`TableVisBasic should render the toolbar 1`] = ` "header": "underline", } } - key="0" minSizeForControls={1} onColumnResize={[Function]} renderCellValue={[Function]} diff --git a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx index cfe1ce5d40a1eb..2476c17c58a4d0 100644 --- a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx +++ b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { memo, useCallback, useMemo, useEffect, useState, useRef } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { EuiDataGrid, EuiDataGridProps, EuiDataGridSorting, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; @@ -111,26 +111,6 @@ export const TableVisBasic = memo( [columns, setColumnsWidth] ); - const firstRender = useRef(true); - const [dataGridUpdateCounter, setDataGridUpdateCounter] = useState(0); - - // key was added as temporary solution to force re-render if we change autoFitRowToContent or we get new data - // cause we have problem with correct updating height cache in EUI datagrid when we use auto-height - // will be removed as soon as fix problem on EUI side - useEffect(() => { - // skip first render - if (firstRender.current) { - firstRender.current = false; - return; - } - // skip if auto height was turned off - if (!visConfig.autoFitRowToContent) { - return; - } - // update counter to remount grid from scratch - setDataGridUpdateCounter((counter) => counter + 1); - }, [visConfig.autoFitRowToContent, table, sort, pagination, columnsWidth]); - return ( <> {title && ( @@ -139,7 +119,6 @@ export const TableVisBasic = memo( )} ([]); const kibana = useKibana(); const argValueSuggestions = useMemo(getArgValueSuggestions, []); @@ -84,7 +84,7 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro useEffect(() => { if (kibana.services.http) { - kibana.services.http.get('../api/timelion/functions').then((data) => { + kibana.services.http.get('../api/timelion/functions').then((data) => { functionList.current = data; }); } diff --git a/src/plugins/vis_types/timelion/public/index.ts b/src/plugins/vis_types/timelion/public/index.ts index 8161f844e8f735..960eda3dc558fc 100644 --- a/src/plugins/vis_types/timelion/public/index.ts +++ b/src/plugins/vis_types/timelion/public/index.ts @@ -13,4 +13,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } -export { VisTypeTimelionPluginStart } from './plugin'; +export type { VisTypeTimelionPluginStart } from './plugin'; diff --git a/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx b/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx index c74c0f2ee6c2d2..633f15a9824ea6 100644 --- a/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx +++ b/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx @@ -45,8 +45,10 @@ export const getTimelionVisRenderer: ( timeFieldName: '*', filters: [ { - range: { - '*': rangeFilterParams, + query: { + range: { + '*': rangeFilterParams, + }, }, }, ], diff --git a/src/plugins/vis_types/timelion/server/types.ts b/src/plugins/vis_types/timelion/server/types.ts index 06b6afa613b064..0cbf2b08820894 100644 --- a/src/plugins/vis_types/timelion/server/types.ts +++ b/src/plugins/vis_types/timelion/server/types.ts @@ -6,4 +6,7 @@ * Side Public License, v 1. */ -export { TimelionFunctionInterface, TimelionFunctionConfig } from './lib/classes/timelion_function'; +export type { + TimelionFunctionInterface, + TimelionFunctionConfig, +} from './lib/classes/timelion_function'; diff --git a/src/plugins/vis_types/timelion/server/ui_settings.ts b/src/plugins/vis_types/timelion/server/ui_settings.ts index 1d8dc997a3f6aa..40907b02714873 100644 --- a/src/plugins/vis_types/timelion/server/ui_settings.ts +++ b/src/plugins/vis_types/timelion/server/ui_settings.ts @@ -30,7 +30,8 @@ export function getUiSettings( }), deprecation: { message: i18n.translate('timelion.uiSettings.legacyChartsLibraryDeprication', { - defaultMessage: 'This setting is deprecated and will not be supported as of 8.0.', + defaultMessage: + 'This setting is deprecated and will not be supported in a future version.', }), docLinksKey: 'timelionSettings', }, diff --git a/src/plugins/vis_types/timeseries/common/types/index.ts b/src/plugins/vis_types/timeseries/common/types/index.ts index 123b6723d8ccd1..5e04fee0d015ce 100644 --- a/src/plugins/vis_types/timeseries/common/types/index.ts +++ b/src/plugins/vis_types/timeseries/common/types/index.ts @@ -9,8 +9,8 @@ import { Filter, IndexPattern, Query } from '../../../../data/common'; import { Panel } from './panel_model'; -export { Metric, Series, Panel, MetricType } from './panel_model'; -export { TimeseriesVisData, PanelData, SeriesData, TableData } from './vis_data'; +export type { Metric, Series, Panel, MetricType } from './panel_model'; +export type { TimeseriesVisData, PanelData, SeriesData, TableData } from './vis_data'; export interface FetchedIndexPattern { indexPattern: IndexPattern | undefined | null; diff --git a/src/plugins/vis_types/timeseries/common/types/panel_model.ts b/src/plugins/vis_types/timeseries/common/types/panel_model.ts index f71602fdf04439..b4b167310a1948 100644 --- a/src/plugins/vis_types/timeseries/common/types/panel_model.ts +++ b/src/plugins/vis_types/timeseries/common/types/panel_model.ts @@ -115,7 +115,6 @@ export interface Series { terms_size?: string; time_range_mode?: string; trend_arrows?: number; - type?: string; value_template?: string; var_name?: string; } diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx index 08f8c072eef3b1..bc74be2a562f91 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx @@ -33,13 +33,13 @@ interface AggProps extends HTMLAttributes { siblings: Metric[]; uiRestrictions: TimeseriesUIRestrictions; dragHandleProps: DragHandleProps; - onChange: (part: Partial) => void; + onModelChange: (part: Partial) => void; onAdd: () => void; onDelete: () => void; } export function Agg(props: AggProps) { - const { model, uiRestrictions, series, name, onChange, fields, siblings } = props; + const { model, uiRestrictions, series, name, onModelChange, fields, siblings } = props; let Component = aggToComponent[model.type]; @@ -72,8 +72,8 @@ export function Agg(props: AggProps) { const isKibanaIndexPattern = props.panel.use_kibana_indexes || indexPattern === ''; const onAggChange = useMemo( - () => seriesChangeHandler({ name, model: series, onChange }, siblings), - [name, onChange, siblings, series] + () => seriesChangeHandler({ name, model: series, onChange: onModelChange }, siblings), + [name, onModelChange, siblings, series] ); useEffect(() => { @@ -86,17 +86,25 @@ export function Agg(props: AggProps) { ); if (isNumberFormatter && !isNumericMetric) { - onChange({ formatter: DATA_FORMATTERS.DEFAULT }); + onModelChange({ formatter: DATA_FORMATTERS.DEFAULT }); } // in case of string index pattern mode, change default formatter depending on metric type // "number" formatter for numeric metric and "" as custom formatter for any other type if (formatterType === DATA_FORMATTERS.DEFAULT && !isKibanaIndexPattern) { - onChange({ + onModelChange({ formatter: isNumericMetric ? DATA_FORMATTERS.NUMBER : '', }); } } - }, [indexPattern, model, onChange, fields, series.formatter, isKibanaIndexPattern, siblings]); + }, [ + indexPattern, + model, + onModelChange, + fields, + series.formatter, + isKibanaIndexPattern, + siblings, + ]); return (

diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx index 516e3551fb0109..f0172eaba01896 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx @@ -51,7 +51,7 @@ export class Aggs extends PureComponent { name={name} model={row} onAdd={() => handleAdd(this.props, newMetricAggFn)} - onChange={onChange} + onModelChange={onChange} onDelete={() => handleDelete(this.props, row)} panel={panel} series={model} diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js b/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js index c131ba2ae804ce..ff96e476814ffa 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js @@ -34,7 +34,7 @@ const runTest = (aggType, name, test, additionalProps = {}) => {
{ onChange({ @@ -126,7 +130,7 @@ export const IndexPattern = ({ const selectedTimeRangeOption = timeRangeOptions.find( ({ value }) => model[TIME_RANGE_MODE_KEY] === value ); - const isTimeSeries = model.type === PANEL_TYPES.TIMESERIES; + const isDataTimerangeModeInvalid = !disabled && selectedTimeRangeOption && diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts index bffc9200c9d6f4..3df52223c253ac 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts @@ -154,7 +154,6 @@ describe('convert series to datatables', () => { ], split_mode: 'terms', terms_field: 'Cancelled', - type: 'timeseries', }, ], } as TimeseriesVisParams; diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index.ts b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index.ts index 4920677a04a2ee..24b49ca8f4d47b 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index.ts +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export { IndexPatternSelect, IndexPatternSelectProps } from './index_pattern_select'; +export type { IndexPatternSelectProps } from './index_pattern_select'; +export { IndexPatternSelect } from './index_pattern_select'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/timeseries_visualization.tsx b/src/plugins/vis_types/timeseries/public/application/components/timeseries_visualization.tsx index 886b569671a6b4..0916892cfda461 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/timeseries_visualization.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/timeseries_visualization.tsx @@ -81,10 +81,12 @@ function TimeseriesVisualization({ timeFieldName: '*', filters: [ { - range: { - '*': { - gte, - lte, + query: { + range: { + '*': { + gte, + lte, + }, }, }, }, diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js index 53ded44353ddb3..30a5867d799cb7 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js @@ -24,7 +24,6 @@ import { import { Split } from '../../split'; import { createTextHandler } from '../../lib/create_text_handler'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { PANEL_TYPES } from '../../../../../common/enums'; const TimeseriesSeriesUI = injectI18n(function (props) { const { @@ -45,7 +44,6 @@ const TimeseriesSeriesUI = injectI18n(function (props) { const defaults = { label: '', - type: PANEL_TYPES.TIMESERIES, }; const model = { ...defaults, ...props.model }; diff --git a/src/plugins/vis_types/timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_types/timeseries/public/application/visualizations/views/timeseries/index.js index 5e98b74c0caa5b..9dfddd3457d445 100644 --- a/src/plugins/vis_types/timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_types/timeseries/public/application/visualizations/views/timeseries/index.js @@ -32,7 +32,11 @@ import { getBaseTheme, getChartClasses } from './utils/theme'; import { TOOLTIP_MODES } from '../../../../../common/enums'; import { getValueOrEmpty } from '../../../../../common/empty_label'; import { getSplitByTermsColor } from '../../../lib/get_split_by_terms_color'; -import { renderEndzoneTooltip, useActiveCursor } from '../../../../../../../charts/public'; +import { + MULTILAYER_TIME_AXIS_STYLE, + renderEndzoneTooltip, + useActiveCursor, +} from '../../../../../../../charts/public'; import { getAxisLabelString } from '../../../components/lib/get_axis_label_string'; import { calculateDomainForSeries } from './utils/series_domain_calculation'; @@ -140,49 +144,15 @@ export const TimeSeries = ({ [palettesService, series, syncColors] ); - const darkMode = uiSettings.get('theme:darkMode'); - const gridLineStyle = !useLegacyTimeAxis - ? { - visible: showGrid, - strokeWidth: 0.1, - stroke: darkMode ? 'white' : 'black', - } - : { - ...GRID_LINE_CONFIG, - visible: showGrid, - }; - const xAxisStyle = !useLegacyTimeAxis - ? { - tickLabel: { - visible: true, - fontSize: 11, - padding: 0, - alignment: { - vertical: Position.Bottom, - horizontal: Position.Left, - }, - offset: { - x: 1.5, - y: 0, - }, - }, - axisLine: { - stroke: darkMode ? 'lightgray' : 'darkgray', - strokeWidth: 1, - }, - tickLine: { - size: 12, - strokeWidth: 0.15, - stroke: darkMode ? 'white' : 'black', - padding: -10, - visible: true, - }, - axisTitle: { - visible: true, - padding: 0, - }, - } - : {}; + const gridLineStyle = { + ...GRID_LINE_CONFIG, + visible: showGrid, + }; + + const shouldUseNewTimeAxis = + series.every( + ({ stack, bars, lines }) => (bars?.show && stack !== STACKED_OPTIONS.NONE) || lines?.show + ) && !useLegacyTimeAxis; return ( @@ -361,10 +331,8 @@ export const TimeSeries = ({ position={position} domain={domain} hide={hide} - gridLine={{ - ...GRID_LINE_CONFIG, - visible: showGrid, - }} + gridLine={gridLineStyle} + ticks={5} tickFormat={tickFormatter} /> ))} @@ -375,8 +343,8 @@ export const TimeSeries = ({ title={getAxisLabelString(interval)} tickFormat={xAxisFormatter} gridLine={gridLineStyle} - style={xAxisStyle} - timeAxisLayerCount={useLegacyTimeAxis ? 0 : 3} + style={shouldUseNewTimeAxis ? MULTILAYER_TIME_AXIS_STYLE : undefined} + timeAxisLayerCount={shouldUseNewTimeAxis ? 3 : 0} /> ); diff --git a/src/plugins/vis_types/timeseries/server/index.ts b/src/plugins/vis_types/timeseries/server/index.ts index 7a10740a53d325..d6b1174177bb6b 100644 --- a/src/plugins/vis_types/timeseries/server/index.ts +++ b/src/plugins/vis_types/timeseries/server/index.ts @@ -10,7 +10,7 @@ import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/serve import { VisTypeTimeseriesConfig, config as configSchema } from './config'; import { VisTypeTimeseriesPlugin } from './plugin'; -export { VisTypeTimeseriesSetup } from './plugin'; +export type { VisTypeTimeseriesSetup } from './plugin'; export const config: PluginConfigDescriptor = { schema: configSchema, @@ -20,5 +20,5 @@ export function plugin(initializerContext: PluginInitializerContext) { return new VisTypeTimeseriesPlugin(initializerContext); } -export { TimeseriesVisData } from '../common/types'; +export type { TimeseriesVisData } from '../common/types'; export { isVisSeriesData, isVisTableData } from '../common/vis_data_utils'; diff --git a/src/plugins/vis_types/vega/public/vega_inspector/index.ts b/src/plugins/vis_types/vega/public/vega_inspector/index.ts index e79d2ee356aefd..72dc539dc2ea72 100644 --- a/src/plugins/vis_types/vega/public/vega_inspector/index.ts +++ b/src/plugins/vis_types/vega/public/vega_inspector/index.ts @@ -6,8 +6,5 @@ * Side Public License, v 1. */ -export { - createInspectorAdapters, - getVegaInspectorView, - VegaInspectorAdapters, -} from './vega_inspector'; +export type { VegaInspectorAdapters } from './vega_inspector'; +export { createInspectorAdapters, getVegaInspectorView } from './vega_inspector'; diff --git a/src/plugins/vis_types/vega/server/index.ts b/src/plugins/vis_types/vega/server/index.ts index 9c448f6c618d3a..62bc9a1494acf1 100644 --- a/src/plugins/vis_types/vega/server/index.ts +++ b/src/plugins/vis_types/vega/server/index.ts @@ -22,4 +22,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new VisTypeVegaPlugin(initializerContext); } -export { VisTypeVegaPluginStart, VisTypeVegaPluginSetup } from './types'; +export type { VisTypeVegaPluginStart, VisTypeVegaPluginSetup } from './types'; diff --git a/src/plugins/vis_types/xy/public/config/get_axis.ts b/src/plugins/vis_types/xy/public/config/get_axis.ts index 796636ef2cb61f..23605feddd3811 100644 --- a/src/plugins/vis_types/xy/public/config/get_axis.ts +++ b/src/plugins/vis_types/xy/public/config/get_axis.ts @@ -8,16 +8,11 @@ import { identity } from 'lodash'; -import { - AxisSpec, - TickFormatter, - YDomainRange, - ScaleType as ECScaleType, - Position, -} from '@elastic/charts'; +import { AxisSpec, TickFormatter, YDomainRange, ScaleType as ECScaleType } from '@elastic/charts'; import { LabelRotation } from '../../../../charts/public'; import { BUCKET_TYPES } from '../../../../data/public'; +import { MULTILAYER_TIME_AXIS_STYLE } from '../../../../charts/common'; import { Aspect, @@ -164,29 +159,13 @@ function getAxisStyle( ): AxisSpec['style'] { return isMultiLayerTimeAxis ? { + ...MULTILAYER_TIME_AXIS_STYLE, tickLabel: { + ...MULTILAYER_TIME_AXIS_STYLE.tickLabel, visible: Boolean(ticks?.show), - rotation: 0, // rotation is disabled on new time axis - fontSize: 11, - padding: 0, - alignment: { - vertical: Position.Bottom, - horizontal: Position.Left, - }, - offset: { - x: 1.5, - y: 0, - }, - }, - axisLine: { - stroke: darkMode ? 'lightgray' : 'darkgray', - strokeWidth: 1, }, tickLine: { - size: 12, - strokeWidth: 0.15, - stroke: darkMode ? 'white' : 'black', - padding: -10, + ...MULTILAYER_TIME_AXIS_STYLE.tickLine, visible: Boolean(ticks?.show), }, axisTitle: { @@ -198,7 +177,7 @@ function getAxisStyle( visible: (title ?? '').trim().length > 0, }, tickLabel: { - visible: ticks?.show, + visible: Boolean(ticks?.show), rotation: -(ticks?.rotation ?? rotationFallback), }, }; diff --git a/src/plugins/vis_types/xy/public/editor/components/common/index.ts b/src/plugins/vis_types/xy/public/editor/components/common/index.ts index 5eec1fff7b7a68..5a91629c50611d 100644 --- a/src/plugins/vis_types/xy/public/editor/components/common/index.ts +++ b/src/plugins/vis_types/xy/public/editor/components/common/index.ts @@ -7,4 +7,5 @@ */ export { TruncateLabelsOption } from './truncate_labels'; -export { ValidationWrapper, ValidationVisOptionsProps } from './validation_wrapper'; +export type { ValidationVisOptionsProps } from './validation_wrapper'; +export { ValidationWrapper } from './validation_wrapper'; diff --git a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.mocks.ts b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.mocks.ts index e51b47bc4c7fa7..b01a04c1623755 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.mocks.ts +++ b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.mocks.ts @@ -389,7 +389,7 @@ export const getVis = (bucketType: string) => { labels: { show: true, rotate: 0, - filter: false, + filter: true, truncate: 100, }, title: { @@ -822,7 +822,7 @@ export const getStateParams = (type: string, thresholdPanelOn: boolean) => { labels: { show: true, rotate: 0, - filter: false, + filter: true, truncate: 100, }, title: { diff --git a/src/plugins/vis_types/xy/public/expression_functions/index.ts b/src/plugins/vis_types/xy/public/expression_functions/index.ts index 32c50e3adff1ea..4d6b2305a36514 100644 --- a/src/plugins/vis_types/xy/public/expression_functions/index.ts +++ b/src/plugins/vis_types/xy/public/expression_functions/index.ts @@ -8,10 +8,17 @@ export { visTypeXyVisFn } from './xy_vis_fn'; -export { categoryAxis, ExpressionValueCategoryAxis } from './category_axis'; -export { timeMarker, ExpressionValueTimeMarker } from './time_marker'; -export { valueAxis, ExpressionValueValueAxis } from './value_axis'; -export { seriesParam, ExpressionValueSeriesParam } from './series_param'; -export { thresholdLine, ExpressionValueThresholdLine } from './threshold_line'; -export { label, ExpressionValueLabel } from './label'; -export { visScale, ExpressionValueScale } from './vis_scale'; +export type { ExpressionValueCategoryAxis } from './category_axis'; +export { categoryAxis } from './category_axis'; +export type { ExpressionValueTimeMarker } from './time_marker'; +export { timeMarker } from './time_marker'; +export type { ExpressionValueValueAxis } from './value_axis'; +export { valueAxis } from './value_axis'; +export type { ExpressionValueSeriesParam } from './series_param'; +export { seriesParam } from './series_param'; +export type { ExpressionValueThresholdLine } from './threshold_line'; +export { thresholdLine } from './threshold_line'; +export type { ExpressionValueLabel } from './label'; +export { label } from './label'; +export type { ExpressionValueScale } from './vis_scale'; +export { visScale } from './vis_scale'; diff --git a/src/plugins/vis_types/xy/public/index.ts b/src/plugins/vis_types/xy/public/index.ts index 1ee96fab352539..41a8e08fa1ad25 100644 --- a/src/plugins/vis_types/xy/public/index.ts +++ b/src/plugins/vis_types/xy/public/index.ts @@ -11,11 +11,11 @@ import { VisTypeXyPlugin as Plugin } from './plugin'; -export { VisTypeXyPluginSetup } from './plugin'; +export type { VisTypeXyPluginSetup } from './plugin'; // TODO: Remove when vis_type_vislib is removed // https://github.com/elastic/kibana/issues/56143 -export { +export type { CategoryAxis, ThresholdLine, ValueAxis, @@ -23,9 +23,8 @@ export { SeriesParam, Dimension, Dimensions, - ScaleType, - AxisType, } from './types'; +export { ScaleType, AxisType } from './types'; export type { ValidationVisOptionsProps } from './editor/components/common/validation_wrapper'; export { TruncateLabelsOption } from './editor/components/common/truncate_labels'; export { getPositions } from './editor/positions'; diff --git a/src/plugins/vis_types/xy/public/mocks.ts b/src/plugins/vis_types/xy/public/mocks.ts index bb740354857234..6c0de8de1ac368 100644 --- a/src/plugins/vis_types/xy/public/mocks.ts +++ b/src/plugins/vis_types/xy/public/mocks.ts @@ -118,7 +118,7 @@ export const visParamsWithTwoYAxes = { }, labels: { type: 'label', - filter: false, + filter: true, rotate: 0, show: true, truncate: 100, @@ -138,7 +138,7 @@ export const visParamsWithTwoYAxes = { mode: 'normal', }, labels: { - filter: false, + filter: true, rotate: 0, show: true, truncate: 100, diff --git a/src/plugins/vis_types/xy/public/vis_types/area.ts b/src/plugins/vis_types/xy/public/vis_types/area.ts index 3ff840f1817e92..a140164cf2eb8a 100644 --- a/src/plugins/vis_types/xy/public/vis_types/area.ts +++ b/src/plugins/vis_types/xy/public/vis_types/area.ts @@ -74,7 +74,7 @@ export const areaVisTypeDefinition = { labels: { show: true, rotate: LabelRotation.Horizontal, - filter: false, + filter: true, truncate: 100, }, title: { diff --git a/src/plugins/vis_types/xy/public/vis_types/histogram.ts b/src/plugins/vis_types/xy/public/vis_types/histogram.ts index dd65d6f31cb801..c9d17c8ed4501f 100644 --- a/src/plugins/vis_types/xy/public/vis_types/histogram.ts +++ b/src/plugins/vis_types/xy/public/vis_types/histogram.ts @@ -76,7 +76,7 @@ export const histogramVisTypeDefinition = { labels: { show: true, rotate: LabelRotation.Horizontal, - filter: false, + filter: true, truncate: 100, }, title: { diff --git a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts index c8494024d1d0af..f6d2a6e0e429ac 100644 --- a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts +++ b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts @@ -56,7 +56,7 @@ export const horizontalBarVisTypeDefinition = { labels: { show: true, rotate: LabelRotation.Horizontal, - filter: false, + filter: true, truncate: 200, }, title: {}, diff --git a/src/plugins/vis_types/xy/public/vis_types/line.ts b/src/plugins/vis_types/xy/public/vis_types/line.ts index 08e17f7e97d463..3b6c9dc1a2084c 100644 --- a/src/plugins/vis_types/xy/public/vis_types/line.ts +++ b/src/plugins/vis_types/xy/public/vis_types/line.ts @@ -74,7 +74,7 @@ export const lineVisTypeDefinition = { labels: { show: true, rotate: LabelRotation.Horizontal, - filter: false, + filter: true, truncate: 100, }, title: { diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index e6ea3cd4895560..8ae0c426689ac5 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -22,17 +22,17 @@ export { VisualizationContainer } from './components'; export { getVisSchemas } from './vis_schemas'; /** @public types */ -export { VisualizationsSetup, VisualizationsStart }; +export type { VisualizationsSetup, VisualizationsStart }; export { VisGroups } from './vis_types/vis_groups_enum'; export type { BaseVisType, VisTypeAlias, VisTypeDefinition, Schema, ISchemas } from './vis_types'; export type { Vis, SerializedVis, SerializedVisData, VisData } from './vis'; export type VisualizeEmbeddableFactoryContract = PublicContract; export type VisualizeEmbeddableContract = PublicContract; -export { VisualizeInput } from './embeddable'; -export { SchemaConfig } from './vis_schemas'; +export type { VisualizeInput } from './embeddable'; +export type { SchemaConfig } from './vis_schemas'; export { updateOldState } from './legacy/vis_update_state'; export type { PersistedState } from './persisted_state'; -export { +export type { ISavedVis, VisSavedObject, VisToExpressionAst, @@ -40,11 +40,15 @@ export { VisEditorOptionsProps, GetVisOptions, } from './types'; -export { VisualizationListItem, VisualizationStage } from './vis_types/vis_type_alias_registry'; +export type { + VisualizationListItem, + VisualizationStage, +} from './vis_types/vis_type_alias_registry'; export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants'; -export { SavedVisState, VisParams, prepareLogTable, Dimension } from '../common'; -export { ExpressionValueVisDimension } from '../common/expression_functions/vis_dimension'; -export { +export type { SavedVisState, VisParams, Dimension } from '../common'; +export { prepareLogTable } from '../common'; +export type { ExpressionValueVisDimension } from '../common/expression_functions/vis_dimension'; +export type { ExpressionValueXYDimension, DateHistogramParams, FakeParams, diff --git a/src/plugins/visualizations/public/vis_types/types_service.ts b/src/plugins/visualizations/public/vis_types/types_service.ts index 567da272dfd0a2..ae8ba8b8ad518e 100644 --- a/src/plugins/visualizations/public/vis_types/types_service.ts +++ b/src/plugins/visualizations/public/vis_types/types_service.ts @@ -110,4 +110,4 @@ export type TypesSetup = ReturnType; export type TypesStart = ReturnType; /** @public types */ -export { VisTypeAlias }; +export type { VisTypeAlias }; diff --git a/src/plugins/visualizations/server/index.ts b/src/plugins/visualizations/server/index.ts index d093a6829f3073..f10caf44b3ccd7 100644 --- a/src/plugins/visualizations/server/index.ts +++ b/src/plugins/visualizations/server/index.ts @@ -18,4 +18,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new VisualizationsPlugin(initializerContext); } -export { VisualizationsPluginSetup, VisualizationsPluginStart } from './types'; +export type { VisualizationsPluginSetup, VisualizationsPluginStart } from './types'; diff --git a/src/plugins/visualize/public/application/types.ts b/src/plugins/visualize/public/application/types.ts index e77520c962d886..b15b521f6251df 100644 --- a/src/plugins/visualize/public/application/types.ts +++ b/src/plugins/visualize/public/application/types.ts @@ -145,4 +145,4 @@ export interface EditorRenderProps { linked: boolean; } -export { PureVisState }; +export type { PureVisState }; diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.test.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.test.tsx index 450654e42585d8..19b52d73661a3a 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.test.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.test.tsx @@ -5,9 +5,18 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { Observable } from 'rxjs'; import { Capabilities } from 'src/core/public'; -import { showPublicUrlSwitch } from './get_top_nav_config'; +import { showPublicUrlSwitch, getTopNavConfig, TopNavConfigParams } from './get_top_nav_config'; +import type { + VisualizeEditorVisInstance, + VisualizeAppStateContainer, + VisualizeServices, +} from '../types'; +import { createVisualizeServicesMock } from './mocks'; +import { sharePluginMock } from '../../../../share/public/mocks'; +import { createEmbeddableStateTransferMock } from '../../../../embeddable/public/mocks'; +import { visualizeAppStateStub } from './stubs'; describe('showPublicUrlSwitch', () => { test('returns false if "visualize" app is not available', () => { @@ -49,3 +58,201 @@ describe('showPublicUrlSwitch', () => { expect(result).toBe(true); }); }); + +describe('getTopNavConfig', () => { + const stateContainerGetStateMock = jest.fn(() => visualizeAppStateStub); + const stateContainer = { + getState: stateContainerGetStateMock, + state$: new Observable(), + transitions: { + updateVisState: jest.fn(), + set: jest.fn(), + }, + } as unknown as VisualizeAppStateContainer; + const mockServices = createVisualizeServicesMock(); + const share = sharePluginMock.createStartContract(); + const services = { + ...mockServices, + dashboard: { + dashboardFeatureFlagConfig: { + allowByValueEmbeddables: true, + }, + }, + visualizeCapabilities: { + save: true, + }, + dashboardCapabilities: { + showWriteControls: true, + }, + share, + }; + test('returns correct links for by reference visualization', () => { + const vis = { + savedVis: { + id: 'test', + sharingSavedObjectProps: { + outcome: 'conflict', + aliasTargetId: 'alias_id', + }, + }, + vis: { + type: { + title: 'TSVB', + }, + }, + } as VisualizeEditorVisInstance; + const topNavLinks = getTopNavConfig( + { + hasUnsavedChanges: false, + setHasUnsavedChanges: jest.fn(), + hasUnappliedChanges: false, + onOpenInspector: jest.fn(), + originatingApp: 'dashboards', + setOriginatingApp: jest.fn(), + visInstance: vis, + stateContainer, + visualizationIdFromUrl: undefined, + stateTransfer: createEmbeddableStateTransferMock(), + } as unknown as TopNavConfigParams, + services as unknown as VisualizeServices + ); + + expect(topNavLinks).toMatchInlineSnapshot(` + Array [ + Object { + "description": "Open Inspector for visualization", + "disableButton": [Function], + "id": "inspector", + "label": "inspect", + "run": undefined, + "testId": "openInspectorButton", + "tooltip": [Function], + }, + Object { + "description": "Share Visualization", + "disableButton": false, + "id": "share", + "label": "share", + "run": [Function], + "testId": "shareTopNavButton", + }, + Object { + "description": "Return to the last app without saving changes", + "emphasize": false, + "id": "cancel", + "label": "Cancel", + "run": [Function], + "testId": "visualizeCancelAndReturnButton", + "tooltip": [Function], + }, + Object { + "description": "Save Visualization", + "disableButton": false, + "emphasize": false, + "iconType": undefined, + "id": "save", + "label": "Save as", + "run": [Function], + "testId": "visualizeSaveButton", + "tooltip": [Function], + }, + Object { + "description": "Finish editing visualization and return to the last app", + "disableButton": false, + "emphasize": true, + "iconType": "checkInCircleFilled", + "id": "saveAndReturn", + "label": "Save and return", + "run": [Function], + "testId": "visualizesaveAndReturnButton", + "tooltip": [Function], + }, + ] + `); + }); + + test('returns correct links for by value visualization', () => { + const vis = { + savedVis: { + id: undefined, + sharingSavedObjectProps: { + outcome: 'conflict', + aliasTargetId: 'alias_id', + }, + }, + vis: { + type: { + title: 'TSVB', + }, + }, + } as VisualizeEditorVisInstance; + const topNavLinks = getTopNavConfig( + { + hasUnsavedChanges: false, + setHasUnsavedChanges: jest.fn(), + hasUnappliedChanges: false, + onOpenInspector: jest.fn(), + originatingApp: 'dashboards', + setOriginatingApp: jest.fn(), + visInstance: vis, + stateContainer, + visualizationIdFromUrl: undefined, + stateTransfer: createEmbeddableStateTransferMock(), + } as unknown as TopNavConfigParams, + services as unknown as VisualizeServices + ); + + expect(topNavLinks).toMatchInlineSnapshot(` + Array [ + Object { + "description": "Open Inspector for visualization", + "disableButton": [Function], + "id": "inspector", + "label": "inspect", + "run": undefined, + "testId": "openInspectorButton", + "tooltip": [Function], + }, + Object { + "description": "Share Visualization", + "disableButton": true, + "id": "share", + "label": "share", + "run": [Function], + "testId": "shareTopNavButton", + }, + Object { + "description": "Return to the last app without saving changes", + "emphasize": false, + "id": "cancel", + "label": "Cancel", + "run": [Function], + "testId": "visualizeCancelAndReturnButton", + "tooltip": [Function], + }, + Object { + "description": "Save Visualization", + "disableButton": false, + "emphasize": false, + "iconType": undefined, + "id": "save", + "label": "Save to library", + "run": [Function], + "testId": "visualizeSaveButton", + "tooltip": [Function], + }, + Object { + "description": "Finish editing visualization and return to the last app", + "disableButton": false, + "emphasize": true, + "iconType": "checkInCircleFilled", + "id": "saveAndReturn", + "label": "Save and return", + "run": [Function], + "testId": "visualizesaveAndReturnButton", + "tooltip": [Function], + }, + ] + `); + }); +}); diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index 9d1c93f25645c9..772565734dac43 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -49,7 +49,7 @@ interface VisualizeCapabilities { show: boolean; } -interface TopNavConfigParams { +export interface TopNavConfigParams { hasUnsavedChanges: boolean; setHasUnsavedChanges: (value: boolean) => void; openInspector: () => void; @@ -243,18 +243,17 @@ export const getTopNavConfig = ( const allowByValue = dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables; const saveButtonLabel = - embeddableId || (!savedVis.id && allowByValue && originatingApp) + !savedVis.id && allowByValue && originatingApp ? i18n.translate('visualize.topNavMenu.saveVisualizationToLibraryButtonLabel', { defaultMessage: 'Save to library', }) - : originatingApp && (embeddableId || savedVis.id) + : originatingApp && savedVis.id ? i18n.translate('visualize.topNavMenu.saveVisualizationAsButtonLabel', { defaultMessage: 'Save as', }) : i18n.translate('visualize.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'Save', }); - const showSaveAndReturn = originatingApp && (savedVis?.id || allowByValue); const showSaveButton = @@ -293,7 +292,7 @@ export const getTopNavConfig = ( }), testId: 'shareTopNavButton', run: (anchorElement) => { - if (share && !embeddableId) { + if (share) { const currentState = stateContainer.getState(); const searchParams = parse(history.location.search); const params: VisualizeLocatorParams = { @@ -336,8 +335,8 @@ export const getTopNavConfig = ( }); } }, - // disable the Share button if no action specified - disableButton: !share || !!embeddableId, + // disable the Share button if no action specified and fot byValue visualizations + disableButton: !share || Boolean(!savedVis.id && allowByValue && originatingApp), }, ...(originatingApp ? [ diff --git a/src/plugins/visualize/public/index.ts b/src/plugins/visualize/public/index.ts index ff1b2ba4c3bf5d..dd77987f6a722d 100644 --- a/src/plugins/visualize/public/index.ts +++ b/src/plugins/visualize/public/index.ts @@ -11,9 +11,9 @@ import { VisualizePlugin, VisualizePluginSetup } from './plugin'; export { VisualizeConstants } from './application/visualize_constants'; -export { IEditorController, EditorRenderProps } from './application/types'; +export type { IEditorController, EditorRenderProps } from './application/types'; -export { VisualizePluginSetup }; +export type { VisualizePluginSetup }; export const plugin = (context: PluginInitializerContext) => { return new VisualizePlugin(context); diff --git a/src/setup_node_env/exit_on_warning.js b/src/setup_node_env/exit_on_warning.js index 998dd02a6bff0c..5e7bae8254c04e 100644 --- a/src/setup_node_env/exit_on_warning.js +++ b/src/setup_node_env/exit_on_warning.js @@ -39,12 +39,6 @@ var IGNORE_WARNINGS = [ name: 'DeprecationWarning', code: 'DEP0148', }, - // In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. - // Remove after https://github.com/elastic/synthetics/pull/390 - { - name: 'DeprecationWarning', - code: 'DEP0147', - }, { // TODO: @elastic/es-clients - The new client will attempt a Product check and it will `process.emitWarning` // that the security features are blocking such check. diff --git a/test/api_integration/apis/custom_integration/integrations.ts b/test/api_integration/apis/custom_integration/integrations.ts index 0784a86e4b546c..036eb2ef33c78f 100644 --- a/test/api_integration/apis/custom_integration/integrations.ts +++ b/test/api_integration/apis/custom_integration/integrations.ts @@ -22,7 +22,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body).to.be.an('array'); - expect(resp.body.length).to.be(33); + expect(resp.body.length).to.be(34); // Test for sample data card expect(resp.body.findIndex((c: { id: string }) => c.id === 'sample_data_all')).to.be.above( @@ -40,7 +40,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body).to.be.an('array'); - expect(resp.body.length).to.be.above(109); // at least the beats + apm + expect(resp.body.length).to.be(109); // the beats }); }); }); diff --git a/test/api_integration/apis/home/sample_data.ts b/test/api_integration/apis/home/sample_data.ts index 2525cbe3300443..55fee03186cbbe 100644 --- a/test/api_integration/apis/home/sample_data.ts +++ b/test/api_integration/apis/home/sample_data.ts @@ -7,6 +7,7 @@ */ import expect from '@kbn/expect'; +import type { Response } from 'superagent'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -15,81 +16,142 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('es'); const MILLISECOND_IN_WEEK = 1000 * 60 * 60 * 24 * 7; + const SPACES = ['default', 'other']; + const FLIGHTS_OVERVIEW_DASHBOARD_ID = '7adfa750-4c81-11e8-b3d7-01146121b73d'; // default ID of the flights overview dashboard + const FLIGHTS_CANVAS_APPLINK_PATH = + '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0'; // includes default ID of the flights canvas applink path describe('sample data apis', () => { before(async () => { await esArchiver.emptyKibanaIndex(); }); - describe('list', () => { - it('should return list of sample data sets with installed status', async () => { - const resp = await supertest.get(`/api/sample_data`).set('kbn-xsrf', 'kibana').expect(200); - - expect(resp.body).to.be.an('array'); - expect(resp.body.length).to.be.above(0); - expect(resp.body[0].status).to.be('not_installed'); - }); + after(async () => { + await esArchiver.emptyKibanaIndex(); }); - describe('install', () => { - it('should return 404 if id does not match any sample data sets', async () => { - await supertest.post(`/api/sample_data/xxxx`).set('kbn-xsrf', 'kibana').expect(404); - }); + for (const space of SPACES) { + const apiPath = `/s/${space}/api/sample_data`; - it('should return 200 if success', async () => { - const resp = await supertest - .post(`/api/sample_data/flights`) - .set('kbn-xsrf', 'kibana') - .expect(200); + describe(`list in the ${space} space (before install)`, () => { + it('should return list of sample data sets with installed status', async () => { + const resp = await supertest.get(apiPath).set('kbn-xsrf', 'kibana').expect(200); - expect(resp.body).to.eql({ - elasticsearchIndicesCreated: { kibana_sample_data_flights: 13059 }, - kibanaSavedObjectsLoaded: 11, + const flightsData = findFlightsData(resp); + expect(flightsData.status).to.be('not_installed'); + // Check and make sure the sample dataset reflects the default object IDs, because no sample data objects exist. + // Instead of checking each object ID, we check the dashboard and canvas app link as representatives. + expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); + expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH); }); }); - it('should load elasticsearch index containing sample data with dates relative to current time', async () => { - const resp = await es.search<{ timestamp: string }>({ - index: 'kibana_sample_data_flights', + describe(`install in the ${space} space`, () => { + it('should return 404 if id does not match any sample data sets', async () => { + await supertest.post(`${apiPath}/xxxx`).set('kbn-xsrf', 'kibana').expect(404); }); - const doc = resp.hits.hits[0]; - const docMilliseconds = Date.parse(doc._source!.timestamp); - const nowMilliseconds = Date.now(); - const delta = Math.abs(nowMilliseconds - docMilliseconds); - expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4); - }); + it('should return 200 if success', async () => { + const resp = await supertest + .post(`${apiPath}/flights`) + .set('kbn-xsrf', 'kibana') + .expect(200); - describe('parameters', () => { - it('should load elasticsearch index containing sample data with dates relative to now parameter', async () => { - const nowString = `2000-01-01T00:00:00`; - await supertest - .post(`/api/sample_data/flights?now=${nowString}`) - .set('kbn-xsrf', 'kibana'); + expect(resp.body).to.eql({ + elasticsearchIndicesCreated: { kibana_sample_data_flights: 13059 }, + kibanaSavedObjectsLoaded: 11, + }); + }); + it('should load elasticsearch index containing sample data with dates relative to current time', async () => { const resp = await es.search<{ timestamp: string }>({ index: 'kibana_sample_data_flights', }); const doc = resp.hits.hits[0]; const docMilliseconds = Date.parse(doc._source!.timestamp); - const nowMilliseconds = Date.parse(nowString); + const nowMilliseconds = Date.now(); const delta = Math.abs(nowMilliseconds - docMilliseconds); expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4); }); + + describe('parameters', () => { + it('should load elasticsearch index containing sample data with dates relative to now parameter', async () => { + const nowString = `2000-01-01T00:00:00`; + await supertest.post(`${apiPath}/flights?now=${nowString}`).set('kbn-xsrf', 'kibana'); + + const resp = await es.search<{ timestamp: string }>({ + index: 'kibana_sample_data_flights', + }); + + const doc = resp.hits.hits[0]; + const docMilliseconds = Date.parse(doc._source!.timestamp); + const nowMilliseconds = Date.parse(nowString); + const delta = Math.abs(nowMilliseconds - docMilliseconds); + expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4); + }); + }); }); - }); - describe('uninstall', () => { - it('should uninstall sample data', async () => { - await supertest.delete(`/api/sample_data/flights`).set('kbn-xsrf', 'kibana').expect(204); + describe(`list in the ${space} space (after install)`, () => { + it('should return list of sample data sets with installed status', async () => { + const resp = await supertest.get(apiPath).set('kbn-xsrf', 'kibana').expect(200); + + const flightsData = findFlightsData(resp); + expect(flightsData.status).to.be('installed'); + // Check and make sure the sample dataset reflects the existing object IDs in each space. + // Instead of checking each object ID, we check the dashboard and canvas app link as representatives. + if (space === 'default') { + expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); + expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH); + } else { + // the sample data objects installed in the 'other' space had their IDs regenerated upon import + expect(flightsData.overviewDashboard).not.to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); + expect(flightsData.appLinks[0].path).not.to.be(FLIGHTS_CANVAS_APPLINK_PATH); + } + }); }); + } + + for (const space of SPACES) { + const apiPath = `/s/${space}/api/sample_data`; - it('should remove elasticsearch index containing sample data', async () => { - const resp = await es.indices.exists({ - index: 'kibana_sample_data_flights', + describe(`uninstall in the ${space} space`, () => { + it('should uninstall sample data', async () => { + // Note: the second time this happens, the index has already been removed, but the uninstall works anyway + await supertest.delete(`${apiPath}/flights`).set('kbn-xsrf', 'kibana').expect(204); + }); + + it('should remove elasticsearch index containing sample data', async () => { + const resp = await es.indices.exists({ + index: 'kibana_sample_data_flights', + }); + expect(resp).to.be(false); }); - expect(resp).to.be(false); }); - }); + + describe(`list in the ${space} space (after uninstall)`, () => { + it('should return list of sample data sets with installed status', async () => { + const resp = await supertest.get(apiPath).set('kbn-xsrf', 'kibana').expect(200); + + const flightsData = findFlightsData(resp); + expect(flightsData.status).to.be('not_installed'); + // Check and make sure the sample dataset reflects the default object IDs, because no sample data objects exist. + // Instead of checking each object ID, we check the dashboard and canvas app link as representatives. + expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); + expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH); + }); + }); + } }); } + +function findFlightsData(response: Response) { + expect(response.body).to.be.an('array'); + expect(response.body.length).to.be.above(0); + // @ts-expect-error Binding element 'id' implicitly has an 'any' type. + const flightsData = response.body.find(({ id }) => id === 'flights'); + if (!flightsData) { + throw new Error('Could not find flights data'); + } + return flightsData; +} diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index 0a8f56ee250eae..8374ccbc389f7d 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -28,16 +28,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover test', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); - - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); - + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); // and load a set of makelogs data await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); }); - + after(async () => { + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + }); describe('query', function () { const queryName1 = 'Query # 1'; diff --git a/test/functional/apps/visualize/_timelion.ts b/test/functional/apps/visualize/_timelion.ts index 631d2148d73c39..afbcba7df52167 100644 --- a/test/functional/apps/visualize/_timelion.ts +++ b/test/functional/apps/visualize/_timelion.ts @@ -23,6 +23,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const elasticChart = getService('elasticChart'); const find = getService('find'); + const retry = getService('retry'); const timelionChartSelector = 'timelionChart'; describe('Timelion visualization', () => { @@ -265,7 +266,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // wait for index patterns will be loaded await common.sleep(500); const suggestions = await timelion.getSuggestionItemsText(); - expect(suggestions.length).not.to.eql(0); expect(suggestions[0].includes('log')).to.eql(true); }); @@ -290,7 +290,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await common.sleep(300); const suggestions = await timelion.getSuggestionItemsText(); - expect(suggestions.length).not.to.eql(0); expect(suggestions[0].includes('@message.raw')).to.eql(true); }); @@ -299,9 +298,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { '.es(index=logstash-*, timefield=@timestamp, metric=avg:', 'timelionCodeEditor' ); - const suggestions = await timelion.getSuggestionItemsText(); - expect(suggestions.length).not.to.eql(0); - expect(suggestions[0].includes('avg:bytes')).to.eql(true); + // other suggestions might be shown for a short amount of time - retry until metric suggestions show up + await retry.try(async () => { + const suggestions = await timelion.getSuggestionItemsText(); + expect(suggestions[0].includes('avg:bytes')).to.eql(true); + }); }); }); }); diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index 4354e8bb441728..009e4a07cd42aa 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -193,8 +193,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/115529 - describe.skip('Elastic charts', () => { + describe('Elastic charts', () => { beforeEach(async () => { await visualBuilder.toggleNewChartsLibraryWithDebug(true); await visualBuilder.clickPanelOptions('timeSeries'); diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index a40465b00dbeb9..69fbf7e49df3c5 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -11,6 +11,7 @@ import expect from '@kbn/expect'; // @ts-ignore import fetch from 'node-fetch'; import { getUrl } from '@kbn/test'; +import moment from 'moment'; import { FtrService } from '../ftr_provider_context'; interface NavigateProps { @@ -502,11 +503,47 @@ export class CommonPageObject extends FtrService { } } - async setTime(time: { from: string; to: string }) { - await this.kibanaServer.uiSettings.replace({ 'timepicker:timeDefaults': JSON.stringify(time) }); + /** + * Due to a warning thrown, documented at: + * https://github.com/elastic/kibana/pull/114997#issuecomment-950823874 + * this fn formats time in a format specified, or defaulted + * to the same format in + * [getTimeDurationInHours()](https://github.com/elastic/kibana/blob/main/test/functional/page_objects/time_picker.ts#L256) + * @param time + * @param fmt + */ + formatTime(time: TimeStrings, fmt: string = 'MMM D, YYYY @ HH:mm:ss.SSS') { + return Object.keys(time) + .map((x) => moment(time[x], [fmt]).format()) + .reduce( + (acc, curr, idx) => { + if (idx === 0) acc.from = curr; + acc.to = curr; + return acc; + }, + { from: '', to: '' } + ); + } + + /** + * Previously, many tests were using the time picker. + * To speed things up, we are now setting time here. + * The formatting fn is called here, such that the tests + * that were using the time picker can use the same time + * parameters as before, but they are auto-formatted. + * @param time + */ + async setTime(time: TimeStrings) { + await this.kibanaServer.uiSettings.replace({ + 'timepicker:timeDefaults': JSON.stringify(this.formatTime(time)), + }); } async unsetTime() { await this.kibanaServer.uiSettings.unset('timepicker:timeDefaults'); } } +export interface TimeStrings extends Record { + from: string; + to: string; +} diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 385d250fe761db..b87962b34291c1 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -506,12 +506,19 @@ export class VisualBuilderPageObject extends FtrService { } public async toggleIndexPatternSelectionModePopover(shouldOpen: boolean) { - const isPopoverOpened = await this.testSubjects.exists( - 'switchIndexPatternSelectionModePopoverContent' - ); - if ((shouldOpen && !isPopoverOpened) || (!shouldOpen && isPopoverOpened)) { - await this.testSubjects.click('switchIndexPatternSelectionModePopoverButton'); - } + await this.retry.try(async () => { + const isPopoverOpened = await this.testSubjects.exists( + 'switchIndexPatternSelectionModePopoverContent' + ); + if ((shouldOpen && !isPopoverOpened) || (!shouldOpen && isPopoverOpened)) { + await this.testSubjects.click('switchIndexPatternSelectionModePopoverButton'); + } + if (shouldOpen) { + await this.testSubjects.existOrFail('switchIndexPatternSelectionModePopoverContent'); + } else { + await this.testSubjects.missingOrFail('switchIndexPatternSelectionModePopoverContent'); + } + }); } public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) { @@ -657,7 +664,10 @@ export class VisualBuilderPageObject extends FtrService { public async setBackgroundColor(colorHex: string): Promise { await this.clickColorPicker(); await this.checkColorPickerPopUpIsPresent(); - await this.find.setValue('.euiColorPicker input', colorHex); + await this.testSubjects.setValue('euiColorPickerInput_top', colorHex, { + clearWithKeyboard: true, + typeCharByChar: true, + }); await this.clickColorPicker(); await this.visChart.waitForVisualizationRenderingStabilized(); } @@ -670,7 +680,10 @@ export class VisualBuilderPageObject extends FtrService { public async setColorPickerValue(colorHex: string, nth: number = 0): Promise { await this.clickColorPicker(nth); await this.checkColorPickerPopUpIsPresent(); - await this.find.setValue('.euiColorPicker input', colorHex); + await this.testSubjects.setValue('euiColorPickerInput_top', colorHex, { + clearWithKeyboard: true, + typeCharByChar: true, + }); await this.clickColorPicker(nth); await this.visChart.waitForVisualizationRenderingStabilized(); } diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 7356ea3fa44c33..7624699928666a 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -315,7 +315,10 @@ export class VisualizePageObject extends FtrService { public async openSavedVisualization(vizName: string) { const dataTestSubj = `visListingTitleLink-${vizName.split(' ').join('-')}`; - await this.testSubjects.click(dataTestSubj, 20000); + await this.retry.try(async () => { + await this.testSubjects.click(dataTestSubj, 20000); + await this.notOnLandingPageOrFail(); + }); await this.header.waitUntilLoadingHasFinished(); } @@ -337,6 +340,11 @@ export class VisualizePageObject extends FtrService { return await this.testSubjects.exists('visualizationLandingPage'); } + public async notOnLandingPageOrFail() { + this.log.debug(`VisualizePage.notOnLandingPageOrFail`); + return await this.testSubjects.missingOrFail('visualizationLandingPage'); + } + public async gotoLandingPage() { this.log.debug('VisualizePage.gotoLandingPage'); const onPage = await this.onLandingPage(); diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index 6706db82ce7086..88201b0ec7e19d 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -46,7 +46,9 @@ export class ComboBoxService extends FtrService { */ private async clickOption(isMouseClick: boolean, element: WebElementWrapper): Promise { // element.click causes scrollIntoView which causes combobox to close, using _webElement.click instead - return isMouseClick ? await element.clickMouseButton() : await element._webElement.click(); + await this.retry.try(async () => { + return isMouseClick ? await element.clickMouseButton() : await element._webElement.click(); + }); } /** diff --git a/test/functional/services/common/index.ts b/test/functional/services/common/index.ts index 95f58027fd5fbc..c5f442c1915439 100644 --- a/test/functional/services/common/index.ts +++ b/test/functional/services/common/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -export { BrowserProvider, Browser } from './browser'; +export type { Browser } from './browser'; +export { BrowserProvider } from './browser'; export { FailureDebuggingProvider } from './failure_debugging'; export { FindProvider } from './find'; export { ScreenshotsService } from './screenshots'; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts index 22afda2fdce1bf..897fbf832d5614 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts @@ -8,7 +8,7 @@ import { PluginInitializer, PluginInitializerContext } from 'src/core/public'; import { Plugin, StartDeps } from './plugin'; -export { StartDeps }; +export type { StartDeps }; export const plugin: PluginInitializer = ( initializerContext: PluginInitializerContext diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/types.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/types.ts index a9b597ebf1e055..02872843cf8bcc 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/types.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/types.ts @@ -9,4 +9,5 @@ import { ExpressionsStart, ExpressionRenderHandler } from 'src/plugins/expressions/public'; import { Adapters } from 'src/plugins/inspector/public'; -export { ExpressionsStart, ExpressionRenderHandler, Adapters }; +export type { ExpressionsStart, Adapters }; +export { ExpressionRenderHandler }; diff --git a/test/interpreter_functional/test_suites/run_pipeline/esaggs_timeshift.ts b/test/interpreter_functional/test_suites/run_pipeline/esaggs_timeshift.ts index 244d07d2cfc820..adfd724f063b49 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/esaggs_timeshift.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/esaggs_timeshift.ts @@ -12,6 +12,10 @@ import { ExpectExpression, expectExpressionProvider } from './helpers'; import { FtrProviderContext } from '../../../functional/ftr_provider_context'; function getCell(esaggsResult: any, row: number, column: number): unknown | undefined { + if (esaggsResult && !esaggsResult.columns) { + throw new Error(`Unexpected esaggs result: ${JSON.stringify(esaggsResult, undefined, ' ')}`); + } + const columnId = esaggsResult?.columns[column]?.id; if (!columnId) { return; @@ -37,8 +41,7 @@ export default function ({ }: FtrProviderContext & { updateBaselines: boolean }) { let expectExpression: ExpectExpression; - // FLAKY https://github.com/elastic/kibana/issues/107028 - describe.skip('esaggs timeshift tests', () => { + describe('esaggs timeshift tests', () => { before(() => { expectExpression = expectExpressionProvider({ getService, updateBaselines }); }); @@ -98,6 +101,7 @@ export default function ({ 'esaggs_shift_single_percentile', expression ).getResponse(); + // percentile is not stable expect(getCell(result, 0, 0)).to.be.within(10000, 20000); expect(getCell(result, 0, 1)).to.be.within(10000, 20000); diff --git a/test/plugin_functional/plugins/core_plugin_a/server/index.ts b/test/plugin_functional/plugins/core_plugin_a/server/index.ts index f4b9f96f1bcdda..7fad3cf7a9e3bb 100644 --- a/test/plugin_functional/plugins/core_plugin_a/server/index.ts +++ b/test/plugin_functional/plugins/core_plugin_a/server/index.ts @@ -7,6 +7,6 @@ */ import { CorePluginAPlugin } from './plugin'; -export { PluginAApiRequestContext } from './plugin'; +export type { PluginAApiRequestContext } from './plugin'; export const plugin = () => new CorePluginAPlugin(); diff --git a/test/plugin_functional/test_suites/core_plugins/execution_context.ts b/test/plugin_functional/test_suites/core_plugins/execution_context.ts index 7dc9922dca51d8..6d1da821d3daa7 100644 --- a/test/plugin_functional/test_suites/core_plugins/execution_context.ts +++ b/test/plugin_functional/test_suites/core_plugins/execution_context.ts @@ -33,9 +33,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide description: 'какое-то странное описание', }; - const result = await coreStart.http.get('/execution_context/pass', { - context, - }); + const result = await coreStart.http.get<{ ['x-opaque-id']: string }>( + '/execution_context/pass', + { context } + ); return result['x-opaque-id']; }) diff --git a/test/scripts/jenkins_fleet_cypress.sh b/test/scripts/jenkins_fleet_cypress.sh new file mode 100755 index 00000000000000..085c78cbf0a412 --- /dev/null +++ b/test/scripts/jenkins_fleet_cypress.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +source test/scripts/jenkins_test_setup_xpack.sh + +echo " -> Running fleet cypress tests" +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "Fleet Cypress Tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --config test/fleet_cypress/cli_config.ts + +echo "" +echo "" diff --git a/typings/@elastic/eui/index.d.ts b/typings/@elastic/eui/index.d.ts index f5baf73df70571..814a386a3b5e70 100644 --- a/typings/@elastic/eui/index.d.ts +++ b/typings/@elastic/eui/index.d.ts @@ -11,7 +11,3 @@ declare module '@elastic/eui/lib/services' { export const RIGHT_ALIGNMENT: any; } - -declare module '@elastic/eui/lib/services/format' { - export const dateFormatAliases: any; -} diff --git a/vars/tasks.groovy b/vars/tasks.groovy index 050b62646fb3bc..c6d926287750c9 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -164,6 +164,14 @@ def functionalXpack(Map params = [:]) { task(kibanaPipeline.functionalTestProcess('xpack-UptimePlaywright', './test/scripts/jenkins_uptime_playwright.sh')) } } + + whenChanged([ + 'x-pack/plugins/fleet/', + ]) { + if (githubPr.isPr()) { + task(kibanaPipeline.functionalTestProcess('xpack-FleetCypress', './test/scripts/jenkins_fleet_cypress.sh')) + } + } } } diff --git a/x-pack/examples/alerting_example/public/components/view_alert.tsx b/x-pack/examples/alerting_example/public/components/view_alert.tsx index 40eeb9fd360dca..5f3581871e2bd5 100644 --- a/x-pack/examples/alerting_example/public/components/view_alert.tsx +++ b/x-pack/examples/alerting_example/public/components/view_alert.tsx @@ -38,10 +38,12 @@ export const ViewAlertPage = withRouter(({ http, id }: Props) => { useEffect(() => { if (!alert) { - http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert); + http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert); } if (!alertState) { - http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`).then(setAlertState); + http + .get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`) + .then(setAlertState); } }, [alert, alertState, http, id]); diff --git a/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx b/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx index 8eef1882b93899..1bab422c2bcf03 100644 --- a/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx +++ b/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx @@ -44,10 +44,14 @@ export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => { useEffect(() => { if (!alert) { - http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert); + http + .get | null>(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`) + .then(setAlert); } if (!alertState) { - http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`).then(setAlertState); + http + .get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`) + .then(setAlertState); } }, [alert, alertState, http, id]); diff --git a/x-pack/examples/alerting_example/public/plugin.tsx b/x-pack/examples/alerting_example/public/plugin.tsx index 242fd6e1fd2f55..25b6800ade2e8f 100644 --- a/x-pack/examples/alerting_example/public/plugin.tsx +++ b/x-pack/examples/alerting_example/public/plugin.tsx @@ -67,7 +67,7 @@ export class AlertingExamplePlugin implements Plugin { + const getPDFJobParamsDefault = (): JobAppParamsPDF => { return { layout: { id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT, diff --git a/x-pack/examples/reporting_example/public/index.ts b/x-pack/examples/reporting_example/public/index.ts index f9f749e2b0cd02..c4c0a9b48f2cb6 100644 --- a/x-pack/examples/reporting_example/public/index.ts +++ b/x-pack/examples/reporting_example/public/index.ts @@ -10,4 +10,4 @@ import { ReportingExamplePlugin } from './plugin'; export function plugin() { return new ReportingExamplePlugin(); } -export { PluginSetup, PluginStart } from './types'; +export type { PluginSetup, PluginStart } from './types'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/index.ts b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/index.ts index f6e1f288641401..f6c6f48e1774cb 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/index.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/index.ts @@ -6,11 +6,9 @@ */ export { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants'; -export { - DashboardToDiscoverDrilldown, - Params as DashboardToDiscoverDrilldownParams, -} from './drilldown'; -export { +export type { Params as DashboardToDiscoverDrilldownParams } from './drilldown'; +export { DashboardToDiscoverDrilldown } from './drilldown'; +export type { ActionContext as DashboardToDiscoverActionContext, Config as DashboardToDiscoverConfig, } from './types'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts index d41b18addcca49..76cfe32ea9ec39 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts @@ -141,7 +141,7 @@ export class UiActionsEnhancedExamplesPlugin links: [ { label: 'README', - href: 'https://github.com/elastic/kibana/tree/master/x-pack/examples/ui_actions_enhanced_examples#ui-actions-enhanced-examples', + href: 'https://github.com/elastic/kibana/tree/main/x-pack/examples/ui_actions_enhanced_examples#ui-actions-enhanced-examples', iconType: 'logoGithub', size: 's', target: '_blank', diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts index ddf0e126116a57..ea469c01decbbb 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts @@ -8,8 +8,6 @@ import { KibanaRequest } from 'kibana/server'; import { securityMock } from '../../../../plugins/security/server/mocks'; import { ActionsAuthorization } from './actions_authorization'; -import { actionsAuthorizationAuditLoggerMock } from './audit_logger.mock'; -import { ActionsAuthorizationAuditLogger, AuthorizationResult } from './audit_logger'; import { ACTION_SAVED_OBJECT_TYPE, ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, @@ -19,9 +17,6 @@ import { AuthorizationMode } from './get_authorization_mode_by_source'; const request = {} as KibanaRequest; -const auditLogger = actionsAuthorizationAuditLoggerMock.create(); -const realAuditLogger = new ActionsAuthorizationAuditLogger(); - const mockAuthorizationAction = (type: string, operation: string) => `${type}/${operation}`; function mockSecurity() { const security = securityMock.createSetup(); @@ -39,19 +34,12 @@ function mockSecurity() { beforeEach(() => { jest.resetAllMocks(); - auditLogger.actionsAuthorizationFailure.mockImplementation((username, ...args) => - realAuditLogger.getAuthorizationMessage(AuthorizationResult.Unauthorized, ...args) - ); - auditLogger.actionsAuthorizationSuccess.mockImplementation((username, ...args) => - realAuditLogger.getAuthorizationMessage(AuthorizationResult.Authorized, ...args) - ); }); describe('ensureAuthorized', () => { test('is a no-op when there is no authorization api', async () => { const actionsAuthorization = new ActionsAuthorization({ request, - auditLogger, }); await actionsAuthorization.ensureAuthorized('create', 'myType'); @@ -63,7 +51,6 @@ describe('ensureAuthorized', () => { const actionsAuthorization = new ActionsAuthorization({ request, authorization, - auditLogger, }); await actionsAuthorization.ensureAuthorized('create', 'myType'); @@ -78,7 +65,6 @@ describe('ensureAuthorized', () => { const actionsAuthorization = new ActionsAuthorization({ request, authorization, - auditLogger, }); checkPrivileges.mockResolvedValueOnce({ @@ -98,16 +84,6 @@ describe('ensureAuthorized', () => { expect(checkPrivileges).toHaveBeenCalledWith({ kibana: mockAuthorizationAction('action', 'create'), }); - - expect(auditLogger.actionsAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.actionsAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.actionsAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "create", - "myType", - ] - `); }); test('ensures the user has privileges to execute an Actions Saved Object type', async () => { @@ -119,7 +95,6 @@ describe('ensureAuthorized', () => { const actionsAuthorization = new ActionsAuthorization({ request, authorization, - auditLogger, }); checkPrivileges.mockResolvedValueOnce({ @@ -149,16 +124,6 @@ describe('ensureAuthorized', () => { mockAuthorizationAction(ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, 'create'), ], }); - - expect(auditLogger.actionsAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.actionsAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.actionsAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "execute", - "myType", - ] - `); }); test('throws if user lacks the required privieleges', async () => { @@ -170,7 +135,6 @@ describe('ensureAuthorized', () => { const actionsAuthorization = new ActionsAuthorization({ request, authorization, - auditLogger, }); checkPrivileges.mockResolvedValueOnce({ @@ -191,16 +155,6 @@ describe('ensureAuthorized', () => { await expect( actionsAuthorization.ensureAuthorized('create', 'myType') ).rejects.toThrowErrorMatchingInlineSnapshot(`"Unauthorized to create a \\"myType\\" action"`); - - expect(auditLogger.actionsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.actionsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.actionsAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "create", - "myType", - ] - `); }); test('exempts users from requiring privileges to execute actions when authorizationMode is Legacy', async () => { @@ -213,7 +167,6 @@ describe('ensureAuthorized', () => { request, authorization, authentication, - auditLogger, authorizationMode: AuthorizationMode.Legacy, }); @@ -225,15 +178,5 @@ describe('ensureAuthorized', () => { expect(authorization.actions.savedObject.get).not.toHaveBeenCalled(); expect(checkPrivileges).not.toHaveBeenCalled(); - - expect(auditLogger.actionsAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.actionsAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.actionsAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "execute", - "myType", - ] - `); }); }); diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.ts index 073778c2987237..8f7885b81b2847 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.ts @@ -8,7 +8,6 @@ import Boom from '@hapi/boom'; import { KibanaRequest } from 'src/core/server'; import { SecurityPluginSetup } from '../../../security/server'; -import { ActionsAuthorizationAuditLogger } from './audit_logger'; import { ACTION_SAVED_OBJECT_TYPE, ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, @@ -17,7 +16,6 @@ import { AuthorizationMode } from './get_authorization_mode_by_source'; export interface ConstructorOptions { request: KibanaRequest; - auditLogger: ActionsAuthorizationAuditLogger; authorization?: SecurityPluginSetup['authz']; authentication?: SecurityPluginSetup['authc']; // In order to support legacy Alerts which predate the introduction of the @@ -46,44 +44,33 @@ const LEGACY_RBAC_EXEMPT_OPERATIONS = new Set(['get', 'execute']); export class ActionsAuthorization { private readonly request: KibanaRequest; private readonly authorization?: SecurityPluginSetup['authz']; - private readonly authentication?: SecurityPluginSetup['authc']; - private readonly auditLogger: ActionsAuthorizationAuditLogger; private readonly authorizationMode: AuthorizationMode; constructor({ request, authorization, authentication, - auditLogger, authorizationMode = AuthorizationMode.RBAC, }: ConstructorOptions) { this.request = request; this.authorization = authorization; - this.authentication = authentication; - this.auditLogger = auditLogger; this.authorizationMode = authorizationMode; } public async ensureAuthorized(operation: string, actionTypeId?: string) { const { authorization } = this; if (authorization?.mode?.useRbacForRequest(this.request)) { - if (this.isOperationExemptDueToLegacyRbac(operation)) { - this.auditLogger.actionsAuthorizationSuccess( - this.authentication?.getCurrentUser(this.request)?.username ?? '', - operation, - actionTypeId - ); - } else { + if (!this.isOperationExemptDueToLegacyRbac(operation)) { const checkPrivileges = authorization.checkPrivilegesDynamicallyWithRequest(this.request); - const { hasAllRequested, username } = await checkPrivileges({ + const { hasAllRequested } = await checkPrivileges({ kibana: operationAlias[operation] ? operationAlias[operation](authorization) : authorization.actions.savedObject.get(ACTION_SAVED_OBJECT_TYPE, operation), }); - if (hasAllRequested) { - this.auditLogger.actionsAuthorizationSuccess(username, operation, actionTypeId); - } else { + if (!hasAllRequested) { throw Boom.forbidden( - this.auditLogger.actionsAuthorizationFailure(username, operation, actionTypeId) + `Unauthorized to ${operation} ${ + actionTypeId ? `a "${actionTypeId}" action` : `actions` + }` ); } } diff --git a/x-pack/plugins/actions/server/authorization/audit_logger.mock.ts b/x-pack/plugins/actions/server/authorization/audit_logger.mock.ts deleted file mode 100644 index 0be62fc0f35f2b..00000000000000 --- a/x-pack/plugins/actions/server/authorization/audit_logger.mock.ts +++ /dev/null @@ -1,23 +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 { ActionsAuthorizationAuditLogger } from './audit_logger'; - -const createActionsAuthorizationAuditLoggerMock = () => { - const mocked = { - getAuthorizationMessage: jest.fn(), - actionsAuthorizationFailure: jest.fn(), - actionsAuthorizationSuccess: jest.fn(), - } as unknown as jest.Mocked; - return mocked; -}; - -export const actionsAuthorizationAuditLoggerMock: { - create: () => jest.Mocked; -} = { - create: createActionsAuthorizationAuditLoggerMock, -}; diff --git a/x-pack/plugins/actions/server/authorization/audit_logger.test.ts b/x-pack/plugins/actions/server/authorization/audit_logger.test.ts deleted file mode 100644 index e304e7368a5148..00000000000000 --- a/x-pack/plugins/actions/server/authorization/audit_logger.test.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ActionsAuthorizationAuditLogger } from './audit_logger'; - -const createMockAuditLogger = () => { - return { - log: jest.fn(), - }; -}; - -describe(`#constructor`, () => { - test('initializes a noop auditLogger if security logger is unavailable', () => { - const actionsAuditLogger = new ActionsAuthorizationAuditLogger(undefined); - - const username = 'foo-user'; - const actionTypeId = 'action-type-id'; - const operation = 'create'; - expect(() => { - actionsAuditLogger.actionsAuthorizationFailure(username, operation, actionTypeId); - actionsAuditLogger.actionsAuthorizationSuccess(username, operation, actionTypeId); - }).not.toThrow(); - }); -}); - -describe(`#actionsAuthorizationFailure`, () => { - test('logs auth failure', () => { - const auditLogger = createMockAuditLogger(); - const actionsAuditLogger = new ActionsAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const actionTypeId = 'action-type-id'; - const operation = 'create'; - - actionsAuditLogger.actionsAuthorizationFailure(username, operation, actionTypeId); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "actions_authorization_failure", - "foo-user Unauthorized to create a \\"action-type-id\\" action", - Object { - "actionTypeId": "action-type-id", - "operation": "create", - "username": "foo-user", - }, - ] - `); - }); -}); - -describe(`#savedObjectsAuthorizationSuccess`, () => { - test('logs auth success', () => { - const auditLogger = createMockAuditLogger(); - const actionsAuditLogger = new ActionsAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const actionTypeId = 'action-type-id'; - - const operation = 'create'; - - actionsAuditLogger.actionsAuthorizationSuccess(username, operation, actionTypeId); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "actions_authorization_success", - "foo-user Authorized to create a \\"action-type-id\\" action", - Object { - "actionTypeId": "action-type-id", - "operation": "create", - "username": "foo-user", - }, - ] - `); - }); -}); diff --git a/x-pack/plugins/actions/server/authorization/audit_logger.ts b/x-pack/plugins/actions/server/authorization/audit_logger.ts deleted file mode 100644 index ce2c6a63875626..00000000000000 --- a/x-pack/plugins/actions/server/authorization/audit_logger.ts +++ /dev/null @@ -1,67 +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 { LegacyAuditLogger } from '../../../security/server'; - -export enum AuthorizationResult { - Unauthorized = 'Unauthorized', - Authorized = 'Authorized', -} - -export class ActionsAuthorizationAuditLogger { - private readonly auditLogger: LegacyAuditLogger; - - constructor(auditLogger: LegacyAuditLogger = { log() {} }) { - this.auditLogger = auditLogger; - } - - public getAuthorizationMessage( - authorizationResult: AuthorizationResult, - operation: string, - actionTypeId?: string - ): string { - return `${authorizationResult} to ${operation} ${ - actionTypeId ? `a "${actionTypeId}" action` : `actions` - }`; - } - - public actionsAuthorizationFailure( - username: string, - operation: string, - actionTypeId?: string - ): string { - const message = this.getAuthorizationMessage( - AuthorizationResult.Unauthorized, - operation, - actionTypeId - ); - this.auditLogger.log('actions_authorization_failure', `${username} ${message}`, { - username, - actionTypeId, - operation, - }); - return message; - } - - public actionsAuthorizationSuccess( - username: string, - operation: string, - actionTypeId?: string - ): string { - const message = this.getAuthorizationMessage( - AuthorizationResult.Authorized, - operation, - actionTypeId - ); - this.auditLogger.log('actions_authorization_success', `${username} ${message}`, { - username, - actionTypeId, - operation, - }); - return message; - } -} diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.ts b/x-pack/plugins/actions/server/builtin_action_types/index.ts index 3351a36b38344b..9f48a45fc4664e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/index.ts @@ -24,37 +24,30 @@ import { import { getActionType as getJiraActionType } from './jira'; import { getActionType as getResilientActionType } from './resilient'; import { getActionType as getTeamsActionType } from './teams'; -import { ENABLE_ITOM } from '../constants/connectors'; -export { ActionParamsType as EmailActionParams, ActionTypeId as EmailActionTypeId } from './email'; +export type { ActionParamsType as EmailActionParams } from './email'; +export { ActionTypeId as EmailActionTypeId } from './email'; +export type { ActionParamsType as IndexActionParams } from './es_index'; +export { ActionTypeId as IndexActionTypeId } from './es_index'; +export type { ActionParamsType as PagerDutyActionParams } from './pagerduty'; +export { ActionTypeId as PagerDutyActionTypeId } from './pagerduty'; +export type { ActionParamsType as ServerLogActionParams } from './server_log'; +export { ActionTypeId as ServerLogActionTypeId } from './server_log'; +export type { ActionParamsType as SlackActionParams } from './slack'; +export { ActionTypeId as SlackActionTypeId } from './slack'; +export type { ActionParamsType as WebhookActionParams } from './webhook'; +export { ActionTypeId as WebhookActionTypeId } from './webhook'; +export type { ActionParamsType as ServiceNowActionParams } from './servicenow'; export { - ActionParamsType as IndexActionParams, - ActionTypeId as IndexActionTypeId, -} from './es_index'; -export { - ActionParamsType as PagerDutyActionParams, - ActionTypeId as PagerDutyActionTypeId, -} from './pagerduty'; -export { - ActionParamsType as ServerLogActionParams, - ActionTypeId as ServerLogActionTypeId, -} from './server_log'; -export { ActionParamsType as SlackActionParams, ActionTypeId as SlackActionTypeId } from './slack'; -export { - ActionParamsType as WebhookActionParams, - ActionTypeId as WebhookActionTypeId, -} from './webhook'; -export { - ActionParamsType as ServiceNowActionParams, ServiceNowITSMActionTypeId, ServiceNowSIRActionTypeId, ServiceNowITOMActionTypeId, } from './servicenow'; -export { ActionParamsType as JiraActionParams, ActionTypeId as JiraActionTypeId } from './jira'; -export { - ActionParamsType as ResilientActionParams, - ActionTypeId as ResilientActionTypeId, -} from './resilient'; -export { ActionParamsType as TeamsActionParams, ActionTypeId as TeamsActionTypeId } from './teams'; +export type { ActionParamsType as JiraActionParams } from './jira'; +export { ActionTypeId as JiraActionTypeId } from './jira'; +export type { ActionParamsType as ResilientActionParams } from './resilient'; +export { ActionTypeId as ResilientActionTypeId } from './resilient'; +export type { ActionParamsType as TeamsActionParams } from './teams'; +export { ActionTypeId as TeamsActionTypeId } from './teams'; export function registerBuiltInActionTypes({ actionsConfigUtils: configurationUtilities, @@ -78,12 +71,8 @@ export function registerBuiltInActionTypes({ actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getServiceNowITSMActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getServiceNowSIRActionType({ logger, configurationUtilities })); + actionTypeRegistry.register(getServiceNowITOMActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getJiraActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getTeamsActionType({ logger, configurationUtilities })); - - // TODO: Remove when ITOM is ready - if (ENABLE_ITOM) { - actionTypeRegistry.register(getServiceNowITOMActionType({ logger, configurationUtilities })); - } } diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts index 41f723bc9e2aa2..26550f17326553 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts @@ -44,7 +44,7 @@ describe('config', () => { importSetTable: 'x_elas2_inc_int_elastic_incident', appScope: 'x_elas2_inc_int', table: 'em_event', - useImportAPI: true, + useImportAPI: false, commentFieldKey: 'work_notes', }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts index 52d2eb7662f53f..ba29bcc39b25a8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts @@ -5,11 +5,6 @@ * 2.0. */ -import { - ENABLE_ITOM, - ENABLE_NEW_SN_ITSM_CONNECTOR, - ENABLE_NEW_SN_SIR_CONNECTOR, -} from '../../constants/connectors'; import { SNProductsConfig } from './types'; export const serviceNowITSMTable = 'incident'; @@ -24,21 +19,21 @@ export const snExternalServiceConfig: SNProductsConfig = { importSetTable: 'x_elas2_inc_int_elastic_incident', appScope: 'x_elas2_inc_int', table: 'incident', - useImportAPI: ENABLE_NEW_SN_ITSM_CONNECTOR, + useImportAPI: true, commentFieldKey: 'work_notes', }, '.servicenow-sir': { importSetTable: 'x_elas2_sir_int_elastic_si_incident', appScope: 'x_elas2_sir_int', table: 'sn_si_incident', - useImportAPI: ENABLE_NEW_SN_SIR_CONNECTOR, + useImportAPI: true, commentFieldKey: 'work_notes', }, '.servicenow-itom': { importSetTable: 'x_elas2_inc_int_elastic_incident', appScope: 'x_elas2_inc_int', table: 'em_event', - useImportAPI: ENABLE_ITOM, + useImportAPI: false, commentFieldKey: 'work_notes', }, }; diff --git a/x-pack/plugins/actions/server/constants/connectors.ts b/x-pack/plugins/actions/server/constants/connectors.ts deleted file mode 100644 index 94324e4d82bc24..00000000000000 --- a/x-pack/plugins/actions/server/constants/connectors.ts +++ /dev/null @@ -1,15 +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. - */ - -// TODO: Remove when Elastic for ITSM is published. -export const ENABLE_NEW_SN_ITSM_CONNECTOR = true; - -// TODO: Remove when Elastic for Security Operations is published. -export const ENABLE_NEW_SN_SIR_CONNECTOR = true; - -// TODO: Remove when ready -export const ENABLE_ITOM = true; diff --git a/x-pack/plugins/actions/server/index.test.ts b/x-pack/plugins/actions/server/index.test.ts index 9021879fa38aa9..fe8cf98a48f43b 100644 --- a/x-pack/plugins/actions/server/index.test.ts +++ b/x-pack/plugins/actions/server/index.test.ts @@ -51,10 +51,10 @@ describe('index', () => { '"xpack.actions.customHostSettings[].ssl.rejectUnauthorized" is deprecated.Use "xpack.actions.customHostSettings[].ssl.verificationMode" instead, with the setting "verificationMode:full" eql to "rejectUnauthorized:true", and "verificationMode:none" eql to "rejectUnauthorized:false".' ); expect(messages[1]).toBe( - '"xpack.actions.rejectUnauthorized" is deprecated. Use "xpack.actions.verificationMode" instead, with the setting "verificationMode:full" eql to "rejectUnauthorized:true", and "verificationMode:none" eql to "rejectUnauthorized:false".' + '"xpack.actions.rejectUnauthorized" is deprecated. Use "xpack.actions.ssl.verificationMode" instead, with the setting "verificationMode:full" eql to "rejectUnauthorized:true", and "verificationMode:none" eql to "rejectUnauthorized:false".' ); expect(messages[2]).toBe( - '"xpack.actions.proxyRejectUnauthorizedCertificates" is deprecated. Use "xpack.actions.proxyVerificationMode" instead, with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".' + '"xpack.actions.proxyRejectUnauthorizedCertificates" is deprecated. Use "xpack.actions.ssl.proxyVerificationMode" instead, with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".' ); }); }); diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index e6c82969a0aa28..e1c60b9fd0491a 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -103,13 +103,13 @@ export const config: PluginConfigDescriptor = { level: 'warning', configPath: `${fromPath}.rejectUnauthorized`, message: - `"xpack.actions.rejectUnauthorized" is deprecated. Use "xpack.actions.verificationMode" instead, ` + + `"xpack.actions.rejectUnauthorized" is deprecated. Use "xpack.actions.ssl.verificationMode" instead, ` + `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` + `and "verificationMode:none" eql to "rejectUnauthorized:false".`, correctiveActions: { manualSteps: [ `Remove "xpack.actions.rejectUnauthorized" from your kibana configs.`, - `Use "xpack.actions.verificationMode" ` + + `Use "xpack.actions.ssl.verificationMode" ` + `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` + `and "verificationMode:none" eql to "rejectUnauthorized:false".`, ], @@ -131,13 +131,13 @@ export const config: PluginConfigDescriptor = { level: 'warning', configPath: `${fromPath}.proxyRejectUnauthorizedCertificates`, message: - `"xpack.actions.proxyRejectUnauthorizedCertificates" is deprecated. Use "xpack.actions.proxyVerificationMode" instead, ` + + `"xpack.actions.proxyRejectUnauthorizedCertificates" is deprecated. Use "xpack.actions.ssl.proxyVerificationMode" instead, ` + `with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",` + `and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".`, correctiveActions: { manualSteps: [ `Remove "xpack.actions.proxyRejectUnauthorizedCertificates" from your kibana configs.`, - `Use "xpack.actions.proxyVerificationMode" ` + + `Use "xpack.actions.ssl.proxyVerificationMode" ` + `with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",` + `and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".`, ], diff --git a/x-pack/plugins/actions/server/lib/errors/index.ts b/x-pack/plugins/actions/server/lib/errors/index.ts index e4035213359a36..bae42db6dd1e94 100644 --- a/x-pack/plugins/actions/server/lib/errors/index.ts +++ b/x-pack/plugins/actions/server/lib/errors/index.ts @@ -13,4 +13,5 @@ export function isErrorThatHandlesItsOwnResponse( return typeof (e as ErrorThatHandlesItsOwnResponse).sendResponse === 'function'; } -export { ActionTypeDisabledError, ActionTypeDisabledReason } from './action_type_disabled'; +export type { ActionTypeDisabledReason } from './action_type_disabled'; +export { ActionTypeDisabledError } from './action_type_disabled'; diff --git a/x-pack/plugins/actions/server/lib/index.ts b/x-pack/plugins/actions/server/lib/index.ts index c52a8b14ee6d8b..d981b3bcc82e04 100644 --- a/x-pack/plugins/actions/server/lib/index.ts +++ b/x-pack/plugins/actions/server/lib/index.ts @@ -13,8 +13,10 @@ export { validateConnector, } from './validate_with_schema'; export { TaskRunnerFactory } from './task_runner_factory'; -export { ActionExecutor, ActionExecutorContract } from './action_executor'; -export { ILicenseState, LicenseState } from './license_state'; +export type { ActionExecutorContract } from './action_executor'; +export { ActionExecutor } from './action_executor'; +export type { ILicenseState } from './license_state'; +export { LicenseState } from './license_state'; export { verifyApiAccess } from './verify_api_access'; export { getActionTypeFeatureUsageName } from './get_action_type_feature_usage_name'; export { spaceIdToNamespace } from './space_id_to_namespace'; @@ -22,13 +24,10 @@ export { extractSavedObjectReferences, injectSavedObjectReferences, } from './action_task_params_utils'; +export type { ActionTypeDisabledReason } from './errors'; +export { ActionTypeDisabledError, isErrorThatHandlesItsOwnResponse } from './errors'; +export type { ActionExecutionSource } from './action_execution_source'; export { - ActionTypeDisabledError, - ActionTypeDisabledReason, - isErrorThatHandlesItsOwnResponse, -} from './errors'; -export { - ActionExecutionSource, asSavedObjectExecutionSource, isSavedObjectExecutionSource, asHttpRequestExecutionSource, diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 2942c7492906a8..bbf00572935faa 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -74,7 +74,6 @@ import { import { setupSavedObjects } from './saved_objects'; import { ACTIONS_FEATURE } from './feature'; import { ActionsAuthorization } from './authorization/actions_authorization'; -import { ActionsAuthorizationAuditLogger } from './authorization/audit_logger'; import { ActionExecutionSource } from './lib/action_execution_source'; import { getAuthorizationModeBySource, @@ -269,7 +268,8 @@ export class ActionsPlugin implements Plugin = Pick>; export type GetServicesFunction = (request: KibanaRequest) => Services; export type ActionTypeRegistryContract = PublicMethodsOf; diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts index 229f06f2e47fa5..07f7f833338449 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts @@ -7,7 +7,7 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../src/core/server/elasticsearch/client/mocks'; -import { getInUseTotalCount, getTotalCount } from './actions_telemetry'; +import { getExecutionsPerDayCount, getInUseTotalCount, getTotalCount } from './actions_telemetry'; describe('actions telemetry', () => { test('getTotalCount should replace first symbol . to __ for action types names', async () => { @@ -604,4 +604,102 @@ Object { } `); }); + + test('getExecutionsTotalCount', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockReturnValueOnce( + // @ts-expect-error not full search response + elasticsearchClientMock.createSuccessTransportRequestPromise({ + aggregations: { + totalExecutions: { + byConnectorTypeId: { + value: { + connectorTypes: { + '.slack': 100, + '.server-log': 20, + }, + total: 120, + }, + }, + }, + failedExecutions: { + refs: { + byConnectorTypeId: { + value: { + connectorTypes: { + '.slack': 7, + }, + total: 7, + }, + }, + }, + }, + avgDuration: { value: 10 }, + avgDurationByType: { + doc_count: 216, + actionSavedObjects: { + doc_count: 108, + byTypeId: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: '.server-log', + doc_count: 99, + refs: { + doc_count: 99, + avgDuration: { + value: 919191.9191919192, + }, + }, + }, + { + key: '.email', + doc_count: 9, + refs: { + doc_count: 9, + avgDuration: { + value: 4.196666666666667e8, + }, + }, + }, + ], + }, + }, + }, + }, + }) + ); + + // for .slack connectors + mockEsClient.search.mockReturnValueOnce( + // @ts-expect-error not full search response + elasticsearchClientMock.createSuccessTransportRequestPromise({ + aggregations: { + avgDuration: { value: 10 }, + }, + }) + ); + const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test'); + + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(telemetry).toStrictEqual({ + avgExecutionTime: 0, + avgExecutionTimeByType: { + '__server-log': 919191.9191919192, + __email: 419666666.6666667, + }, + + countByType: { + __slack: 100, + + '__server-log': 20, + }, + countFailed: 7, + countFailedByType: { + __slack: 7, + }, + countTotal: 120, + }); + }); }); diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index ab72352d460e35..d288611af5e212 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -379,4 +379,184 @@ function replaceFirstAndLastDotSymbols(strToReplace: string) { return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString; } -// TODO: Implement executions count telemetry with eventLog, when it will write to index +export async function getExecutionsPerDayCount( + esClient: ElasticsearchClient, + eventLogIndex: string +): Promise<{ + countTotal: number; + countByType: Record; + countFailed: number; + countFailedByType: Record; + avgExecutionTime: number; + avgExecutionTimeByType: Record; +}> { + const scriptedMetric = { + scripted_metric: { + init_script: 'state.connectorTypes = [:]; state.total = 0;', + map_script: ` + if (doc['kibana.saved_objects.type'].value == 'action') { + String connectorType = doc['kibana.saved_objects.type_id'].value; + state.connectorTypes.put(connectorType, state.connectorTypes.containsKey(connectorType) ? state.connectorTypes.get(connectorType) + 1 : 1); + state.total++; + } + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map connectorTypes = [:]; + long total = 0; + for (state in states) { + if (state !== null) { + total += state.total; + for (String k : state.connectorTypes.keySet()) { + connectorTypes.put(k, connectorTypes.containsKey(k) ? connectorTypes.get(k) + state.connectorTypes.get(k) : state.connectorTypes.get(k)); + } + } + } + Map result = new HashMap(); + result.total = total; + result.connectorTypes = connectorTypes; + return result; + `, + }, + }; + + const { body: actionResults } = await esClient.search({ + index: eventLogIndex, + size: 0, + body: { + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { 'event.action': 'execute' }, + }, + { + term: { 'event.provider': 'actions' }, + }, + { + range: { + '@timestamp': { + gte: 'now-1d', + }, + }, + }, + ], + }, + }, + }, + }, + aggs: { + totalExecutions: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + byConnectorTypeId: scriptedMetric, + }, + }, + failedExecutions: { + filter: { + bool: { + filter: [ + { + term: { + 'event.outcome': 'failure', + }, + }, + ], + }, + }, + aggs: { + refs: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + byConnectorTypeId: scriptedMetric, + }, + }, + }, + }, + avgDuration: { avg: { field: 'event.duration' } }, + avgDurationByType: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + actionSavedObjects: { + filter: { term: { 'kibana.saved_objects.type': 'action' } }, + aggs: { + byTypeId: { + terms: { + field: 'kibana.saved_objects.type_id', + }, + aggs: { + refs: { + reverse_nested: {}, + aggs: { + avgDuration: { avg: { field: 'event.duration' } }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + + // @ts-expect-error aggegation type is not specified + const aggsExecutions = actionResults.aggregations.totalExecutions?.byConnectorTypeId.value; + // convert nanoseconds to milliseconds + const aggsAvgExecutionTime = Math.round( + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.avgDuration.value / (1000 * 1000) + ); + const aggsFailedExecutions = + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.failedExecutions?.refs?.byConnectorTypeId.value; + + const avgDurationByType = + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.avgDurationByType?.actionSavedObjects?.byTypeId?.buckets; + + const avgExecutionTimeByType: Record = avgDurationByType.reduce( + // @ts-expect-error aggegation type is not specified + (res: Record, bucket) => { + res[replaceFirstAndLastDotSymbols(bucket.key)] = bucket?.refs.avgDuration.value; + return res; + }, + {} + ); + + return { + countTotal: aggsExecutions.total, + countByType: Object.entries(aggsExecutions.connectorTypes).reduce( + (res: Record, [key, value]) => { + // @ts-expect-error aggegation type is not specified + res[replaceFirstAndLastDotSymbols(key)] = value; + return res; + }, + {} + ), + countFailed: aggsFailedExecutions.total, + countFailedByType: Object.entries(aggsFailedExecutions.connectorTypes).reduce( + (res: Record, [key, value]) => { + // @ts-expect-error aggegation type is not specified + res[replaceFirstAndLastDotSymbols(key)] = value; + return res; + }, + {} + ), + avgExecutionTime: aggsAvgExecutionTime, + avgExecutionTimeByType, + }; +} diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts index 9ba9d7390a7b61..3e690d18063d60 100644 --- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts +++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts @@ -37,8 +37,14 @@ export function createActionsUsageCollector( }, }, count_active_by_type: byTypeSchema, + count_actions_executions_per_day: { type: 'long' }, + count_actions_executions_by_type_per_day: byTypeSchema, count_active_email_connectors_by_service_type: byServiceProviderTypeSchema, count_actions_namespaces: { type: 'long' }, + count_actions_executions_failed_per_day: { type: 'long' }, + count_actions_executions_failed_by_type_per_day: byTypeSchema, + avg_execution_time_per_day: { type: 'long' }, + avg_execution_time_by_type_per_day: byTypeSchema, }, fetch: async () => { try { @@ -60,6 +66,12 @@ export function createActionsUsageCollector( count_active_by_type: {}, count_active_email_connectors_by_service_type: {}, count_actions_namespaces: 0, + count_actions_executions_per_day: 0, + count_actions_executions_by_type_per_day: {}, + count_actions_executions_failed_per_day: 0, + count_actions_executions_failed_by_type_per_day: {}, + avg_execution_time_per_day: 0, + avg_execution_time_by_type_per_day: {}, }; } }, diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index bacb9e5f725716..5ddcbab4261d1f 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -7,13 +7,14 @@ import { Logger, CoreSetup } from 'kibana/server'; import moment from 'moment'; +import { IEventLogService } from '../../../event_log/server'; import { RunContext, TaskManagerSetupContract, TaskManagerStartContract, } from '../../../task_manager/server'; import { PreConfiguredAction } from '../types'; -import { getTotalCount, getInUseTotalCount } from './actions_telemetry'; +import { getTotalCount, getInUseTotalCount, getExecutionsPerDayCount } from './actions_telemetry'; export const TELEMETRY_TASK_TYPE = 'actions_telemetry'; @@ -24,9 +25,17 @@ export function initializeActionsTelemetry( taskManager: TaskManagerSetupContract, core: CoreSetup, kibanaIndex: string, - preconfiguredActions: PreConfiguredAction[] + preconfiguredActions: PreConfiguredAction[], + eventLog: IEventLogService ) { - registerActionsTelemetryTask(logger, taskManager, core, kibanaIndex, preconfiguredActions); + registerActionsTelemetryTask( + logger, + taskManager, + core, + kibanaIndex, + preconfiguredActions, + eventLog + ); } export function scheduleActionsTelemetry(logger: Logger, taskManager: TaskManagerStartContract) { @@ -38,13 +47,20 @@ function registerActionsTelemetryTask( taskManager: TaskManagerSetupContract, core: CoreSetup, kibanaIndex: string, - preconfiguredActions: PreConfiguredAction[] + preconfiguredActions: PreConfiguredAction[], + eventLog: IEventLogService ) { taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Actions usage fetch task', timeout: '5m', - createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex, preconfiguredActions), + createTaskRunner: telemetryTaskRunner( + logger, + core, + kibanaIndex, + preconfiguredActions, + eventLog + ), }, }); } @@ -66,10 +82,12 @@ export function telemetryTaskRunner( logger: Logger, core: CoreSetup, kibanaIndex: string, - preconfiguredActions: PreConfiguredAction[] + preconfiguredActions: PreConfiguredAction[], + eventLog: IEventLogService ) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; + const eventLogIndex = eventLog.getIndexPattern(); const getEsClient = () => core.getStartServices().then( ([ @@ -84,8 +102,9 @@ export function telemetryTaskRunner( return Promise.all([ getTotalCount(esClient, kibanaIndex, preconfiguredActions), getInUseTotalCount(esClient, kibanaIndex, undefined, preconfiguredActions), + getExecutionsPerDayCount(esClient, eventLogIndex), ]) - .then(([totalAggegations, totalInUse]) => { + .then(([totalAggegations, totalInUse, totalExecutionsPerDay]) => { return { state: { runs: (state.runs || 0) + 1, @@ -96,6 +115,13 @@ export function telemetryTaskRunner( count_active_alert_history_connectors: totalInUse.countByAlertHistoryConnectorType, count_active_email_connectors_by_service_type: totalInUse.countEmailByService, count_actions_namespaces: totalInUse.countNamespaces, + count_actions_executions_per_day: totalExecutionsPerDay.countTotal, + count_actions_executions_by_type_per_day: totalExecutionsPerDay.countByType, + count_actions_executions_failed_per_day: totalExecutionsPerDay.countFailed, + count_actions_executions_failed_by_type_per_day: + totalExecutionsPerDay.countFailedByType, + avg_execution_time_per_day: totalExecutionsPerDay.avgExecutionTime, + avg_execution_time_by_type_per_day: totalExecutionsPerDay.avgExecutionTimeByType, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/actions/server/usage/types.ts b/x-pack/plugins/actions/server/usage/types.ts index 52677b35ac75b8..2d041b1ba0d0e2 100644 --- a/x-pack/plugins/actions/server/usage/types.ts +++ b/x-pack/plugins/actions/server/usage/types.ts @@ -16,9 +16,12 @@ export interface ActionsUsage { count_active_by_type: Record; count_active_email_connectors_by_service_type: Record; count_actions_namespaces: number; - // TODO: Implement executions count telemetry with eventLog, when it will write to index - // executions_by_type: Record; - // executions_total: number; + count_actions_executions_per_day: number; + count_actions_executions_by_type_per_day: Record; + count_actions_executions_failed_per_day: number; + count_actions_executions_failed_by_type_per_day: Record; + avg_execution_time_per_day: number; + avg_execution_time_by_type_per_day: Record; } export const byTypeSchema: MakeSchemaFrom['count_by_type'] = { diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts index e7e311902d08d7..af009217ed99bd 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts @@ -42,7 +42,7 @@ describe('AlertNavigationRegistry', () => { test('returns true for registered consumer & alert types handlers', () => { const registry = new AlertNavigationRegistry(); const alertType = mockAlertType('index_threshold'); - registry.register('siem', alertType, handler); + registry.register('siem', alertType.id, handler); expect(registry.has('siem', alertType)).toEqual(true); }); @@ -72,7 +72,7 @@ describe('AlertNavigationRegistry', () => { test('registers a handler by consumer & Alert Type', () => { const registry = new AlertNavigationRegistry(); const alertType = mockAlertType('index_threshold'); - registry.register('siem', alertType, handler); + registry.register('siem', alertType.id, handler); expect(registry.has('siem', alertType)).toEqual(true); }); @@ -80,11 +80,11 @@ describe('AlertNavigationRegistry', () => { const registry = new AlertNavigationRegistry(); const indexThresholdAlertType = mockAlertType('index_threshold'); - registry.register('siem', indexThresholdAlertType, handler); + registry.register('siem', indexThresholdAlertType.id, handler); expect(registry.has('siem', indexThresholdAlertType)).toEqual(true); const geoAlertType = mockAlertType('geogrid'); - registry.register('siem', geoAlertType, handler); + registry.register('siem', geoAlertType.id, handler); expect(registry.has('siem', geoAlertType)).toEqual(true); }); @@ -92,19 +92,19 @@ describe('AlertNavigationRegistry', () => { const registry = new AlertNavigationRegistry(); const indexThresholdAlertType = mockAlertType('geogrid'); - registry.register('siem', indexThresholdAlertType, handler); + registry.register('siem', indexThresholdAlertType.id, handler); expect(registry.has('siem', indexThresholdAlertType)).toEqual(true); - registry.register('apm', indexThresholdAlertType, handler); + registry.register('apm', indexThresholdAlertType.id, handler); expect(registry.has('apm', indexThresholdAlertType)).toEqual(true); }); test('throws if an existing handler is registered', () => { const registry = new AlertNavigationRegistry(); const alertType = mockAlertType('index_threshold'); - registry.register('siem', alertType, handler); + registry.register('siem', alertType.id, handler); expect(() => { - registry.register('siem', alertType, handler); + registry.register('siem', alertType.id, handler); }).toThrowErrorMatchingInlineSnapshot( `"Navigation for Alert type \\"index_threshold\\" within \\"siem\\" is already registered."` ); @@ -125,7 +125,7 @@ describe('AlertNavigationRegistry', () => { expect(registry.hasDefaultHandler('siem')).toEqual(true); const geoAlertType = mockAlertType('geogrid'); - registry.register('siem', geoAlertType, handler); + registry.register('siem', geoAlertType.id, handler); expect(registry.has('siem', geoAlertType)).toEqual(true); }); @@ -149,7 +149,7 @@ describe('AlertNavigationRegistry', () => { } const indexThresholdAlertType = mockAlertType('indexThreshold'); - registry.register('siem', indexThresholdAlertType, indexThresholdHandler); + registry.register('siem', indexThresholdAlertType.id, indexThresholdHandler); expect(registry.get('siem', indexThresholdAlertType)).toEqual(indexThresholdHandler); }); @@ -167,7 +167,7 @@ describe('AlertNavigationRegistry', () => { test('returns default handlers by consumer when there are other alert type handler', () => { const registry = new AlertNavigationRegistry(); - registry.register('siem', mockAlertType('indexThreshold'), () => ({})); + registry.register('siem', mockAlertType('indexThreshold').id, () => ({})); function defaultHandler(alert: SanitizedAlert) { return {}; diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts index 65b7c1e68e431d..0c7bf052fef4cb 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts @@ -15,11 +15,11 @@ export class AlertNavigationRegistry { new Map(); public has(consumer: string, alertType: AlertType) { - return this.hasTypedHandler(consumer, alertType) || this.hasDefaultHandler(consumer); + return this.hasTypedHandler(consumer, alertType.id) || this.hasDefaultHandler(consumer); } - public hasTypedHandler(consumer: string, alertType: AlertType) { - return this.alertNavigations.get(consumer)?.has(alertType.id) ?? false; + public hasTypedHandler(consumer: string, ruleTypeId: string) { + return this.alertNavigations.get(consumer)?.has(ruleTypeId) ?? false; } public hasDefaultHandler(consumer: string) { @@ -50,14 +50,14 @@ export class AlertNavigationRegistry { consumerNavigations.set(DEFAULT_HANDLER, handler); } - public register(consumer: string, alertType: AlertType, handler: AlertNavigationHandler) { - if (this.hasTypedHandler(consumer, alertType)) { + public register(consumer: string, ruleTypeId: string, handler: AlertNavigationHandler) { + if (this.hasTypedHandler(consumer, ruleTypeId)) { throw new Error( i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError', { defaultMessage: 'Navigation for Alert type "{alertType}" within "{consumer}" is already registered.', values: { - alertType: alertType.id, + alertType: ruleTypeId, consumer, }, }) @@ -67,7 +67,7 @@ export class AlertNavigationRegistry { const consumerNavigations = this.alertNavigations.get(consumer) ?? this.createConsumerNavigation(consumer); - consumerNavigations.set(alertType.id, handler); + consumerNavigations.set(ruleTypeId, handler); } public get(consumer: string, alertType: AlertType): AlertNavigationHandler { diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerting/public/plugin.ts index be7080f5df6dac..71fb0c7fe32b28 100644 --- a/x-pack/plugins/alerting/public/plugin.ts +++ b/x-pack/plugins/alerting/public/plugin.ts @@ -59,15 +59,7 @@ export class AlertingPublicPlugin implements Plugin { - const alertType = await loadAlertType({ http: core.http, id: ruleTypeId }); - if (!alertType) { - // eslint-disable-next-line no-console - console.log( - `Unable to register navigation for rule type "${ruleTypeId}" because it is not registered on the server side.` - ); - return; - } - this.alertNavigationRegistry!.register(applicationId, alertType, handler); + this.alertNavigationRegistry!.register(applicationId, ruleTypeId, handler); }; const registerDefaultNavigation = async ( diff --git a/x-pack/plugins/alerting/server/alert_instance/index.ts b/x-pack/plugins/alerting/server/alert_instance/index.ts index c342c2f6bd0518..7b5dd064c5dca3 100644 --- a/x-pack/plugins/alerting/server/alert_instance/index.ts +++ b/x-pack/plugins/alerting/server/alert_instance/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { AlertInstance, PublicAlertInstance } from './alert_instance'; +export type { PublicAlertInstance } from './alert_instance'; +export { AlertInstance } from './alert_instance'; export { createAlertInstanceFactory } from './create_alert_instance_factory'; diff --git a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.test.ts b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.test.ts index ebd9ef3305a09b..4ff92109ab2241 100644 --- a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.test.ts @@ -10,7 +10,6 @@ import { ruleTypeRegistryMock } from './rule_type_registry.mock'; import { KibanaRequest } from '../../../../src/core/server'; import { savedObjectsClientMock } from '../../../../src/core/server/mocks'; import { securityMock } from '../../security/server/mocks'; -import { ALERTS_FEATURE_ID } from '../common'; import { AlertingAuthorizationClientFactory, AlertingAuthorizationClientFactoryOpts, @@ -18,7 +17,6 @@ import { import { featuresPluginMock } from '../../features/server/mocks'; jest.mock('./authorization/alerting_authorization'); -jest.mock('./authorization/audit_logger'); const savedObjectsClient = savedObjectsClientMock.create(); const features = featuresPluginMock.createStart(); @@ -63,7 +61,6 @@ test('creates an alerting authorization client with proper constructor arguments ...alertingAuthorizationClientFactoryParams, }); const request = KibanaRequest.from(fakeRequest); - const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger'); factory.create(request); @@ -73,20 +70,15 @@ test('creates an alerting authorization client with proper constructor arguments authorization: securityPluginStart.authz, ruleTypeRegistry: alertingAuthorizationClientFactoryParams.ruleTypeRegistry, features: alertingAuthorizationClientFactoryParams.features, - auditLogger: expect.any(AlertingAuthorizationAuditLogger), getSpace: expect.any(Function), getSpaceId: expect.any(Function), }); - - expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled(); - expect(securityPluginSetup.audit.getLogger).toHaveBeenCalledWith(ALERTS_FEATURE_ID); }); test('creates an alerting authorization client with proper constructor arguments', async () => { const factory = new AlertingAuthorizationClientFactory(); factory.initialize(alertingAuthorizationClientFactoryParams); const request = KibanaRequest.from(fakeRequest); - const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger'); factory.create(request); @@ -95,11 +87,7 @@ test('creates an alerting authorization client with proper constructor arguments request, ruleTypeRegistry: alertingAuthorizationClientFactoryParams.ruleTypeRegistry, features: alertingAuthorizationClientFactoryParams.features, - auditLogger: expect.any(AlertingAuthorizationAuditLogger), getSpace: expect.any(Function), getSpaceId: expect.any(Function), }); - - expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled(); - expect(securityPluginSetup.audit.getLogger).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts index 27b2d92eba2561..0888f2e7a09f2a 100644 --- a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts +++ b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts @@ -6,12 +6,10 @@ */ import { KibanaRequest } from 'src/core/server'; -import { ALERTS_FEATURE_ID } from '../common'; import { RuleTypeRegistry } from './types'; import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; import { AlertingAuthorization } from './authorization/alerting_authorization'; -import { AlertingAuthorizationAuditLogger } from './authorization/audit_logger'; import { Space } from '../../spaces/server'; export interface AlertingAuthorizationClientFactoryOpts { @@ -27,7 +25,6 @@ export class AlertingAuthorizationClientFactory { private isInitialized = false; private ruleTypeRegistry!: RuleTypeRegistry; private securityPluginStart?: SecurityPluginStart; - private securityPluginSetup?: SecurityPluginSetup; private features!: FeaturesPluginStart; private getSpace!: (request: KibanaRequest) => Promise; private getSpaceId!: (request: KibanaRequest) => string | undefined; @@ -39,14 +36,13 @@ export class AlertingAuthorizationClientFactory { this.isInitialized = true; this.getSpace = options.getSpace; this.ruleTypeRegistry = options.ruleTypeRegistry; - this.securityPluginSetup = options.securityPluginSetup; this.securityPluginStart = options.securityPluginStart; this.features = options.features; this.getSpaceId = options.getSpaceId; } public create(request: KibanaRequest): AlertingAuthorization { - const { securityPluginSetup, securityPluginStart, features } = this; + const { securityPluginStart, features } = this; return new AlertingAuthorization({ authorization: securityPluginStart?.authz, request, @@ -54,9 +50,6 @@ export class AlertingAuthorizationClientFactory { getSpaceId: this.getSpaceId, ruleTypeRegistry: this.ruleTypeRegistry, features: features!, - auditLogger: new AlertingAuthorizationAuditLogger( - securityPluginSetup?.audit.getLogger(ALERTS_FEATURE_ID) - ), }); } } diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts index 212d8238f84000..95ab85dcd8271d 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts @@ -19,8 +19,6 @@ import { ReadOperations, AlertingAuthorizationEntity, } from './alerting_authorization'; -import { alertingAuthorizationAuditLoggerMock } from './audit_logger.mock'; -import { AlertingAuthorizationAuditLogger, AuthorizationResult } from './audit_logger'; import uuid from 'uuid'; import { RecoveredActionGroup } from '../../common'; import { RegistryRuleType } from '../rule_type_registry'; @@ -31,9 +29,6 @@ const ruleTypeRegistry = ruleTypeRegistryMock.create(); const features: jest.Mocked = featuresPluginMock.createStart(); const request = {} as KibanaRequest; -const auditLogger = alertingAuthorizationAuditLoggerMock.create(); -const realAuditLogger = new AlertingAuthorizationAuditLogger(); - const getSpace = jest.fn(); const getSpaceId = () => 'space1'; @@ -189,15 +184,6 @@ const myFeatureWithoutAlerting = mockFeature('myOtherApp'); beforeEach(() => { jest.resetAllMocks(); - auditLogger.logAuthorizationFailure.mockImplementation((username, ...args) => - realAuditLogger.getAuthorizationMessage(AuthorizationResult.Unauthorized, ...args) - ); - auditLogger.logAuthorizationSuccess.mockImplementation((username, ...args) => - realAuditLogger.getAuthorizationMessage(AuthorizationResult.Authorized, ...args) - ); - auditLogger.logUnscopedAuthorizationFailure.mockImplementation( - (username, operation) => `Unauthorized ${username}/${operation}` - ); ruleTypeRegistry.get.mockImplementation((id) => ({ id, name: 'My Alert Type', @@ -232,7 +218,6 @@ describe('AlertingAuthorization', () => { request, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -247,7 +232,6 @@ describe('AlertingAuthorization', () => { request, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -270,7 +254,6 @@ describe('AlertingAuthorization', () => { ruleTypeRegistry, authorization, features, - auditLogger, getSpace, getSpaceId, }); @@ -296,7 +279,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -326,19 +308,6 @@ describe('AlertingAuthorization', () => { expect(checkPrivileges).toHaveBeenCalledWith({ kibana: [mockAuthorizationAction('myType', 'myApp', 'rule', 'create')], }); - - expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myApp", - "create", - "rule", - ] - `); }); test('ensures the user has privileges to execute alerts for the specified rule type and operation without consumer when producer and consumer are the same', async () => { @@ -352,7 +321,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -382,19 +350,6 @@ describe('AlertingAuthorization', () => { expect(checkPrivileges).toHaveBeenCalledWith({ kibana: [mockAuthorizationAction('myType', 'myApp', 'alert', 'update')], }); - - expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myApp", - "update", - "alert", - ] - `); }); test('ensures the user has privileges to execute rules for the specified rule type and operation without consumer when consumer is alerts', async () => { @@ -408,7 +363,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -444,19 +398,6 @@ describe('AlertingAuthorization', () => { expect(checkPrivileges).toHaveBeenCalledWith({ kibana: [mockAuthorizationAction('myType', 'myApp', 'rule', 'create')], }); - - expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "alerts", - "create", - "rule", - ] - `); }); test('ensures the user has privileges to execute alerts for the specified rule type and operation without consumer when consumer is alerts', async () => { @@ -470,7 +411,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -506,19 +446,6 @@ describe('AlertingAuthorization', () => { expect(checkPrivileges).toHaveBeenCalledWith({ kibana: [mockAuthorizationAction('myType', 'myApp', 'alert', 'update')], }); - - expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "alerts", - "update", - "alert", - ] - `); }); test('ensures the user has privileges to execute rules for the specified rule type, operation and producer when producer is different from consumer', async () => { @@ -538,7 +465,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -571,19 +497,6 @@ describe('AlertingAuthorization', () => { mockAuthorizationAction('myType', 'myApp', 'rule', 'create'), ], }); - - expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myOtherApp", - "create", - "rule", - ] - `); }); test('ensures the user has privileges to execute alerts for the specified rule type, operation and producer when producer is different from consumer', async () => { @@ -603,7 +516,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -636,19 +548,6 @@ describe('AlertingAuthorization', () => { mockAuthorizationAction('myType', 'myApp', 'alert', 'update'), ], }); - - expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myOtherApp", - "update", - "alert", - ] - `); }); test('throws if user lacks the required rule privileges for the consumer', async () => { @@ -662,7 +561,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -694,19 +592,6 @@ describe('AlertingAuthorization', () => { ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unauthorized to create a \\"myType\\" rule for \\"myOtherApp\\""` ); - - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myOtherApp", - "create", - "rule", - ] - `); }); test('throws if user lacks the required alert privileges for the consumer', async () => { @@ -720,7 +605,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -756,19 +640,6 @@ describe('AlertingAuthorization', () => { ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unauthorized to update a \\"myType\\" alert for \\"myAppRulesOnly\\""` ); - - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myAppRulesOnly", - "update", - "alert", - ] - `); }); test('throws if user lacks the required privileges for the producer', async () => { @@ -782,7 +653,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -814,19 +684,6 @@ describe('AlertingAuthorization', () => { ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unauthorized to update a \\"myType\\" alert by \\"myApp\\""` ); - - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 1, - "myApp", - "update", - "alert", - ] - `); }); test('throws if user lacks the required privileges for both consumer and producer', async () => { @@ -840,7 +697,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -872,19 +728,6 @@ describe('AlertingAuthorization', () => { ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unauthorized to create a \\"myType\\" alert for \\"myOtherApp\\""` ); - - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myOtherApp", - "create", - "alert", - ] - `); }); }); @@ -931,7 +774,6 @@ describe('AlertingAuthorization', () => { request, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -951,7 +793,6 @@ describe('AlertingAuthorization', () => { request, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -966,8 +807,6 @@ describe('AlertingAuthorization', () => { } ); ensureRuleTypeIsAuthorized('someMadeUpType', 'myApp', 'rule'); - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); }); test('creates a filter based on the privileged types', async () => { const { authorization } = mockSecurity(); @@ -985,7 +824,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1005,7 +843,6 @@ describe('AlertingAuthorization', () => { `((path.to.rule_type_id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:myOtherAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:mySecondAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)))` ) ); - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); }); test('throws if user has no privileges to any rule type', async () => { const { authorization } = mockSecurity(); @@ -1034,7 +871,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1047,8 +883,9 @@ describe('AlertingAuthorization', () => { consumer: 'consumer-field', }, }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Unauthorized some-user/find"`); - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized to find rules for any rule types"` + ); }); test('creates an `ensureRuleTypeIsAuthorized` function which throws if type is unauthorized', async () => { const { authorization } = mockSecurity(); @@ -1090,7 +927,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1110,18 +946,6 @@ describe('AlertingAuthorization', () => { }).toThrowErrorMatchingInlineSnapshot( `"Unauthorized to find a \\"myAppAlertType\\" alert for \\"myOtherApp\\""` ); - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myAppAlertType", - 0, - "myOtherApp", - "find", - "alert", - ] - `); }); test('creates an `ensureRuleTypeIsAuthorized` function which is no-op if type is authorized', async () => { const { authorization } = mockSecurity(); @@ -1163,7 +987,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1181,8 +1004,6 @@ describe('AlertingAuthorization', () => { expect(() => { ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'rule'); }).not.toThrow(); - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); }); test('creates an `logSuccessfulAuthorization` function which logs every authorized type', async () => { const { authorization } = mockSecurity(); @@ -1237,46 +1058,25 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); ruleTypeRegistry.list.mockReturnValue(setOfAlertTypes); - const { ensureRuleTypeIsAuthorized, logSuccessfulAuthorization } = - await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + const { ensureRuleTypeIsAuthorized } = await alertAuthorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + { type: AlertingAuthorizationFilterType.KQL, fieldNames: { ruleTypeId: 'ruleId', consumer: 'consumer', }, - }); + } + ); expect(() => { ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'rule'); ensureRuleTypeIsAuthorized('mySecondAppAlertType', 'myOtherApp', 'rule'); ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'rule'); }).not.toThrow(); - expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); - logSuccessfulAuthorization(); - expect(auditLogger.logBulkAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.logBulkAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - Array [ - Array [ - "myAppAlertType", - "myOtherApp", - ], - Array [ - "mySecondAppAlertType", - "myOtherApp", - ], - ], - 0, - "find", - "rule", - ] - `); }); // This is a specific use case currently for alerts as data @@ -1287,7 +1087,6 @@ describe('AlertingAuthorization', () => { request, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1341,7 +1140,6 @@ describe('AlertingAuthorization', () => { request, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1466,7 +1264,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1562,7 +1359,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1667,7 +1463,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1784,7 +1579,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1894,7 +1688,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); @@ -1968,7 +1761,6 @@ describe('AlertingAuthorization', () => { authorization, ruleTypeRegistry, features, - auditLogger, getSpace, getSpaceId, }); diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 271214fa9fc073..aafeb5003eaab5 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -14,7 +14,6 @@ import { ALERTS_FEATURE_ID, RuleTypeRegistry } from '../types'; import { SecurityPluginSetup } from '../../../security/server'; import { RegistryRuleType } from '../rule_type_registry'; import { PluginStartContract as FeaturesPluginStart } from '../../../features/server'; -import { AlertingAuthorizationAuditLogger, ScopeType } from './audit_logger'; import { Space } from '../../../spaces/server'; import { asFiltersByRuleTypeAndConsumer, @@ -70,7 +69,6 @@ export interface ConstructorOptions { features: FeaturesPluginStart; getSpace: (request: KibanaRequest) => Promise; getSpaceId: (request: KibanaRequest) => string | undefined; - auditLogger: AlertingAuthorizationAuditLogger; authorization?: SecurityPluginSetup['authz']; } @@ -78,7 +76,6 @@ export class AlertingAuthorization { private readonly ruleTypeRegistry: RuleTypeRegistry; private readonly request: KibanaRequest; private readonly authorization?: SecurityPluginSetup['authz']; - private readonly auditLogger: AlertingAuthorizationAuditLogger; private readonly featuresIds: Promise>; private readonly allPossibleConsumers: Promise; private readonly spaceId: string | undefined; @@ -88,14 +85,12 @@ export class AlertingAuthorization { request, authorization, features, - auditLogger, getSpace, getSpaceId, }: ConstructorOptions) { this.request = request; this.authorization = authorization; this.ruleTypeRegistry = ruleTypeRegistry; - this.auditLogger = auditLogger; this.spaceId = getSpaceId(request); @@ -183,7 +178,7 @@ export class AlertingAuthorization { const shouldAuthorizeConsumer = consumer !== ALERTS_FEATURE_ID; const checkPrivileges = authorization.checkPrivilegesDynamicallyWithRequest(this.request); - const { hasAllRequested, username, privileges } = await checkPrivileges({ + const { hasAllRequested, privileges } = await checkPrivileges({ kibana: shouldAuthorizeConsumer && consumer !== ruleType.producer ? [ @@ -208,27 +203,11 @@ export class AlertingAuthorization { * This check will ensure we don't accidentally let these through */ throw Boom.forbidden( - this.auditLogger.logAuthorizationFailure( - username, - ruleTypeId, - ScopeType.Consumer, - consumer, - operation, - entity - ) + getUnauthorizedMessage(ruleTypeId, ScopeType.Consumer, consumer, operation, entity) ); } - if (hasAllRequested) { - this.auditLogger.logAuthorizationSuccess( - username, - ruleTypeId, - ScopeType.Consumer, - consumer, - operation, - entity - ); - } else { + if (!hasAllRequested) { const authorizedPrivileges = map( privileges.kibana.filter((privilege) => privilege.authorized), 'privilege' @@ -244,8 +223,7 @@ export class AlertingAuthorization { : [ScopeType.Producer, ruleType.producer]; throw Boom.forbidden( - this.auditLogger.logAuthorizationFailure( - username, + getUnauthorizedMessage( ruleTypeId, unauthorizedScopeType, unauthorizedScope, @@ -256,14 +234,7 @@ export class AlertingAuthorization { } } else if (!isAvailableConsumer) { throw Boom.forbidden( - this.auditLogger.logAuthorizationFailure( - '', - ruleTypeId, - ScopeType.Consumer, - consumer, - operation, - entity - ) + getUnauthorizedMessage(ruleTypeId, ScopeType.Consumer, consumer, operation, entity) ); } } @@ -274,7 +245,6 @@ export class AlertingAuthorization { ): Promise<{ filter?: KueryNode | JsonObject; ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void; - logSuccessfulAuthorization: () => void; }> { return this.getAuthorizationFilter(authorizationEntity, filterOpts, ReadOperations.Find); } @@ -286,19 +256,16 @@ export class AlertingAuthorization { ): Promise<{ filter?: KueryNode | JsonObject; ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void; - logSuccessfulAuthorization: () => void; }> { if (this.authorization && this.shouldCheckAuthorization()) { - const { username, authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization( + const { authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization( this.ruleTypeRegistry.list(), [operation], authorizationEntity ); if (!authorizedRuleTypes.size) { - throw Boom.forbidden( - this.auditLogger.logUnscopedAuthorizationFailure(username!, 'find', authorizationEntity) - ); + throw Boom.forbidden(`Unauthorized to find ${authorizationEntity}s for any rule types`); } const authorizedRuleTypeIdsToConsumers = new Set( @@ -320,8 +287,7 @@ export class AlertingAuthorization { ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => { if (!authorizedRuleTypeIdsToConsumers.has(`${ruleTypeId}/${consumer}/${authType}`)) { throw Boom.forbidden( - this.auditLogger.logAuthorizationFailure( - username!, + getUnauthorizedMessage( ruleTypeId, ScopeType.Consumer, consumer, @@ -337,32 +303,12 @@ export class AlertingAuthorization { } } }, - logSuccessfulAuthorization: () => { - if (authorizedEntries.size) { - this.auditLogger.logBulkAuthorizationSuccess( - username!, - [...authorizedEntries.entries()].reduce>( - (authorizedPairs, [alertTypeId, consumers]) => { - for (const consumer of consumers) { - authorizedPairs.push([alertTypeId, consumer]); - } - return authorizedPairs; - }, - [] - ), - ScopeType.Consumer, - 'find', - authorizationEntity - ); - } - }, }; } return { filter: asFiltersBySpaceId(filterOpts, this.spaceId) as JsonObject, ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => {}, - logSuccessfulAuthorization: () => {}, }; } @@ -500,3 +446,20 @@ function asAuthorizedConsumers( ): AuthorizedConsumers { return fromPairs(consumers.map((feature) => [feature, hasPrivileges])); } + +enum ScopeType { + Consumer, + Producer, +} + +function getUnauthorizedMessage( + alertTypeId: string, + scopeType: ScopeType, + scope: string, + operation: string, + entity: string +): string { + return `Unauthorized to ${operation} a "${alertTypeId}" ${entity} ${ + scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"` + }`; +} diff --git a/x-pack/plugins/alerting/server/authorization/audit_logger.mock.ts b/x-pack/plugins/alerting/server/authorization/audit_logger.mock.ts deleted file mode 100644 index 8e2c072336a180..00000000000000 --- a/x-pack/plugins/alerting/server/authorization/audit_logger.mock.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 { AlertingAuthorizationAuditLogger } from './audit_logger'; - -const createAlertingAuthorizationAuditLoggerMock = () => { - const mocked = { - getAuthorizationMessage: jest.fn(), - logAuthorizationFailure: jest.fn(), - logUnscopedAuthorizationFailure: jest.fn(), - logAuthorizationSuccess: jest.fn(), - logBulkAuthorizationSuccess: jest.fn(), - } as unknown as jest.Mocked; - return mocked; -}; - -export const alertingAuthorizationAuditLoggerMock: { - create: () => jest.Mocked; -} = { - create: createAlertingAuthorizationAuditLoggerMock, -}; diff --git a/x-pack/plugins/alerting/server/authorization/audit_logger.test.ts b/x-pack/plugins/alerting/server/authorization/audit_logger.test.ts deleted file mode 100644 index 7b0ffdb193c831..00000000000000 --- a/x-pack/plugins/alerting/server/authorization/audit_logger.test.ts +++ /dev/null @@ -1,338 +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 { AlertingAuthorizationAuditLogger, ScopeType } from './audit_logger'; - -const createMockAuditLogger = () => { - return { - log: jest.fn(), - }; -}; - -describe(`#constructor`, () => { - test('initializes a noop auditLogger if security logger is unavailable', () => { - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(undefined); - - const username = 'foo-user'; - const alertTypeId = 'alert-type-id'; - const scopeType = ScopeType.Consumer; - const scope = 'myApp'; - const operation = 'create'; - const entity = 'rule'; - expect(() => { - alertsAuditLogger.logAuthorizationFailure( - username, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - - alertsAuditLogger.logAuthorizationSuccess( - username, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - }).not.toThrow(); - }); -}); - -describe(`#logUnscopedAuthorizationFailure`, () => { - test('logs auth failure of operation', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logUnscopedAuthorizationFailure(username, operation, entity); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_unscoped_authorization_failure", - "foo-user Unauthorized to create rules for any rule types", - Object { - "operation": "create", - "username": "foo-user", - }, - ] - `); - }); - - test('logs auth failure with producer scope', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const alertTypeId = 'alert-type-id'; - const scopeType = ScopeType.Producer; - const scope = 'myOtherApp'; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logAuthorizationFailure( - username, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_authorization_failure", - "foo-user Unauthorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"", - Object { - "alertTypeId": "alert-type-id", - "entity": "rule", - "operation": "create", - "scope": "myOtherApp", - "scopeType": 1, - "username": "foo-user", - }, - ] - `); - }); -}); - -describe(`#logAuthorizationFailure`, () => { - test('logs auth failure with consumer scope', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const alertTypeId = 'alert-type-id'; - const scopeType = ScopeType.Consumer; - const scope = 'myApp'; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logAuthorizationFailure( - username, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_authorization_failure", - "foo-user Unauthorized to create a \\"alert-type-id\\" rule for \\"myApp\\"", - Object { - "alertTypeId": "alert-type-id", - "entity": "rule", - "operation": "create", - "scope": "myApp", - "scopeType": 0, - "username": "foo-user", - }, - ] - `); - }); - - test('logs auth failure with producer scope', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const alertTypeId = 'alert-type-id'; - const scopeType = ScopeType.Producer; - const scope = 'myOtherApp'; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logAuthorizationFailure( - username, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_authorization_failure", - "foo-user Unauthorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"", - Object { - "alertTypeId": "alert-type-id", - "entity": "rule", - "operation": "create", - "scope": "myOtherApp", - "scopeType": 1, - "username": "foo-user", - }, - ] - `); - }); -}); - -describe(`#logBulkAuthorizationSuccess`, () => { - test('logs auth success with consumer scope', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const scopeType = ScopeType.Consumer; - const authorizedEntries: Array<[string, string]> = [ - ['alert-type-id', 'myApp'], - ['other-alert-type-id', 'myOtherApp'], - ]; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logBulkAuthorizationSuccess( - username, - authorizedEntries, - scopeType, - operation, - entity - ); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_authorization_success", - "foo-user Authorized to create: \\"alert-type-id\\" rules for \\"myApp\\", \\"other-alert-type-id\\" rules for \\"myOtherApp\\"", - Object { - "authorizedEntries": Array [ - Array [ - "alert-type-id", - "myApp", - ], - Array [ - "other-alert-type-id", - "myOtherApp", - ], - ], - "entity": "rule", - "operation": "create", - "scopeType": 0, - "username": "foo-user", - }, - ] - `); - }); - - test('logs auth success with producer scope', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const scopeType = ScopeType.Producer; - const authorizedEntries: Array<[string, string]> = [ - ['alert-type-id', 'myApp'], - ['other-alert-type-id', 'myOtherApp'], - ]; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logBulkAuthorizationSuccess( - username, - authorizedEntries, - scopeType, - operation, - entity - ); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_authorization_success", - "foo-user Authorized to create: \\"alert-type-id\\" rules by \\"myApp\\", \\"other-alert-type-id\\" rules by \\"myOtherApp\\"", - Object { - "authorizedEntries": Array [ - Array [ - "alert-type-id", - "myApp", - ], - Array [ - "other-alert-type-id", - "myOtherApp", - ], - ], - "entity": "rule", - "operation": "create", - "scopeType": 1, - "username": "foo-user", - }, - ] - `); - }); -}); - -describe(`#savedObjectsAuthorizationSuccess`, () => { - test('logs auth success with consumer scope', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const alertTypeId = 'alert-type-id'; - const scopeType = ScopeType.Consumer; - const scope = 'myApp'; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logAuthorizationSuccess( - username, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_authorization_success", - "foo-user Authorized to create a \\"alert-type-id\\" rule for \\"myApp\\"", - Object { - "alertTypeId": "alert-type-id", - "entity": "rule", - "operation": "create", - "scope": "myApp", - "scopeType": 0, - "username": "foo-user", - }, - ] - `); - }); - - test('logs auth success with producer scope', () => { - const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); - const username = 'foo-user'; - const alertTypeId = 'alert-type-id'; - const scopeType = ScopeType.Producer; - const scope = 'myOtherApp'; - const operation = 'create'; - const entity = 'rule'; - - alertsAuditLogger.logAuthorizationSuccess( - username, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - - expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alerting_authorization_success", - "foo-user Authorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"", - Object { - "alertTypeId": "alert-type-id", - "entity": "rule", - "operation": "create", - "scope": "myOtherApp", - "scopeType": 1, - "username": "foo-user", - }, - ] - `); - }); -}); diff --git a/x-pack/plugins/alerting/server/authorization/audit_logger.ts b/x-pack/plugins/alerting/server/authorization/audit_logger.ts deleted file mode 100644 index 2a0c8517338006..00000000000000 --- a/x-pack/plugins/alerting/server/authorization/audit_logger.ts +++ /dev/null @@ -1,131 +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 { LegacyAuditLogger } from '../../../security/server'; - -export enum ScopeType { - Consumer, - Producer, -} - -export enum AuthorizationResult { - Unauthorized = 'Unauthorized', - Authorized = 'Authorized', -} - -export class AlertingAuthorizationAuditLogger { - private readonly auditLogger: LegacyAuditLogger; - - constructor(auditLogger: LegacyAuditLogger = { log() {} }) { - this.auditLogger = auditLogger; - } - - public getAuthorizationMessage( - authorizationResult: AuthorizationResult, - alertTypeId: string, - scopeType: ScopeType, - scope: string, - operation: string, - entity: string - ): string { - return `${authorizationResult} to ${operation} a "${alertTypeId}" ${entity} ${ - scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"` - }`; - } - - public logAuthorizationFailure( - username: string, - alertTypeId: string, - scopeType: ScopeType, - scope: string, - operation: string, - entity: string - ): string { - const message = this.getAuthorizationMessage( - AuthorizationResult.Unauthorized, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - this.auditLogger.log('alerting_authorization_failure', `${username} ${message}`, { - username, - alertTypeId, - scopeType, - scope, - operation, - entity, - }); - return message; - } - - public logUnscopedAuthorizationFailure( - username: string, - operation: string, - entity: string - ): string { - const message = `Unauthorized to ${operation} ${entity}s for any rule types`; - this.auditLogger.log('alerting_unscoped_authorization_failure', `${username} ${message}`, { - username, - operation, - }); - return message; - } - - public logAuthorizationSuccess( - username: string, - alertTypeId: string, - scopeType: ScopeType, - scope: string, - operation: string, - entity: string - ): string { - const message = this.getAuthorizationMessage( - AuthorizationResult.Authorized, - alertTypeId, - scopeType, - scope, - operation, - entity - ); - this.auditLogger.log('alerting_authorization_success', `${username} ${message}`, { - username, - alertTypeId, - scopeType, - scope, - operation, - entity, - }); - return message; - } - - public logBulkAuthorizationSuccess( - username: string, - authorizedEntries: Array<[string, string]>, - scopeType: ScopeType, - operation: string, - entity: string - ): string { - const message = `${AuthorizationResult.Authorized} to ${operation}: ${authorizedEntries - .map( - ([alertTypeId, scope]) => - `"${alertTypeId}" ${entity}s ${ - scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"` - }` - ) - .join(', ')}`; - this.auditLogger.log('alerting_authorization_success', `${username} ${message}`, { - username, - scopeType, - authorizedEntries, - operation, - entity, - }); - return message; - } -} diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts index 2ddb6ff711c469..8ed91cc821412c 100644 --- a/x-pack/plugins/alerting/server/index.ts +++ b/x-pack/plugins/alerting/server/index.ts @@ -30,9 +30,9 @@ export type { RuleParamsAndRefs, } from './types'; export { DEFAULT_MAX_EPHEMERAL_ACTIONS_PER_ALERT } from './config'; -export { PluginSetupContract, PluginStartContract } from './plugin'; -export { FindResult } from './rules_client'; -export { PublicAlertInstance as AlertInstance } from './alert_instance'; +export type { PluginSetupContract, PluginStartContract } from './plugin'; +export type { FindResult } from './rules_client'; +export type { PublicAlertInstance as AlertInstance } from './alert_instance'; export { parseDuration } from './lib'; export { getEsErrorMessage } from './lib/errors'; export { diff --git a/x-pack/plugins/alerting/server/lib/errors/index.ts b/x-pack/plugins/alerting/server/lib/errors/index.ts index 66db39916832a1..36ca30bc95ba81 100644 --- a/x-pack/plugins/alerting/server/lib/errors/index.ts +++ b/x-pack/plugins/alerting/server/lib/errors/index.ts @@ -14,5 +14,7 @@ export function isErrorThatHandlesItsOwnResponse( return typeof (e as ErrorThatHandlesItsOwnResponse).sendResponse === 'function'; } -export { ErrorThatHandlesItsOwnResponse, ElasticsearchError, getEsErrorMessage }; -export { AlertTypeDisabledError, AlertTypeDisabledReason } from './alert_type_disabled'; +export type { ErrorThatHandlesItsOwnResponse, ElasticsearchError }; +export { getEsErrorMessage }; +export type { AlertTypeDisabledReason } from './alert_type_disabled'; +export { AlertTypeDisabledError } from './alert_type_disabled'; diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerting/server/lib/index.ts index ed55548307e7cf..24f2513e1c6503 100644 --- a/x-pack/plugins/alerting/server/lib/index.ts +++ b/x-pack/plugins/alerting/server/lib/index.ts @@ -6,18 +6,18 @@ */ export { parseDuration, validateDurationSchema } from '../../common/parse_duration'; -export { ILicenseState, LicenseState } from './license_state'; +export type { ILicenseState } from './license_state'; +export { LicenseState } from './license_state'; export { validateAlertTypeParams } from './validate_alert_type_params'; export { getAlertNotifyWhenType } from './get_alert_notify_when_type'; export { verifyApiAccess } from './license_api_access'; export { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; -export { - AlertTypeDisabledError, +export type { AlertTypeDisabledReason, ErrorThatHandlesItsOwnResponse, - isErrorThatHandlesItsOwnResponse, ElasticsearchError, } from './errors'; +export { AlertTypeDisabledError, isErrorThatHandlesItsOwnResponse } from './errors'; export { executionStatusFromState, executionStatusFromError, diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index c8f52110f5bcc2..f0703defbca3dd 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -6,10 +6,9 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { first, map, share } from 'rxjs/operators'; +import { first } from 'rxjs/operators'; import { BehaviorSubject } from 'rxjs'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { combineLatest } from 'rxjs'; import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; import { EncryptedSavedObjectsPluginSetup, @@ -61,11 +60,7 @@ import { initializeApiKeyInvalidator, scheduleApiKeyInvalidatorTask, } from './invalidate_pending_api_keys/task'; -import { - getHealthStatusStream, - scheduleAlertingHealthCheck, - initializeAlertingHealth, -} from './health'; +import { scheduleAlertingHealthCheck, initializeAlertingHealth } from './health'; import { AlertsConfig } from './config'; import { getHealth } from './health/get_health'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; @@ -214,7 +209,13 @@ export class AlertingPlugin { usageCollection, core.getStartServices().then(([_, { taskManager }]) => taskManager) ); - initializeAlertingTelemetry(this.telemetryLogger, core, plugins.taskManager, kibanaIndex); + initializeAlertingTelemetry( + this.telemetryLogger, + core, + plugins.taskManager, + kibanaIndex, + this.eventLogService + ); } // Usage counter for telemetry @@ -236,33 +237,33 @@ export class AlertingPlugin { ); const serviceStatus$ = new BehaviorSubject({ - level: ServiceStatusLevels.degraded, - summary: 'Alerting is initializing', + level: ServiceStatusLevels.available, + summary: 'Alerting is (probably) ready', }); core.status.set(serviceStatus$); - core.getStartServices().then(async ([coreStart, startPlugins]) => { - combineLatest([ - core.status.derivedStatus$, - getHealthStatusStream( - startPlugins.taskManager, - this.logger, - coreStart.savedObjects, - this.config - ), - ]) - .pipe( - map(([derivedStatus, healthStatus]) => { - if (healthStatus.level > derivedStatus.level) { - return healthStatus as ServiceStatus; - } else { - return derivedStatus; - } - }), - share() - ) - .subscribe(serviceStatus$); - }); + // core.getStartServices().then(async ([coreStart, startPlugins]) => { + // combineLatest([ + // core.status.derivedStatus$, + // getHealthStatusStream( + // startPlugins.taskManager, + // this.logger, + // coreStart.savedObjects, + // this.config + // ), + // ]) + // .pipe( + // map(([derivedStatus, healthStatus]) => { + // if (healthStatus.level > derivedStatus.level) { + // return healthStatus as ServiceStatus; + // } else { + // return derivedStatus; + // } + // }), + // share() + // ) + // .subscribe(serviceStatus$); + // }); initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices()); diff --git a/x-pack/plugins/alerting/server/routes/lib/index.ts b/x-pack/plugins/alerting/server/routes/lib/index.ts index dc8f3ac3b5de9e..2c14660ae47de0 100644 --- a/x-pack/plugins/alerting/server/routes/lib/index.ts +++ b/x-pack/plugins/alerting/server/routes/lib/index.ts @@ -11,6 +11,10 @@ export { isSecurityPluginDisabledError, } from './error_handler'; export { renameKeys } from './rename_keys'; -export { AsApiContract, RewriteRequestCase, RewriteResponseCase } from './rewrite_request_case'; +export type { + AsApiContract, + RewriteRequestCase, + RewriteResponseCase, +} from './rewrite_request_case'; export { verifyAccessAndContext } from './verify_access_and_context'; export { countUsageOfPredefinedIds } from './count_usage_of_predefined_ids'; 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 e6f20049bc4706..75e56afcfd9bf8 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -579,11 +579,7 @@ export class RulesClient { ); throw error; } - const { - filter: authorizationFilter, - ensureRuleTypeIsAuthorized, - logSuccessfulAuthorization, - } = authorizationTuple; + const { filter: authorizationFilter, ensureRuleTypeIsAuthorized } = authorizationTuple; const { page, @@ -638,8 +634,6 @@ export class RulesClient { ) ); - logSuccessfulAuthorization(); - return { page, perPage, @@ -654,11 +648,10 @@ export class RulesClient { // Replace this when saved objects supports aggregations https://github.com/elastic/kibana/pull/64002 const alertExecutionStatus = await Promise.all( AlertExecutionStatusValues.map(async (status: string) => { - const { filter: authorizationFilter, logSuccessfulAuthorization } = - await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts - ); + const { filter: authorizationFilter } = await this.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); const filter = options.filter ? `${options.filter} and alert.attributes.executionStatus.status:(${status})` : `alert.attributes.executionStatus.status:(${status})`; @@ -676,8 +669,6 @@ export class RulesClient { type: 'alert', }); - logSuccessfulAuthorization(); - return { [status]: total }; }) ); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index 537a2a2a4cdce7..faea609d39ffe2 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -69,7 +69,6 @@ describe('aggregate()', () => { beforeEach(() => { authorization.getFindAuthorizationFilter.mockResolvedValue({ ensureRuleTypeIsAuthorized() {}, - logSuccessfulAuthorization() {}, }); unsecuredSavedObjectsClient.find .mockResolvedValueOnce({ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index e151188cf39795..2729b42490d9f6 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -77,7 +77,6 @@ describe('find()', () => { beforeEach(() => { authorization.getFindAuthorizationFilter.mockResolvedValue({ ensureRuleTypeIsAuthorized() {}, - logSuccessfulAuthorization() {}, }); unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ total: 1, @@ -295,7 +294,6 @@ describe('find()', () => { jest.resetAllMocks(); authorization.getFindAuthorizationFilter.mockResolvedValue({ ensureRuleTypeIsAuthorized() {}, - logSuccessfulAuthorization() {}, }); const injectReferencesFn = jest.fn().mockReturnValue({ bar: true, @@ -491,7 +489,6 @@ describe('find()', () => { jest.resetAllMocks(); authorization.getFindAuthorizationFilter.mockResolvedValue({ ensureRuleTypeIsAuthorized() {}, - logSuccessfulAuthorization() {}, }); const injectReferencesFn = jest.fn().mockImplementation(() => { throw new Error('something went wrong!'); @@ -628,7 +625,6 @@ describe('find()', () => { authorization.getFindAuthorizationFilter.mockResolvedValue({ filter, ensureRuleTypeIsAuthorized() {}, - logSuccessfulAuthorization() {}, }); const rulesClient = new RulesClient(rulesClientParams); @@ -651,10 +647,8 @@ describe('find()', () => { test('ensures authorization even when the fields required to authorize are omitted from the find', async () => { const ensureRuleTypeIsAuthorized = jest.fn(); - const logSuccessfulAuthorization = jest.fn(); authorization.getFindAuthorizationFilter.mockResolvedValue({ ensureRuleTypeIsAuthorized, - logSuccessfulAuthorization, }); unsecuredSavedObjectsClient.find.mockReset(); @@ -704,7 +698,6 @@ describe('find()', () => { type: 'alert', }); expect(ensureRuleTypeIsAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'rule'); - expect(logSuccessfulAuthorization).toHaveBeenCalled(); }); }); @@ -748,7 +741,6 @@ describe('find()', () => { ensureRuleTypeIsAuthorized: jest.fn(() => { throw new Error('Unauthorized'); }), - logSuccessfulAuthorization: jest.fn(), }); await expect(async () => await rulesClient.find()).rejects.toThrow(); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index 6ccde13ff12a68..76d09c3623e835 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -20,7 +20,6 @@ import { AuthenticatedUser } from '../../security/common/model'; import { securityMock } from '../../security/server/mocks'; import { PluginStartContract as ActionsStartContract } from '../../actions/server'; import { actionsMock, actionsAuthorizationMock } from '../../actions/server/mocks'; -import { LegacyAuditLogger } from '../../security/server'; import { eventLogMock } from '../../event_log/server/mocks'; import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock'; import { alertingAuthorizationClientFactoryMock } from './alerting_authorization_client_factory.mock'; @@ -29,7 +28,6 @@ import { AlertingAuthorizationClientFactory } from './alerting_authorization_cli jest.mock('./rules_client'); jest.mock('./authorization/alerting_authorization'); -jest.mock('./authorization/audit_logger'); const savedObjectsClient = savedObjectsClientMock.create(); const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(); @@ -93,11 +91,6 @@ test('creates an alerts client with proper constructor arguments when security i alertsAuthorization as unknown as AlertingAuthorization ); - const logger = { - log: jest.fn(), - } as jest.Mocked; - securityPluginSetup.audit.getLogger.mockReturnValue(logger); - factory.create(request, savedObjectsService); expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request, { diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts index 03a96d19b8e8ae..af08c8c75c1442 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts @@ -7,7 +7,11 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../src/core/server/elasticsearch/client/mocks'; -import { getTotalCountAggregations, getTotalCountInUse } from './alerts_telemetry'; +import { + getTotalCountAggregations, + getTotalCountInUse, + getExecutionsPerDayCount, +} from './alerts_telemetry'; describe('alerts telemetry', () => { test('getTotalCountInUse should replace first "." symbol to "__" in alert types names', async () => { @@ -114,4 +118,74 @@ Object { } `); }); + + test('getTotalExecutionsCount should return execution aggregations for total count, count by rule type and number of failed executions', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + elasticsearchClientMock.createSuccessTransportRequestPromise({ + aggregations: { + byRuleTypeId: { + value: { + ruleTypes: { + '.index-threshold': 2, + 'logs.alert.document.count': 1, + 'document.test.': 1, + }, + ruleTypesDuration: { + '.index-threshold': 2087868, + 'logs.alert.document.count': 1675765, + 'document.test.': 17687687, + }, + }, + }, + failuresByReason: { + value: { + reasons: { + unknown: { + '.index-threshold': 2, + 'logs.alert.document.count': 1, + 'document.test.': 1, + }, + }, + }, + }, + avgDuration: { value: 10 }, + }, + hits: { + hits: [], + }, + }) + ); + + const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test'); + + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + + expect(telemetry).toStrictEqual({ + avgExecutionTime: 0, + avgExecutionTimeByType: { + '__index-threshold': 1043934, + 'document.test__': 17687687, + 'logs.alert.document.count': 1675765, + }, + countByType: { + '__index-threshold': 2, + 'document.test__': 1, + 'logs.alert.document.count': 1, + }, + countFailuresByReason: { + unknown: 4, + }, + countFailuresByReasonByType: { + unknown: { + '.index-threshold': 2, + 'document.test.': 1, + 'logs.alert.document.count': 1, + }, + }, + countTotal: 4, + countTotalFailures: 4, + }); + }); }); diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts index 7ff9538c1aa265..180ee4300f18cf 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -38,6 +38,65 @@ const alertTypeMetric = { }, }; +const ruleTypeExecutionsMetric = { + scripted_metric: { + init_script: 'state.ruleTypes = [:]; state.ruleTypesDuration = [:];', + map_script: ` + String ruleType = doc['rule.category'].value; + long duration = doc['event.duration'].value / (1000 * 1000); + state.ruleTypes.put(ruleType, state.ruleTypes.containsKey(ruleType) ? state.ruleTypes.get(ruleType) + 1 : 1); + state.ruleTypesDuration.put(ruleType, state.ruleTypesDuration.containsKey(ruleType) ? state.ruleTypesDuration.get(ruleType) + duration : duration); + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map result = [:]; + for (Map m : states.toArray()) { + if (m !== null) { + for (String k : m.keySet()) { + result.put(k, result.containsKey(k) ? result.get(k) + m.get(k) : m.get(k)); + } + } + } + return result; + `, + }, +}; + +const ruleTypeFailureExecutionsMetric = { + scripted_metric: { + init_script: 'state.reasons = [:]', + map_script: ` + if (doc['event.outcome'].value == 'failure') { + String reason = doc['event.reason'].value; + String ruleType = doc['rule.category'].value; + Map ruleTypes = state.reasons.containsKey(reason) ? state.reasons.get(reason) : [:]; + ruleTypes.put(ruleType, ruleTypes.containsKey(ruleType) ? ruleTypes.get(ruleType) + 1 : 1); + state.reasons.put(reason, ruleTypes); + } + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map result = [:]; + for (Map m : states.toArray()) { + if (m !== null) { + for (String k : m.keySet()) { + result.put(k, result.containsKey(k) ? result.get(k) + m.get(k) : m.get(k)); + } + } + } + return result; + `, + }, +}; + export async function getTotalCountAggregations( esClient: ElasticsearchClient, kibanaInex: string @@ -260,4 +319,130 @@ function replaceFirstAndLastDotSymbols(strToReplace: string) { return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString; } -// TODO: Implement executions count telemetry with eventLog, when it will write to index +export async function getExecutionsPerDayCount( + esClient: ElasticsearchClient, + eventLogIndex: string +) { + const { body: searchResult } = await esClient.search({ + index: eventLogIndex, + size: 0, + body: { + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { 'event.action': 'execute' }, + }, + { + term: { 'event.provider': 'alerting' }, + }, + { + range: { + '@timestamp': { + gte: 'now-1d', + }, + }, + }, + ], + }, + }, + }, + }, + aggs: { + byRuleTypeId: ruleTypeExecutionsMetric, + failuresByReason: ruleTypeFailureExecutionsMetric, + avgDuration: { avg: { field: 'event.duration' } }, + }, + }, + }); + + const executionsAggregations = searchResult.aggregations as { + byRuleTypeId: { + value: { ruleTypes: Record; ruleTypesDuration: Record }; + }; + }; + + const aggsAvgExecutionTime = Math.round( + // @ts-expect-error aggegation type is not specified + // convert nanoseconds to milliseconds + searchResult.aggregations.avgDuration.value / (1000 * 1000) + ); + + const executionFailuresAggregations = searchResult.aggregations as { + failuresByReason: { value: { reasons: Record> } }; + }; + + return { + countTotal: Object.keys(executionsAggregations.byRuleTypeId.value.ruleTypes).reduce( + (total: number, key: string) => + parseInt(executionsAggregations.byRuleTypeId.value.ruleTypes[key], 10) + total, + 0 + ), + countByType: Object.keys(executionsAggregations.byRuleTypeId.value.ruleTypes).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, key: string) => ({ + ...obj, + [replaceFirstAndLastDotSymbols(key)]: + executionsAggregations.byRuleTypeId.value.ruleTypes[key], + }), + {} + ), + countTotalFailures: Object.keys( + executionFailuresAggregations.failuresByReason.value.reasons + ).reduce((total: number, reason: string) => { + const byRuleTypesRefs = executionFailuresAggregations.failuresByReason.value.reasons[reason]; + const countByRuleTypes = Object.keys(byRuleTypesRefs).reduce( + (totalByType, ruleType) => parseInt(byRuleTypesRefs[ruleType] + totalByType, 10), + 0 + ); + return countByRuleTypes + total; + }, 0), + countFailuresByReason: Object.keys( + executionFailuresAggregations.failuresByReason.value.reasons + ).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, reason: string) => { + const byRuleTypesRefs = + executionFailuresAggregations.failuresByReason.value.reasons[reason]; + const countByRuleTypes = Object.keys(byRuleTypesRefs).reduce( + (totalByType, ruleType) => parseInt(byRuleTypesRefs[ruleType] + totalByType, 10), + 0 + ); + return { + ...obj, + [replaceFirstAndLastDotSymbols(reason)]: countByRuleTypes, + }; + }, + {} + ), + countFailuresByReasonByType: Object.keys( + executionFailuresAggregations.failuresByReason.value.reasons + ).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, key: string) => ({ + ...obj, + [replaceFirstAndLastDotSymbols(key)]: + executionFailuresAggregations.failuresByReason.value.reasons[key], + }), + {} + ), + avgExecutionTime: aggsAvgExecutionTime, + avgExecutionTimeByType: Object.keys(executionsAggregations.byRuleTypeId.value.ruleTypes).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, key: string) => ({ + ...obj, + [replaceFirstAndLastDotSymbols(key)]: Math.round( + executionsAggregations.byRuleTypeId.value.ruleTypesDuration[key] / + parseInt(executionsAggregations.byRuleTypeId.value.ruleTypes[key], 10) + ), + }), + {} + ), + }; +} diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts index e9405c51dbf15b..e5b25ea75fc1ca 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts @@ -50,6 +50,26 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = { xpack__ml__anomaly_detection_jobs_health: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention }; +const byReasonSchema: MakeSchemaFrom['count_rules_executions_failured_by_reason_per_day'] = + { + // TODO: Find out an automated way to populate the keys or reformat these into an array (and change the Remote Telemetry indexer accordingly) + DYNAMIC_KEY: { type: 'long' }, + read: { type: 'long' }, + decrypt: { type: 'long' }, + license: { type: 'long' }, + unknown: { type: 'long' }, + }; + +const byReasonSchemaByType: MakeSchemaFrom['count_rules_executions_failured_by_reason_by_type_per_day'] = + { + // TODO: Find out an automated way to populate the keys or reformat these into an array (and change the Remote Telemetry indexer accordingly) + DYNAMIC_KEY: byTypeSchema, + read: byTypeSchema, + decrypt: byTypeSchema, + license: byTypeSchema, + unknown: byTypeSchema, + }; + export function createAlertsUsageCollector( usageCollection: UsageCollectionSetup, taskManager: Promise @@ -92,6 +112,13 @@ export function createAlertsUsageCollector( count_active_by_type: {}, count_by_type: {}, count_rules_namespaces: 0, + count_rules_executions_per_day: 0, + count_rules_executions_by_type_per_day: {}, + count_rules_executions_failured_per_day: 0, + count_rules_executions_failured_by_reason_per_day: {}, + count_rules_executions_failured_by_reason_by_type_per_day: {}, + avg_execution_time_per_day: 0, + avg_execution_time_by_type_per_day: {}, }; } }, @@ -117,6 +144,13 @@ export function createAlertsUsageCollector( count_active_by_type: byTypeSchema, count_by_type: byTypeSchema, count_rules_namespaces: { type: 'long' }, + count_rules_executions_per_day: { type: 'long' }, + count_rules_executions_by_type_per_day: byTypeSchema, + count_rules_executions_failured_per_day: { type: 'long' }, + count_rules_executions_failured_by_reason_per_day: byReasonSchema, + count_rules_executions_failured_by_reason_by_type_per_day: byReasonSchemaByType, + avg_execution_time_per_day: { type: 'long' }, + avg_execution_time_by_type_per_day: byTypeSchema, }, }); } diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 9d39b3765cb5dc..2fbd56c105c31f 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -7,13 +7,18 @@ import { Logger, CoreSetup } from 'kibana/server'; import moment from 'moment'; +import { IEventLogService } from '../../../event_log/server'; import { RunContext, TaskManagerSetupContract, TaskManagerStartContract, } from '../../../task_manager/server'; -import { getTotalCountAggregations, getTotalCountInUse } from './alerts_telemetry'; +import { + getTotalCountAggregations, + getTotalCountInUse, + getExecutionsPerDayCount, +} from './alerts_telemetry'; export const TELEMETRY_TASK_TYPE = 'alerting_telemetry'; @@ -23,9 +28,10 @@ export function initializeAlertingTelemetry( logger: Logger, core: CoreSetup, taskManager: TaskManagerSetupContract, - kibanaIndex: string + kibanaIndex: string, + eventLog: IEventLogService ) { - registerAlertingTelemetryTask(logger, core, taskManager, kibanaIndex); + registerAlertingTelemetryTask(logger, core, taskManager, kibanaIndex, eventLog); } export function scheduleAlertingTelemetry(logger: Logger, taskManager?: TaskManagerStartContract) { @@ -38,13 +44,14 @@ function registerAlertingTelemetryTask( logger: Logger, core: CoreSetup, taskManager: TaskManagerSetupContract, - kibanaIndex: string + kibanaIndex: string, + eventLog: IEventLogService ) { taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Alerting usage fetch task', timeout: '5m', - createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex), + createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex, eventLog), }, }); } @@ -62,9 +69,15 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra } } -export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { +export function telemetryTaskRunner( + logger: Logger, + core: CoreSetup, + kibanaIndex: string, + eventLog: IEventLogService +) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; + const eventLogIndex = eventLog.getIndexPattern(); const getEsClient = () => core.getStartServices().then( ([ @@ -80,8 +93,9 @@ export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex return Promise.all([ getTotalCountAggregations(esClient, kibanaIndex), getTotalCountInUse(esClient, kibanaIndex), + getExecutionsPerDayCount(esClient, eventLogIndex), ]) - .then(([totalCountAggregations, totalInUse]) => { + .then(([totalCountAggregations, totalInUse, totalExecutions]) => { return { state: { runs: (state.runs || 0) + 1, @@ -90,6 +104,15 @@ export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex count_active_total: totalInUse.countTotal, count_disabled_total: totalCountAggregations.count_total - totalInUse.countTotal, count_rules_namespaces: totalInUse.countNamespaces, + count_rules_executions_per_day: totalExecutions.countTotal, + count_rules_executions_by_type_per_day: totalExecutions.countByType, + count_rules_executions_failured_per_day: totalExecutions.countTotalFailures, + count_rules_executions_failured_by_reason_per_day: + totalExecutions.countFailuresByReason, + count_rules_executions_failured_by_reason_by_type_per_day: + totalExecutions.countFailuresByReasonByType, + avg_execution_time_per_day: totalExecutions.avgExecutionTime, + avg_execution_time_by_type_per_day: totalExecutions.avgExecutionTimeByType, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerting/server/usage/types.ts index 0e489893a1bbc1..50d9b80c44b707 100644 --- a/x-pack/plugins/alerting/server/usage/types.ts +++ b/x-pack/plugins/alerting/server/usage/types.ts @@ -12,6 +12,13 @@ export interface AlertsUsage { count_by_type: Record; count_active_by_type: Record; count_rules_namespaces: number; + count_rules_executions_per_day: number; + count_rules_executions_by_type_per_day: Record; + count_rules_executions_failured_per_day: number; + count_rules_executions_failured_by_reason_per_day: Record; + count_rules_executions_failured_by_reason_by_type_per_day: Record>; + avg_execution_time_per_day: number; + avg_execution_time_by_type_per_day: Record; throttle_time: { min: number; avg: number; diff --git a/x-pack/plugins/apm/dev_docs/routing_and_linking.md b/x-pack/plugins/apm/dev_docs/routing_and_linking.md index a1fdff3821c4c1..1f6160a6c4a99a 100644 --- a/x-pack/plugins/apm/dev_docs/routing_and_linking.md +++ b/x-pack/plugins/apm/dev_docs/routing_and_linking.md @@ -52,7 +52,7 @@ const { `useApmParams` will strip query parameters for which there is no validation. The route path should match exactly, but you can also use wildcards: `useApmParams('/*)`. In that case, the return type will be a union type of all possible matching routes. -Previously we used `useUrlParams` for path and query parameters, which we are trying to get away from. When possible, any usage of `useUrlParams` should be replaced by `useApmParams` or other custom hooks that use `useApmParams` internally. +Previously we used `useLegacyUrlParams` for path and query parameters, which we are trying to get away from. When possible, any usage of `useLegacyUrlParams` should be replaced by `useApmParams` or other custom hooks that use `useApmParams` internally. ## Linking diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md index ba48e7e229e273..2a7533402ecca3 100644 --- a/x-pack/plugins/apm/dev_docs/testing.md +++ b/x-pack/plugins/apm/dev_docs/testing.md @@ -27,11 +27,12 @@ API tests are separated in two suites: node scripts/test/api [--trial] [--help] ``` -The API tests are located in `x-pack/test/apm_api_integration/`. +The API tests are located in [`x-pack/test/apm_api_integration/`](/x-pack/test/apm_api_integration/). **API Test tips** -- For debugging access Elasticsearch on http://localhost:9220 (`elastic` / `changeme`) +- For data generation in API tests have a look at the [elastic-apm-synthtrace](../../../../packages/elastic-apm-synthtrace/README.md) package +- For debugging access Elasticsearch on http://localhost:9220 and Kibana on http://localhost:5620 (`elastic` / `changeme`) - To update snapshots append `--updateSnapshots` to the functional_test_runner command --- @@ -42,11 +43,12 @@ The API tests are located in `x-pack/test/apm_api_integration/`. node scripts/test/e2e [--trial] [--help] ``` -The E2E tests are located [here](../ftr_e2e) +The E2E tests are located in [`x-pack/plugins/apm/ftr_e2e`](../ftr_e2e) --- ## Functional tests (Security and Correlations tests) + TODO: We could try moving this tests to the new e2e tests located at `x-pack/plugins/apm/ftr_e2e`. **Start server** @@ -65,12 +67,18 @@ APM tests are located in `x-pack/test/functional/apps/apm`. For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) diff --git a/x-pack/plugins/apm/scripts/test/README.md b/x-pack/plugins/apm/scripts/test/README.md - ## Storybook ### Start + ``` yarn storybook apm ``` All files with a .stories.tsx extension will be loaded. You can access the development environment at http://localhost:9001. + +## Data generation + +For end-to-end (e.g. agent -> apm server -> elasticsearch <- kibana) development and testing of Elastic APM please check the the [APM Integration Testing repository](https://github.com/elastic/apm-integration-testing). + +Data can also be generated using the [elastic-apm-synthtrace](../../../../packages/elastic-apm-synthtrace/README.md) CLI. diff --git a/x-pack/plugins/apm/ftr_e2e/cypress.json b/x-pack/plugins/apm/ftr_e2e/cypress.json index ee62f12976678d..1791baaa5aae49 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress.json +++ b/x-pack/plugins/apm/ftr_e2e/cypress.json @@ -2,7 +2,7 @@ "fileServerFolder": "./cypress", "fixturesFolder": "./cypress/fixtures", "integrationFolder": "./cypress/integration", - "pluginsFile": "./cypress/plugins/index.js", + "pluginsFile": "./cypress/plugins/index.ts", "screenshotsFolder": "./cypress/screenshots", "supportFile": "./cypress/support/index.ts", "videosFolder": "./cypress/videos", @@ -13,4 +13,4 @@ "viewportWidth": 1440, "video": false, "screenshotOnRunFailure": false -} \ No newline at end of file +} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_8.0.0/data.json.gz b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_8.0.0/data.json.gz deleted file mode 100644 index 1450997a0107e6..00000000000000 Binary files a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_8.0.0/data.json.gz and /dev/null differ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_8.0.0/mappings.json b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_8.0.0_empty/mappings.json similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_8.0.0/mappings.json rename to x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_8.0.0_empty/mappings.json diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0_empty/mappings.json b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_mappings_only_8.0.0/mappings.json similarity index 100% rename from x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0_empty/mappings.json rename to x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/apm_mappings_only_8.0.0/mappings.json diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts new file mode 100644 index 00000000000000..bd01c83b9cc6ef --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts @@ -0,0 +1,70 @@ +/* + * 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 { + service, + browser, + timerange, + getChromeUserAgentDefaults, +} from '@elastic/apm-synthtrace'; + +export function opbeans({ from, to }: { from: number; to: number }) { + const range = timerange(from, to); + + const opbeansJava = service('opbeans-java', 'production', 'java') + .instance('opbeans-java-prod-1') + .podId('opbeans-java-prod-1-pod'); + + const opbeansNode = service('opbeans-node', 'production', 'nodejs').instance( + 'opbeans-node-prod-1' + ); + + const opbeansRum = browser( + 'opbeans-rum', + 'production', + getChromeUserAgentDefaults() + ); + + return [ + ...range + .interval('1s') + .rate(1) + .flatMap((timestamp) => [ + ...opbeansJava + .transaction('GET /api/product') + .timestamp(timestamp) + .duration(1000) + .success() + .errors(opbeansJava.error('[MockError] Foo').timestamp(timestamp)) + .children( + opbeansJava + .span('SELECT * FROM product', 'db', 'postgresql') + .timestamp(timestamp) + .duration(50) + .success() + .destination('postgresql') + ) + .serialize(), + ...opbeansNode + .transaction('GET /api/product/:id') + .timestamp(timestamp) + .duration(500) + .success() + .serialize(), + ...opbeansNode + .transaction('Worker job', 'Worker') + .timestamp(timestamp) + .duration(1000) + .success() + .serialize(), + ...opbeansRum + .transaction('/') + .timestamp(timestamp) + .duration(1000) + .serialize(), + ]), + ]; +} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts index 5b4a48b65b33fd..89e203860179ff 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts @@ -52,6 +52,10 @@ describe('Rules', () => { cy.contains('Error count').click(); cy.contains('Create threshold rule').click(); + // Check for the existence of this element to make sure the form + // has loaded. + cy.contains('for the last'); + // Save, with no actions cy.contains('button:not(:disabled)', 'Save').click(); cy.get(confirmModalButtonSelector).click(); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/dependencies.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/dependencies.spec.ts index 0ab2d5682a900c..2c2e93d463c508 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/dependencies.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/dependencies.spec.ts @@ -4,13 +4,31 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { synthtrace } from '../../../synthtrace'; +import { opbeans } from '../../fixtures/synthtrace/opbeans'; + +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; const timeRange = { - rangeFrom: Cypress.env('START_DATE'), - rangeTo: Cypress.env('END_DATE'), + rangeFrom: start, + rangeTo: end, }; describe('Dependencies', () => { + before(async () => { + await synthtrace.index( + opbeans({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + beforeEach(() => { cy.loginAsReadOnlyUser(); }); @@ -40,21 +58,21 @@ describe('Dependencies', () => { cy.get('[data-test-subj="throughputChart"]'); cy.get('[data-test-subj="errorRateChart"]'); - cy.contains('opbeans-python').click({ force: true }); + cy.contains('opbeans-java').click({ force: true }); - cy.contains('h1', 'opbeans-python'); + cy.contains('h1', 'opbeans-java'); }); }); describe('service overview page', () => { it('shows dependency information and you can navigate to a page for a dependency', () => { cy.visit( - `/app/apm/services/opbeans-python/overview?${new URLSearchParams( + `/app/apm/services/opbeans-java/overview?${new URLSearchParams( timeRange )}` ); - cy.contains('postgresql').click({ force: true }); + cy.contains('a', 'postgresql').click({ force: true }); cy.contains('h1', 'postgresql'); }); @@ -63,7 +81,7 @@ describe('Dependencies', () => { describe('service dependencies tab', () => { it('shows dependency information and you can navigate to a page for a dependency', () => { cy.visit( - `/app/apm/services/opbeans-python/overview?${new URLSearchParams( + `/app/apm/services/opbeans-java/overview?${new URLSearchParams( timeRange )}` ); @@ -72,7 +90,7 @@ describe('Dependencies', () => { cy.contains('Time spent by dependency'); - cy.contains('postgresql').click({ force: true }); + cy.contains('a', 'postgresql').click({ force: true }); cy.contains('h1', 'postgresql'); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts index 8457282f5c2566..1e09ec6dbf7c13 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts @@ -6,9 +6,11 @@ */ import url from 'url'; -import archives_metadata from '../../fixtures/es_archiver/archives_metadata'; +import { synthtrace } from '../../../synthtrace'; +import { opbeans } from '../../fixtures/synthtrace/opbeans'; -const { start, end } = archives_metadata['apm_8.0.0']; +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; const serviceInventoryHref = url.format({ pathname: '/app/apm/services', @@ -27,6 +29,19 @@ const apisToIntercept = [ ]; describe('Home page', () => { + before(async () => { + await synthtrace.index( + opbeans({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + beforeEach(() => { cy.loginAsReadOnlyUser(); }); @@ -44,7 +59,6 @@ describe('Home page', () => { cy.visit( `${serviceInventoryHref}&kuery=not%20(processor.event%3A%22transaction%22)` ); - cy.contains('opbeans-python'); cy.contains('opbeans-java'); cy.contains('opbeans-node'); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts index 6950a0bbadb9cd..a7667002d4ea95 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts @@ -5,9 +5,11 @@ * 2.0. */ import url from 'url'; -import archives_metadata from '../../../fixtures/es_archiver/archives_metadata'; +import { synthtrace } from '../../../../synthtrace'; +import { opbeans } from '../../../fixtures/synthtrace/opbeans'; -const { start, end } = archives_metadata['apm_8.0.0']; +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; const serviceOverviewHref = url.format({ pathname: '/app/apm/services/opbeans-node/overview', @@ -62,6 +64,16 @@ const apisToIntercept = [ ]; describe('Service overview - header filters', () => { + before(async () => { + await synthtrace.index( + opbeans({ from: new Date(start).getTime(), to: new Date(end).getTime() }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + beforeEach(() => { cy.loginAsReadOnlyUser(); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts index c7d9fa4e32106c..dad92b83349818 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts @@ -6,15 +6,19 @@ */ import url from 'url'; -import archives_metadata from '../../../fixtures/es_archiver/archives_metadata'; +import { synthtrace } from '../../../../synthtrace'; +import { opbeans } from '../../../fixtures/synthtrace/opbeans'; -const { start, end } = archives_metadata['apm_8.0.0']; +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; const serviceOverviewHref = url.format({ pathname: '/app/apm/services/opbeans-java/overview', query: { rangeFrom: start, rangeTo: end }, }); +const serviceNodeName = 'opbeans-java-prod-1'; + const apisToIntercept = [ { endpoint: @@ -27,8 +31,7 @@ const apisToIntercept = [ name: 'instancesDetailsRequest', }, { - endpoint: - '/internal/apm/services/opbeans-java/service_overview_instances/details/31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad?*', + endpoint: `/internal/apm/services/opbeans-java/service_overview_instances/details/${serviceNodeName}?*`, name: 'instanceDetailsRequest', }, ]; @@ -49,8 +52,18 @@ describe('Instances table', () => { // }); describe('when data is loaded', () => { - const serviceNodeName = - '31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad'; + before(async () => { + await synthtrace.index( + opbeans({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); it('has data in the table', () => { cy.visit(serviceOverviewHref); @@ -91,7 +104,7 @@ describe('Instances table', () => { cy.wait('@instancesDetailsRequest'); cy.get( `[data-test-subj="instanceActionsButton_${serviceNodeName}"]` - ).realClick(); + ).click(); cy.contains('Pod logs'); cy.contains('Pod metrics'); cy.contains('Container logs'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts index b0cf424b8067e1..5601ade671908f 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts @@ -6,9 +6,11 @@ */ import url from 'url'; -import archives_metadata from '../../../fixtures/es_archiver/archives_metadata'; +import { synthtrace } from '../../../../synthtrace'; +import { opbeans } from '../../../fixtures/synthtrace/opbeans'; -const { start, end } = archives_metadata['apm_8.0.0']; +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; const serviceOverviewPath = '/app/apm/services/opbeans-node/overview'; const baseUrl = url.format({ @@ -17,6 +19,19 @@ const baseUrl = url.format({ }); describe('Service Overview', () => { + before(async () => { + await synthtrace.index( + opbeans({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + beforeEach(() => { cy.loginAsReadOnlyUser(); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts index 65a82a8ab6bdf1..e905c86a5854c0 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts @@ -6,9 +6,11 @@ */ import url from 'url'; import moment from 'moment'; -import archives_metadata from '../../../fixtures/es_archiver/archives_metadata'; +import { synthtrace } from '../../../../synthtrace'; +import { opbeans } from '../../../fixtures/synthtrace/opbeans'; -const { start, end } = archives_metadata['apm_8.0.0']; +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; const serviceOverviewPath = '/app/apm/services/opbeans-java/overview'; const serviceOverviewHref = url.format({ @@ -49,6 +51,19 @@ const apisToIntercept = [ ]; describe('Service overview: Time Comparison', () => { + before(async () => { + await synthtrace.index( + opbeans({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + beforeEach(() => { cy.loginAsReadOnlyUser(); }); @@ -79,8 +94,12 @@ describe('Service overview: Time Comparison', () => { cy.contains('opbeans-java'); cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled'); - const comparisonStartEnd = - 'comparisonStart=2021-08-02T06%3A50%3A00.000Z&comparisonEnd=2021-08-02T07%3A20%3A15.910Z'; + const comparisonStartEnd = `comparisonStart=${encodeURIComponent( + moment(start).subtract(1, 'day').toISOString() + )}&comparisonEnd=${encodeURIComponent( + moment(end).subtract(1, 'day').toISOString() + )}`; + // When the page loads it fetches all APIs with comparison time range cy.wait(apisToIntercept.map(({ name }) => `@${name}`)).then( (interceptions) => { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transactions_overview/transactions_overview.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transactions_overview/transactions_overview.spec.ts index 9180e6371fda7d..3deb4b8619f603 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transactions_overview/transactions_overview.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transactions_overview/transactions_overview.spec.ts @@ -6,9 +6,11 @@ */ import url from 'url'; -import archives_metadata from '../../../fixtures/es_archiver/archives_metadata'; +import { synthtrace } from '../../../../synthtrace'; +import { opbeans } from '../../../fixtures/synthtrace/opbeans'; -const { start, end } = archives_metadata['apm_8.0.0']; +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; const serviceOverviewHref = url.format({ pathname: '/app/apm/services/opbeans-node/transactions', @@ -16,6 +18,19 @@ const serviceOverviewHref = url.format({ }); describe('Transactions Overview', () => { + before(async () => { + await synthtrace.index( + opbeans({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + beforeEach(() => { cy.loginAsReadOnlyUser(); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts new file mode 100644 index 00000000000000..350d90ccb3fe49 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts @@ -0,0 +1,61 @@ +/* + * 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 Fs from 'fs'; +import { Client, HttpConnection } from '@elastic/elasticsearch'; +import { SynthtraceEsClient } from '@elastic/apm-synthtrace'; +import { createLogger, LogLevel } from '@elastic/apm-synthtrace'; +import { CA_CERT_PATH } from '@kbn/dev-utils'; + +// *********************************************************** +// This example plugins/index.ts can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +const plugin: Cypress.PluginConfig = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config + + const node = config.env.ES_NODE; + const requestTimeout = config.env.ES_REQUEST_TIMEOUT; + const isCloud = config.env.TEST_CLOUD; + + const client = new Client({ + node, + requestTimeout, + Connection: HttpConnection, + ...(isCloud ? { tls: { ca: Fs.readFileSync(CA_CERT_PATH, 'utf-8') } } : {}), + }); + + const synthtraceEsClient = new SynthtraceEsClient( + client, + createLogger(LogLevel.info) + ); + + on('task', { + 'synthtrace:index': async (events) => { + await synthtraceEsClient.index(events); + return null; + }, + 'synthtrace:clean': async () => { + await synthtraceEsClient.clean(); + return null; + }, + }); +}; + +module.exports = plugin; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress_open.ts b/x-pack/plugins/apm/ftr_e2e/cypress_open.ts deleted file mode 100644 index 3f7758b40b90df..00000000000000 --- a/x-pack/plugins/apm/ftr_e2e/cypress_open.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrConfigProviderContext } from '@kbn/test'; -import { cypressOpenTests } from './cypress_start'; - -async function openE2ETests({ readConfigFile }: FtrConfigProviderContext) { - const kibanaConfig = await readConfigFile(require.resolve('./config.ts')); - return { - ...kibanaConfig.getAll(), - testRunner: cypressOpenTests, - }; -} - -// eslint-disable-next-line import/no-default-export -export default openE2ETests; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress_run.ts b/x-pack/plugins/apm/ftr_e2e/cypress_run.ts deleted file mode 100644 index 16f93b39910f3f..00000000000000 --- a/x-pack/plugins/apm/ftr_e2e/cypress_run.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { argv } from 'yargs'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { cypressRunTests } from './cypress_start'; - -const specArg = argv.spec as string | undefined; - -async function runE2ETests({ readConfigFile }: FtrConfigProviderContext) { - const kibanaConfig = await readConfigFile(require.resolve('./config.ts')); - return { - ...kibanaConfig.getAll(), - testRunner: cypressRunTests(specArg), - }; -} - -// eslint-disable-next-line import/no-default-export -export default runE2ETests; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress_start.ts b/x-pack/plugins/apm/ftr_e2e/cypress_start.ts index 0cfc58653801a6..c8ab216cbce5cc 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress_start.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress_start.ts @@ -7,36 +7,20 @@ /* eslint-disable no-console */ +import { argv } from 'yargs'; import Url from 'url'; import cypress from 'cypress'; import { FtrProviderContext } from './ftr_provider_context'; -import archives_metadata from './cypress/fixtures/es_archiver/archives_metadata'; import { createApmUsersAndRoles } from '../scripts/create-apm-users-and-roles/create_apm_users_and_roles'; import { esArchiverLoad, esArchiverUnload } from './cypress/tasks/es_archiver'; -export function cypressRunTests(spec?: string) { - return async ({ getService }: FtrProviderContext) => { - const result = await cypressStart(getService, cypress.run, spec); - - if (result && (result.status === 'failed' || result.totalFailed > 0)) { - throw new Error(`APM Cypress tests failed`); - } - }; -} - -export async function cypressOpenTests({ getService }: FtrProviderContext) { - await cypressStart(getService, cypress.open); -} - -async function cypressStart( +export async function cypressStart( getService: FtrProviderContext['getService'], - cypressExecution: typeof cypress.run | typeof cypress.open, - spec?: string + cypressExecution: typeof cypress.run | typeof cypress.open ) { const config = getService('config'); - const archiveName = 'apm_8.0.0'; - const { start, end } = archives_metadata[archiveName]; + const archiveName = 'apm_mappings_only_8.0.0'; const kibanaUrl = Url.format({ protocol: config.get('servers.kibana.protocol'), @@ -56,21 +40,34 @@ async function cypressStart( }, }); - console.log('Loading esArchiver...'); - await esArchiverLoad('apm_8.0.0'); + const esNode = Url.format({ + protocol: config.get('servers.elasticsearch.protocol'), + port: config.get('servers.elasticsearch.port'), + hostname: config.get('servers.elasticsearch.hostname'), + auth: `${config.get('servers.elasticsearch.username')}:${config.get( + 'servers.elasticsearch.password' + )}`, + }); + + const esRequestTimeout = config.get('timeouts.esRequestTimeout'); + + console.log(`Loading ES archive "${archiveName}"`); + await esArchiverLoad(archiveName); + const spec = argv.grep as string | undefined; const res = await cypressExecution({ - ...(spec !== undefined ? { spec } : {}), + ...(spec ? { spec } : {}), config: { baseUrl: kibanaUrl }, env: { - START_DATE: start, - END_DATE: end, KIBANA_URL: kibanaUrl, + ES_NODE: esNode, + ES_REQUEST_TIMEOUT: esRequestTimeout, + TEST_CLOUD: process.env.TEST_CLOUD, }, }); - console.log('Removing esArchiver...'); - await esArchiverUnload('apm_8.0.0'); + console.log('Unloading ES archives...'); + await esArchiverUnload(archiveName); return res; } diff --git a/x-pack/plugins/apm/ftr_e2e/config.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/config.ts rename to x-pack/plugins/apm/ftr_e2e/ftr_config.ts diff --git a/x-pack/plugins/apm/ftr_e2e/ftr_config_open.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config_open.ts new file mode 100644 index 00000000000000..92f605d3287899 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/ftr_config_open.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 { FtrConfigProviderContext } from '@kbn/test'; +import cypress from 'cypress'; +import { FtrProviderContext } from './ftr_provider_context'; +import { cypressStart } from './cypress_start'; + +async function ftrConfigOpen({ readConfigFile }: FtrConfigProviderContext) { + const kibanaConfig = await readConfigFile(require.resolve('./ftr_config.ts')); + return { + ...kibanaConfig.getAll(), + testRunner, + }; +} + +export async function testRunner({ getService }: FtrProviderContext) { + await cypressStart(getService, cypress.open); +} + +// eslint-disable-next-line import/no-default-export +export default ftrConfigOpen; diff --git a/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts new file mode 100644 index 00000000000000..51c859a8477f23 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts @@ -0,0 +1,30 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; +import cypress from 'cypress'; +import { cypressStart } from './cypress_start'; +import { FtrProviderContext } from './ftr_provider_context'; + +async function ftrConfigRun({ readConfigFile }: FtrConfigProviderContext) { + const kibanaConfig = await readConfigFile(require.resolve('./ftr_config.ts')); + return { + ...kibanaConfig.getAll(), + testRunner, + }; +} + +async function testRunner({ getService }: FtrProviderContext) { + const result = await cypressStart(getService, cypress.run); + + if (result && (result.status === 'failed' || result.totalFailed > 0)) { + throw new Error(`APM Cypress tests failed`); + } +} + +// eslint-disable-next-line import/no-default-export +export default ftrConfigRun; diff --git a/x-pack/plugins/apm/ftr_e2e/synthtrace.ts b/x-pack/plugins/apm/ftr_e2e/synthtrace.ts new file mode 100644 index 00000000000000..3c818b65200b63 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/synthtrace.ts @@ -0,0 +1,17 @@ +/* + * 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 synthtrace = { + index: (events: any[]) => + new Promise((resolve) => { + cy.task('synthtrace:index', events).then(resolve); + }), + clean: () => + new Promise((resolve) => { + cy.task('synthtrace:clean').then(resolve); + }), +}; diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js index 66b4b164a794c7..cc985407698bfd 100644 --- a/x-pack/plugins/apm/jest.config.js +++ b/x-pack/plugins/apm/jest.config.js @@ -19,5 +19,6 @@ module.exports = { coverageReporters: ['text', 'html'], collectCoverageFrom: [ '/x-pack/plugins/apm/{common,public,server}/**/*.{js,ts,tsx}', + '!/**/*.stories.*', ], }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx index 593de7c3a6f705..3bf21de7487de2 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx @@ -12,7 +12,7 @@ import { createExploratoryViewUrl, HeaderMenuPortal, } from '../../../../../../observability/public'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { AppMountParameters } from '../../../../../../../../src/core/public'; import { InspectorHeaderLink } from '../../../shared/apm_header_action_menu/inspector_header_link'; @@ -38,7 +38,7 @@ export function UXActionMenu({ const { services: { http }, } = useKibana(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { rangeTo, rangeFrom, serviceName } = urlParams; const uxExploratoryViewLink = createExploratoryViewUrl( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index 6be2eada6a9ec9..e5ee427e166771 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -28,7 +28,7 @@ import moment from 'moment'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { ChartWrapper } from '../ChartWrapper'; import { I18LABELS } from '../translations'; @@ -43,7 +43,7 @@ interface Props { export function PageViewsChart({ data, loading }: Props) { const history = useHistory(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { start, end } = urlParams; const diffInDays = moment(new Date(end as string)).diff( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index c525a71ea45891..7c48531d21990c 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -15,13 +15,13 @@ import { } from '@elastic/eui'; import { I18LABELS } from '../translations'; import { getPercentileLabel } from '../UXMetrics/translations'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { Metrics } from './Metrics'; export function ClientMetrics() { const { urlParams: { percentile }, - } = useUrlParams(); + } = useLegacyUrlParams(); return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx index c8956c091d267e..b8bdc36ed4e0dc 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx @@ -18,7 +18,7 @@ import { import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { CsmSharedContext } from '../CsmSharedContext'; @@ -31,7 +31,7 @@ interface JSErrorItem { } export function JSErrors() { - const { urlParams, uxUiFilters } = useUrlParams(); + const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, serviceName, searchTerm } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/SelectedFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/SelectedFilters.tsx index ee0827c4d81e55..d9f9154c5c100e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/SelectedFilters.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/SelectedFilters.tsx @@ -9,7 +9,7 @@ import React, { Fragment } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FilterValueLabel } from '../../../../../../observability/public'; import { FiltersUIHook } from '../hooks/useLocalUIFilters'; import { UxLocalUIFilterName } from '../../../../../common/ux_ui_filter'; @@ -38,7 +38,7 @@ export function SelectedFilters({ const { uxUiFilters, urlParams: { searchTerm }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { transactionUrl } = uxUiFilters; const urlValues = transactionUrl ?? []; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/selected_wildcards.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/selected_wildcards.tsx index 1bc0807bd2f716..6a9dfd1fddd11a 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/selected_wildcards.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/selected_wildcards.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { FilterValueLabel } from '../../../../../../observability/public'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { TRANSACTION_URL } from '../../../../../common/elasticsearch_fieldnames'; import { IndexPattern } from '../../../../../../../../src/plugins/data_views/common'; @@ -22,7 +22,7 @@ export function SelectedWildcards({ indexPattern }: Props) { const { urlParams: { searchTerm }, - } = useUrlParams(); + } = useLegacyUrlParams(); const updateSearchTerm = useCallback( (searchTermN: string) => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index a9dac0a37c3533..f75d0fd093b5a2 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -14,7 +14,7 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; @@ -34,7 +34,7 @@ export function PageLoadDistribution() { services: { http }, } = useKibana(); - const { urlParams, uxUiFilters } = useUrlParams(); + const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, rangeFrom, rangeTo, searchTerm } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index e43ba9c7e557e5..0cfa293c878444 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -6,7 +6,7 @@ */ import { useFetcher } from '../../../../hooks/use_fetcher'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { PercentileRange } from './index'; interface Props { @@ -16,7 +16,7 @@ interface Props { } export const useBreakdowns = ({ percentileRange, field, value }: Props) => { - const { urlParams, uxUiFilters } = useUrlParams(); + const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, searchTerm } = urlParams; const { min: minP, max: maxP } = percentileRange ?? {}; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 5f7c0738e56420..581260f5931e7a 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -14,7 +14,7 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; @@ -28,7 +28,7 @@ export function PageViewsTrend() { services: { http }, } = useKibana(); - const { urlParams, uxUiFilters } = useUrlParams(); + const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { serviceName } = uxUiFilters; const { start, end, searchTerm, rangeTo, rangeFrom } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx index 58cffb39d0a042..5b1cca0ec44fa0 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx @@ -9,12 +9,12 @@ import React from 'react'; import { ServiceNameFilter } from '../URLFilter/ServiceNameFilter'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; export function WebApplicationSelect() { const { urlParams: { start, end }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { data, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx index cc4bd0d14e290a..54c34121ea0cb3 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx @@ -48,7 +48,7 @@ export function RumHome() { 'Enable RUM with the APM agent to collect user experience data.', } ), - href: core.http.basePath.prepend(`integrations/detail/apm`), + href: core.http.basePath.prepend(`/app/home#/tutorial/apm`), }, }, docsLink: core.docLinks.links.observability.guide, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx index 5750dd9cf441e1..f6891a5d8fb672 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx @@ -9,7 +9,7 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; interface Props { @@ -21,7 +21,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) { const history = useHistory(); const { urlParams: { serviceName: selectedServiceName }, - } = useUrlParams(); + } = useLegacyUrlParams(); const options = serviceNames.map((type) => ({ text: type, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx index 7aa8d5d85e539b..e34270f963599d 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'; import { isEqual, map } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { I18LABELS } from '../../translations'; import { formatToSec } from '../../UXMetrics/KeyUXMetrics'; import { getPercentileLabel } from '../../UXMetrics/translations'; @@ -93,7 +93,7 @@ export function URLSearch({ const { uxUiFilters: { transactionUrl, transactionUrlExcluded }, urlParams, - } = useUrlParams(); + } = useLegacyUrlParams(); const { searchTerm, percentile } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx index 64f51714ed66eb..7b6b093c703673 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx @@ -9,7 +9,7 @@ import useDebounce from 'react-use/lib/useDebounce'; import { useState } from 'react'; import { useFetcher } from '../../../../../hooks/use_fetcher'; import { useUxQuery } from '../../hooks/useUxQuery'; -import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../../context/url_params_context/use_url_params'; interface Props { popoverIsOpen: boolean; @@ -19,7 +19,7 @@ interface Props { export const useUrlSearch = ({ popoverIsOpen, query }: Props) => { const uxQuery = useUxQuery(); - const { uxUiFilters } = useUrlParams(); + const { uxUiFilters } = useLegacyUrlParams(); const { transactionUrl, transactionUrlExcluded, ...restFilters } = uxUiFilters; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index dd93a0143c05a9..673f045ecfb978 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -20,13 +20,13 @@ import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { getCoreVitalsComponent } from '../../../../../../observability/public'; import { CsmSharedContext } from '../CsmSharedContext'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getPercentileLabel } from './translations'; export function UXMetrics() { const { urlParams: { percentile }, - } = useUrlParams(); + } = useLegacyUrlParams(); const uxQuery = useUxQuery(); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx index 0a5669095c2e52..7d05b188e9bbed 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useEffect } from 'react'; import { EuiSelect } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { I18LABELS } from '../translations'; @@ -20,7 +20,7 @@ export function UserPercentile() { const { urlParams: { percentile }, - } = useUrlParams(); + } = useLegacyUrlParams(); const updatePercentile = useCallback( (percentileN?: number, replaceHistory?: boolean) => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index a4edd56310ea11..f044890a9b6499 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -10,10 +10,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { VisitorBreakdownChart } from '../Charts/VisitorBreakdownChart'; import { I18LABELS, VisitorBreakdownLabel } from '../translations'; import { useFetcher } from '../../../../hooks/use_fetcher'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; export function VisitorBreakdown() { - const { urlParams, uxUiFilters } = useUrlParams(); + const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, searchTerm } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx index b468b5c9133875..17a380f4d5e35c 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx @@ -21,7 +21,7 @@ import { isErrorEmbeddable, } from '../../../../../../../../src/plugins/embeddable/public'; import { useLayerList } from './useLayerList'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import type { RenderTooltipContentParams } from '../../../../../../maps/public'; import { MapToolTip } from './MapToolTip'; @@ -50,7 +50,7 @@ interface KibanaDeps { embeddable: EmbeddableStart; } export function EmbeddedMapComponent() { - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const apmPluginContext = useApmPluginContext(); const { start, end, serviceName } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts index ce42b530b80f57..7f81e9d1051867 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts @@ -22,7 +22,7 @@ import { } from '../../../../../../maps/common'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { SERVICE_NAME, TRANSACTION_TYPE, @@ -84,7 +84,7 @@ interface VectorLayerDescriptor extends BaseVectorLayerDescriptor { } export function useLayerList() { - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { serviceName } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts index c9e8b41c26ea05..0c1b7b6b9e7ee9 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts @@ -7,7 +7,7 @@ import { useMemo } from 'react'; import { FieldFilter as Filter } from '@kbn/es-query'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { CLIENT_GEO_COUNTRY_ISO_CODE, SERVICE_NAME, @@ -92,7 +92,7 @@ const existFilter: Filter = { }; export const useMapFilters = (): Filter[] => { - const { urlParams, uxUiFilters } = useUrlParams(); + const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { serviceName, searchTerm } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts index 28c9488d7c82ce..8045e4947dcb0b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts @@ -17,7 +17,7 @@ import { toQuery, } from '../../../../components/shared/Links/url_helpers'; import { removeUndefinedProps } from '../../../../context/url_params_context/helpers'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getExcludedName } from '../LocalUIFilters'; export type FiltersUIHook = ReturnType; @@ -28,7 +28,7 @@ export function useLocalUIFilters({ filterNames: UxLocalUIFilterName[]; }) { const history = useHistory(); - const { uxUiFilters } = useUrlParams(); + const { uxUiFilters } = useLegacyUrlParams(); const setFilterValue = (name: UxLocalUIFilterName, value: string[]) => { const search = omit(toQuery(history.location.search), name); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts index 585070d479fa82..e3f697e02a2952 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts @@ -6,10 +6,10 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useUxQuery() { - const { urlParams, uxUiFilters } = useUrlParams(); + const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, searchTerm, percentile } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx index 72273bf8c9e19d..4e3f7f4bee8113 100644 --- a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { getNodeName, NodeType } from '../../../../common/connections'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../hooks/use_fetcher'; import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison'; import { DependenciesTable } from '../../shared/dependencies_table'; @@ -19,7 +19,7 @@ import { useTimeRange } from '../../../hooks/use_time_range'; export function BackendDetailDependenciesTable() { const { urlParams: { comparisonEnabled, comparisonType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { backendName, rangeFrom, rangeTo, kuery, environment }, diff --git a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx index b84e8830aae5fc..9782afc8df3e84 100644 --- a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useUiTracker } from '../../../../../../observability/public'; import { getNodeName, NodeType } from '../../../../../common/connections'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { useTimeRange } from '../../../../hooks/use_time_range'; @@ -21,7 +21,7 @@ import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time export function BackendInventoryDependenciesTable() { const { urlParams: { comparisonEnabled, comparisonType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { rangeFrom, rangeTo, environment, kuery }, diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx index 101923a7678fb7..040190cf44ad62 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx @@ -45,40 +45,40 @@ export function Example() { const distribution = { bucketSize: 62350, currentPeriod: [ - { key: 1624279912350, count: 6 }, - { key: 1624279974700, count: 1 }, - { key: 1624280037050, count: 2 }, - { key: 1624280099400, count: 3 }, - { key: 1624280161750, count: 13 }, - { key: 1624280224100, count: 1 }, - { key: 1624280286450, count: 2 }, - { key: 1624280348800, count: 0 }, - { key: 1624280411150, count: 4 }, - { key: 1624280473500, count: 4 }, - { key: 1624280535850, count: 1 }, - { key: 1624280598200, count: 4 }, - { key: 1624280660550, count: 0 }, - { key: 1624280722900, count: 2 }, - { key: 1624280785250, count: 3 }, - { key: 1624280847600, count: 0 }, + { x: 1624279912350, y: 6 }, + { x: 1624279974700, y: 1 }, + { x: 1624280037050, y: 2 }, + { x: 1624280099400, y: 3 }, + { x: 1624280161750, y: 13 }, + { x: 1624280224100, y: 1 }, + { x: 1624280286450, y: 2 }, + { x: 1624280348800, y: 0 }, + { x: 1624280411150, y: 4 }, + { x: 1624280473500, y: 4 }, + { x: 1624280535850, y: 1 }, + { x: 1624280598200, y: 4 }, + { x: 1624280660550, y: 0 }, + { x: 1624280722900, y: 2 }, + { x: 1624280785250, y: 3 }, + { x: 1624280847600, y: 0 }, ], previousPeriod: [ - { key: 1624279912350, count: 6 }, - { key: 1624279974700, count: 1 }, - { key: 1624280037050, count: 2 }, - { key: 1624280099400, count: 3 }, - { key: 1624280161750, count: 13 }, - { key: 1624280224100, count: 1 }, - { key: 1624280286450, count: 2 }, - { key: 1624280348800, count: 0 }, - { key: 1624280411150, count: 4 }, - { key: 1624280473500, count: 4 }, - { key: 1624280535850, count: 1 }, - { key: 1624280598200, count: 4 }, - { key: 1624280660550, count: 0 }, - { key: 1624280722900, count: 2 }, - { key: 1624280785250, count: 3 }, - { key: 1624280847600, count: 0 }, + { x: 1624279912350, y: 6 }, + { x: 1624279974700, y: 1 }, + { x: 1624280037050, y: 2 }, + { x: 1624280099400, y: 3 }, + { x: 1624280161750, y: 13 }, + { x: 1624280224100, y: 1 }, + { x: 1624280286450, y: 2 }, + { x: 1624280348800, y: 0 }, + { x: 1624280411150, y: 4 }, + { x: 1624280473500, y: 4 }, + { x: 1624280535850, y: 1 }, + { x: 1624280598200, y: 4 }, + { x: 1624280660550, y: 0 }, + { x: 1624280722900, y: 2 }, + { x: 1624280785250, y: 3 }, + { x: 1624280847600, y: 0 }, ], }; diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx index 6a3157b3c4b7fb..4c6aa78278093f 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx @@ -22,7 +22,6 @@ import { ALERT_RULE_TYPE_ID as ALERT_RULE_TYPE_ID_NON_TYPED } from '@kbn/rule-da import { i18n } from '@kbn/i18n'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { offsetPreviousPeriodCoordinates } from '../../../../../common/utils/offset_previous_period_coordinate'; import { useTheme } from '../../../../hooks/use_theme'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { AlertType } from '../../../../../common/alert_types'; @@ -30,8 +29,7 @@ import { getAlertAnnotations } from '../../../shared/charts/helper/get_alert_ann import { ChartContainer } from '../../../shared/charts/chart_container'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { LazyAlertsFlyout } from '../../../../../../observability/public'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { Coordinate } from '../../../../../typings/timeseries'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getTimeZone } from '../../../shared/charts/helper/timezone'; const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED = @@ -40,18 +38,6 @@ const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED = type ErrorDistributionAPIResponse = APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>; -export function getCoordinatedBuckets( - buckets: - | ErrorDistributionAPIResponse['currentPeriod'] - | ErrorDistributionAPIResponse['previousPeriod'] -): Coordinate[] { - return buckets.map(({ count, key }) => { - return { - x: key, - y: count, - }; - }); -} interface Props { fetchStatus: FETCH_STATUS; distribution: ErrorDistributionAPIResponse; @@ -61,15 +47,13 @@ interface Props { export function ErrorDistribution({ distribution, title, fetchStatus }: Props) { const { core } = useApmPluginContext(); const theme = useTheme(); - const currentPeriod = getCoordinatedBuckets(distribution.currentPeriod); - const previousPeriod = getCoordinatedBuckets(distribution.previousPeriod); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { comparisonEnabled } = urlParams; const timeseries = [ { - data: currentPeriod, + data: distribution.currentPeriod, color: theme.eui.euiColorVis1, title: i18n.translate('xpack.apm.errorGroup.chart.ocurrences', { defaultMessage: 'Occurences', @@ -78,10 +62,7 @@ export function ErrorDistribution({ distribution, title, fetchStatus }: Props) { ...(comparisonEnabled ? [ { - data: offsetPreviousPeriodCoordinates({ - currentPeriodTimeseries: currentPeriod, - previousPeriodTimeseries: previousPeriod, - }), + data: distribution.previousPeriod, color: theme.eui.euiColorMediumShade, title: i18n.translate( 'xpack.apm.errorGroup.chart.ocurrences.previousPeriodLabel', diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx index bc12b0c64f1792..f3bd8812dfd36c 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx @@ -20,7 +20,7 @@ import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common' import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useApmRouter } from '../../../hooks/use_apm_router'; import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; @@ -94,7 +94,7 @@ function ErrorGroupHeader({ } export function ErrorGroupDetails() { - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { serviceName } = useApmServiceContext(); diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx index d7c5b1f4bc3580..7facc9a4ca3765 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx @@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { truncate, unit } from '../../../../utils/style'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; @@ -56,7 +56,7 @@ interface Props { } function ErrorGroupList({ items, serviceName }: Props) { - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const columns = useMemo(() => { return [ diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx index 5e9095def6e558..9e113b37a1394d 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx @@ -7,7 +7,6 @@ import { EuiFlexGroup, - EuiFlexGrid, EuiFlexItem, EuiPanel, EuiSpacer, @@ -73,28 +72,30 @@ export function ErrorGroupOverview() { return ( - - - - - + + + + + + + + + - - - - - - - + + + + diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index aea7c1faab6011..155de8fbdd9473 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -17,7 +17,7 @@ import uuid from 'uuid'; import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { useApmParams } from '../../../hooks/use_apm_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; @@ -42,7 +42,7 @@ let hasDisplayedToast = false; function useServicesFetcher() { const { urlParams: { comparisonEnabled, comparisonType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { rangeFrom, rangeTo, environment, kuery }, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx new file mode 100644 index 00000000000000..0a4adc07e1a980 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx @@ -0,0 +1,81 @@ +/* + * 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 { Meta, Story } from '@storybook/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { CoreStart } from '../../../../../../../src/core/public'; +import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; +import { TimeRangeComparisonEnum } from '../../../../common/runtime_types/comparison_type_rt'; +import { AnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { ServiceInventory } from './'; + +const stories: Meta<{}> = { + title: 'app/ServiceInventory', + component: ServiceInventory, + decorators: [ + (StoryComponent) => { + const coreMock = { + http: { + get: (endpoint: string) => { + switch (endpoint) { + case '/internal/apm/services': + return { items: [] }; + default: + return {}; + } + return {}; + }, + }, + notifications: { toasts: { add: () => {}, addWarning: () => {} } }, + uiSettings: { get: () => [] }, + } as unknown as CoreStart; + + const KibanaReactContext = createKibanaReactContext(coreMock); + + const anomlyDetectionJobsContextValue = { + anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false }, + anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS, + anomalyDetectionJobsRefetch: () => {}, + }; + + return ( + + + + + + + + + + + + ); + }, + ], +}; +export default stories; + +export const Example: Story<{}> = () => { + return ; +}; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index 4a020f9b0db4ef..36b1053248d252 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -5,249 +5,17 @@ * 2.0. */ -import { render, waitFor } from '@testing-library/react'; -import { CoreStart } from 'kibana/public'; -import { merge } from 'lodash'; -import React, { ReactNode } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; -import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { ServiceHealthStatus } from '../../../../common/service_health_status'; -import { TimeRangeComparisonEnum } from '../../../../common/runtime_types/comparison_type_rt'; -import { ServiceInventory } from '.'; -import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; -import { - mockApmPluginContextValue, - MockApmPluginContextWrapper, -} from '../../../context/apm_plugin/mock_apm_plugin_context'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import { clearCache } from '../../../services/rest/callApi'; -import * as useDynamicDataViewHooks from '../../../hooks/use_dynamic_data_view'; -import { SessionStorageMock } from '../../../services/__mocks__/SessionStorageMock'; -import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; -import * as hook from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context'; +import { composeStories } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import * as stories from './service_inventory.stories'; -const KibanaReactContext = createKibanaReactContext({ - usageCollection: { reportUiCounter: () => {} }, -} as Partial); - -const addWarning = jest.fn(); -const httpGet = jest.fn(); - -function wrapper({ children }: { children?: ReactNode }) { - const mockPluginContext = merge({}, mockApmPluginContextValue, { - core: { - http: { - get: httpGet, - }, - notifications: { - toasts: { - addWarning, - }, - }, - }, - }) as unknown as ApmPluginContextValue; - - return ( - - - - - - {children} - - - - - - ); -} +const { Example } = composeStories(stories); describe('ServiceInventory', () => { - beforeEach(() => { - // @ts-expect-error - global.sessionStorage = new SessionStorageMock(); - clearCache(); - - jest.spyOn(hook, 'useAnomalyDetectionJobsContext').mockReturnValue({ - anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false }, - anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS, - anomalyDetectionJobsRefetch: () => {}, - }); - - jest - .spyOn(useDynamicDataViewHooks, 'useDynamicDataViewFetcher') - .mockReturnValue({ - dataView: undefined, - status: FETCH_STATUS.SUCCESS, - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should render services, when list is not empty', async () => { - // mock rest requests - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [ - { - serviceName: 'My Python Service', - agentName: 'python', - transactionsPerMinute: 100, - errorsPerMinute: 200, - avgResponseTime: 300, - environments: ['test', 'dev'], - healthStatus: ServiceHealthStatus.warning, - }, - { - serviceName: 'My Go Service', - agentName: 'go', - transactionsPerMinute: 400, - errorsPerMinute: 500, - avgResponseTime: 600, - environments: [], - severity: ServiceHealthStatus.healthy, - }, - ], - }); - - const { container, findByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - await findByText('My Python Service'); - - expect(container.querySelectorAll('.euiTableRow')).toHaveLength(2); - }); - - it('should render empty message, when list is empty and historical data is found', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [], - }); - - const { findByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - const noServicesText = await findByText('No services found'); - - expect(noServicesText).not.toBeEmptyDOMElement(); - }); - - describe('when legacy data is found', () => { - it('renders an upgrade migration notification', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: true, - hasHistoricalData: true, - items: [], - }); - - render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - - expect(addWarning).toHaveBeenLastCalledWith( - expect.objectContaining({ - title: 'Legacy data was detected within the selected time range', - }) - ); - }); - }); - - describe('when legacy data is not found', () => { - it('does not render an upgrade migration notification', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [], - }); - - render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - - expect(addWarning).not.toHaveBeenCalled(); - }); - }); - - describe('when ML data is not found', () => { - it('does not render the health column', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [ - { - serviceName: 'My Python Service', - agentName: 'python', - transactionsPerMinute: 100, - errorsPerMinute: 200, - avgResponseTime: 300, - environments: ['test', 'dev'], - }, - ], - }); - - const { queryByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); - - expect(queryByText('Health')).toBeNull(); - }); - }); - - describe('when ML data is found', () => { - it('renders the health column', async () => { - httpGet - .mockResolvedValueOnce({ fallbackToTransactions: false }) - .mockResolvedValueOnce({ - hasLegacyData: false, - hasHistoricalData: true, - items: [ - { - serviceName: 'My Python Service', - agentName: 'python', - transactionsPerMinute: 100, - errorsPerMinute: 200, - avgResponseTime: 300, - environments: ['test', 'dev'], - healthStatus: ServiceHealthStatus.warning, - }, - ], - }); - - const { queryAllByText } = render(, { wrapper }); - - // wait for requests to be made - await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2)); + it('renders', async () => { + render(); - expect(queryAllByText('Health').length).toBeGreaterThan(1); - }); + expect(await screen.findByRole('table')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/apm/public/components/app/service_map/Controls.tsx b/x-pack/plugins/apm/public/components/app/service_map/Controls.tsx index 69644216fc2674..4605952a6f3960 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Controls.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Controls.tsx @@ -11,8 +11,8 @@ import React, { useContext, useEffect, useState } from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useTheme } from '../../../hooks/use_theme'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getLegacyApmHref } from '../../shared/Links/apm/APMLink'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../../shared/Links/url_helpers'; import { CytoscapeContext } from './Cytoscape'; import { getAnimationOptions, getNodeHeight } from './cytoscape_options'; @@ -103,7 +103,7 @@ export function Controls() { const { basePath } = core.http; const theme = useTheme(); const cy = useContext(CytoscapeContext); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { query: { kuery }, diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx index 75d5837f738c5c..0ec1e6630003a3 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx @@ -168,8 +168,8 @@ export function ServiceMap({ status === FETCH_STATUS.FAILURE && error && 'body' in error && - error.body.statusCode === 500 && - error.body.message === SERVICE_MAP_TIMEOUT_ERROR + error.body?.statusCode === 500 && + error.body?.message === SERVICE_MAP_TIMEOUT_ERROR ) { return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx new file mode 100644 index 00000000000000..b632d3a33dea86 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx @@ -0,0 +1,76 @@ +/* + * 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 { Meta, Story } from '@storybook/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import type { CoreStart } from '../../../../../../../src/core/public'; +import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; +import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { + APMServiceContext, + APMServiceContextValue, +} from '../../../context/apm_service/apm_service_context'; +import { ServiceOverview } from './'; + +const stories: Meta<{}> = { + title: 'app/ServiceOverview', + component: ServiceOverview, + decorators: [ + (StoryComponent) => { + const serviceName = 'testServiceName'; + const mockCore = { + http: { + basePath: { prepend: () => {} }, + get: (endpoint: string) => { + switch (endpoint) { + case `/api/apm/services/${serviceName}/annotation/search`: + return { annotations: [] }; + case '/internal/apm/fallback_to_transactions': + return { fallbackToTransactions: false }; + case `/internal/apm/services/${serviceName}/dependencies`: + return { serviceDependencies: [] }; + default: + return {}; + } + }, + }, + notifications: { toasts: { add: () => {} } }, + uiSettings: { get: () => 'Browser' }, + } as unknown as CoreStart; + const serviceContextValue = { + alerts: [], + serviceName, + } as unknown as APMServiceContextValue; + const KibanaReactContext = createKibanaReactContext(mockCore); + + return ( + + + + + + + + + + ); + }, + ], +}; +export default stories; + +export const Example: Story<{}> = () => { + return ; +}; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index aae6dddbec718c..fb60604aa53b2e 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -5,180 +5,19 @@ * 2.0. */ -import React, { ReactNode } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { CoreStart } from 'src/core/public'; -import { isEqual } from 'lodash'; -import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; -import { - mockApmPluginContextValue, - MockApmPluginContextWrapper, -} from '../../../context/apm_plugin/mock_apm_plugin_context'; -import * as useDynamicDataViewHooks from '../../../hooks/use_dynamic_data_view'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import * as useAnnotationsHooks from '../../../context/annotations/use_annotations_context'; -import * as useTransactionBreakdownHooks from '../../shared/charts/transaction_breakdown_chart/use_transaction_breakdown'; -import { renderWithTheme } from '../../../utils/testHelpers'; -import { ServiceOverview } from './'; -import { waitFor } from '@testing-library/dom'; -import * as useApmServiceContextHooks from '../../../context/apm_service/use_apm_service_context'; -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { - getCallApmApiSpy, - getCreateCallApmApiSpy, -} from '../../../services/rest/callApmApiSpy'; -import { fromQuery } from '../../shared/Links/url_helpers'; -import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; -import { uiSettingsServiceMock } from '../../../../../../../src/core/public/mocks'; +import { composeStories } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import * as stories from './service_overview.stories'; -const uiSettings = uiSettingsServiceMock.create().setup({} as any); - -const KibanaReactContext = createKibanaReactContext({ - notifications: { toasts: { add: () => {} } }, - uiSettings, - usageCollection: { reportUiCounter: () => {} }, -} as unknown as Partial); - -const mockParams = { - rangeFrom: 'now-15m', - rangeTo: 'now', - latencyAggregationType: LatencyAggregationType.avg, -}; - -const location = { - pathname: '/services/test%20service%20name/overview', - search: fromQuery(mockParams), -}; - -function Wrapper({ children }: { children?: ReactNode }) { - const value = { - ...mockApmPluginContextValue, - core: { - ...mockApmPluginContextValue.core, - http: { - basePath: { prepend: () => {} }, - get: () => {}, - }, - }, - } as unknown as ApmPluginContextValue; - - return ( - - - - - {children} - - - - - ); -} +const { Example } = composeStories(stories); describe('ServiceOverview', () => { it('renders', async () => { - jest - .spyOn(useApmServiceContextHooks, 'useApmServiceContext') - .mockReturnValue({ - serviceName: 'test service name', - agentName: 'java', - transactionType: 'request', - transactionTypes: ['request'], - alerts: [], - }); - jest - .spyOn(useAnnotationsHooks, 'useAnnotationsContext') - .mockReturnValue({ annotations: [] }); - jest - .spyOn(useDynamicDataViewHooks, 'useDynamicDataViewFetcher') - .mockReturnValue({ - dataView: undefined, - status: FETCH_STATUS.SUCCESS, - }); - - /* eslint-disable @typescript-eslint/naming-convention */ - const calls = { - 'GET /internal/apm/services/{serviceName}/error_groups/main_statistics': { - error_groups: [] as any[], - }, - 'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics': - { - transactionGroups: [] as any[], - totalTransactionGroups: 0, - isAggregationAccurate: true, - }, - 'GET /internal/apm/services/{serviceName}/dependencies': { - serviceDependencies: [], - }, - 'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics': - [], - 'GET /internal/apm/services/{serviceName}/transactions/charts/latency': { - currentPeriod: { - overallAvgDuration: null, - latencyTimeseries: [], - }, - previousPeriod: { - overallAvgDuration: null, - latencyTimeseries: [], - }, - }, - 'GET /internal/apm/services/{serviceName}/throughput': { - currentPeriod: [], - previousPeriod: [], - }, - 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate': - { - currentPeriod: { - transactionErrorRate: [], - noHits: true, - average: null, - }, - previousPeriod: { - transactionErrorRate: [], - noHits: true, - average: null, - }, - }, - 'GET /api/apm/services/{serviceName}/annotation/search': { - annotations: [], - }, - 'GET /internal/apm/fallback_to_transactions': { - fallbackToTransactions: false, - }, - }; - /* eslint-enable @typescript-eslint/naming-convention */ - - const callApmApiSpy = getCallApmApiSpy().mockImplementation( - ({ endpoint }) => { - const response = calls[endpoint as keyof typeof calls]; - - return response - ? Promise.resolve(response) - : Promise.reject(`Response for ${endpoint} is not defined`); - } - ); - - getCreateCallApmApiSpy().mockImplementation(() => callApmApiSpy as any); - jest - .spyOn(useTransactionBreakdownHooks, 'useTransactionBreakdown') - .mockReturnValue({ - data: { timeseries: [] }, - error: undefined, - status: FETCH_STATUS.SUCCESS, - }); - - const { findAllByText } = renderWithTheme(, { - wrapper: Wrapper, - }); - - await waitFor(() => { - const endpoints = callApmApiSpy.mock.calls.map( - (call) => call[0].endpoint - ); - return isEqual(endpoints.sort(), Object.keys(calls).sort()); - }); + render(); - expect((await findAllByText('Latency')).length).toBeGreaterThan(0); + expect( + await screen.findByRole('heading', { name: /Latency/ }) + ).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index 208d1a30a46d11..cbf60b7b59e4d8 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -11,7 +11,7 @@ import React, { ReactNode } from 'react'; import { useUiTracker } from '../../../../../../observability/public'; import { getNodeName, NodeType } from '../../../../../common/connections'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { useTimeRange } from '../../../../hooks/use_time_range'; @@ -33,7 +33,7 @@ export function ServiceOverviewDependenciesTable({ }: ServiceOverviewDependenciesTableProps) { const { urlParams: { comparisonEnabled, comparisonType, latencyAggregationType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { environment, kuery, rangeFrom, rangeTo }, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index d7116763780398..ce4ba85b233e2a 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -16,7 +16,7 @@ import { orderBy } from 'lodash'; import React, { useState } from 'react'; import uuid from 'uuid'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { ErrorOverviewLink } from '../../../shared/Links/apm/ErrorOverviewLink'; @@ -61,7 +61,7 @@ const INITIAL_STATE_DETAILED_STATISTICS: ErrorGroupDetailedStatistics = { export function ServiceOverviewErrorsTable({ serviceName }: Props) { const { urlParams: { comparisonType, comparisonEnabled }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { transactionType } = useApmServiceContext(); const [tableOptions, setTableOptions] = useState<{ pageIndex: number; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index 74a9a60afd9157..07e4e81e4d2161 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -10,7 +10,7 @@ import { orderBy } from 'lodash'; import React, { useState } from 'react'; import uuid from 'uuid'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; @@ -78,7 +78,7 @@ export function ServiceOverviewInstancesChartAndTable({ const { urlParams: { latencyAggregationType, comparisonType, comparisonEnabled }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx index 9084ffdda59f85..71ea1412c64265 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx @@ -14,7 +14,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { ReactNode, useEffect, useState } from 'react'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { @@ -76,7 +76,7 @@ export function ServiceOverviewInstancesTable({ const { urlParams: { latencyAggregationType, comparisonEnabled }, - } = useUrlParams(); + } = useLegacyUrlParams(); const [itemIdToOpenActionMenuRowMap, setItemIdToOpenActionMenuRowMap] = useState>({}); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index 6648eaec80fa6a..058c7d97b43ccc 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { asExactTransactionRate } from '../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useTheme } from '../../../hooks/use_theme'; @@ -48,7 +48,7 @@ export function ServiceOverviewThroughputChart({ const { urlParams: { comparisonEnabled, comparisonType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { rangeFrom, rangeTo }, diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index 8862596fd6d2ac..ad52adfa13a529 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -25,7 +25,7 @@ import { useUiTracker } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/search_strategies/constants'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart'; @@ -69,7 +69,7 @@ export function TransactionDistribution({ traceSamples, }: TransactionDistributionProps) { const transactionColors = useTransactionColors(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { waterfall, status: waterfallStatus } = useWaterfallFetcher(); const markerCurrentTransaction = diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts index 0edf5f648a980d..9fb945100414fa 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts @@ -16,7 +16,7 @@ import { EventOutcome } from '../../../../../common/event_outcome'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTimeRange } from '../../../../hooks/use_time_range'; @@ -37,7 +37,7 @@ export const useTransactionDistributionChartData = () => { core: { notifications }, } = useApmPluginContext(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { transactionName } = urlParams; const { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx index 9ccca9886e6799..f379369e86643c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx @@ -13,7 +13,7 @@ import { useHistory } from 'react-router-dom'; import { XYBrushEvent } from '@elastic/charts'; import { EuiPanel, EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useTransactionTraceSamplesFetcher } from '../../../hooks/use_transaction_trace_samples_fetcher'; @@ -34,7 +34,7 @@ const tabs = [ export function TransactionDetailsTabs() { const { query } = useApmParams('/services/{serviceName}/transactions/view'); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const history = useHistory(); const [currentTab, setCurrentTab] = useState(traceSamplesTab.key); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts b/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts index e7fbc315522e46..4f26a5875347ca 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts @@ -6,7 +6,7 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; @@ -19,7 +19,7 @@ const INITIAL_DATA = { }; export function useWaterfallFetcher() { - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { traceId, transactionId } = urlParams; const { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/MaybeViewTraceLink.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/MaybeViewTraceLink.tsx index 359dcdfda0a146..c18bc42a98b2c3 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/MaybeViewTraceLink.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/MaybeViewTraceLink.tsx @@ -9,7 +9,7 @@ import { EuiButton, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { getNextEnvironmentUrlParam } from '../../../../../common/environment_filter_values'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { Transaction as ITransaction } from '../../../../../typings/es_schemas/ui/transaction'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; import { IWaterfall } from './waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers'; @@ -26,7 +26,7 @@ export function MaybeViewTraceLink({ }) { const { urlParams: { latencyAggregationType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const viewFullTraceButtonLabel = i18n.translate( 'xpack.apm.transactionDetails.viewFullTraceButtonLabel', diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/FlyoutTopLevelProperties.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/FlyoutTopLevelProperties.tsx index 8954081f9ab473..e34fdd893ff7f8 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/FlyoutTopLevelProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/FlyoutTopLevelProperties.tsx @@ -13,7 +13,7 @@ import { } from '../../../../../../../common/elasticsearch_fieldnames'; import { getNextEnvironmentUrlParam } from '../../../../../../../common/environment_filter_values'; import { Transaction } from '../../../../../../../typings/es_schemas/ui/transaction'; -import { useUrlParams } from '../../../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../../../../hooks/use_apm_params'; import { TransactionDetailLink } from '../../../../../shared/Links/apm/transaction_detail_link'; import { ServiceLink } from '../../../../../shared/service_link'; @@ -26,7 +26,7 @@ interface Props { export function FlyoutTopLevelProperties({ transaction }: Props) { const { urlParams: { latencyAggregationType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query } = useApmParams('/services/{serviceName}/transactions/view'); if (!transaction) { diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts index 2b88e1a1df9ed4..ec9f7409323760 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts @@ -33,7 +33,7 @@ export function getApmSettings({ 'xpack.apm.fleet_integration.settings.apm.hostDescription', { defaultMessage: - 'Choose a name and description to help identify how this integration will be used.', + 'Host defines the host and port the server is listening on. URL is the unchangeable, publicly reachable server URL for deployments on Elastic Cloud or ECK.', } ), @@ -164,10 +164,7 @@ export function getApmSettings({ ), rowDescription: i18n.translate( 'xpack.apm.fleet_integration.settings.apm.responseHeadersDescription', - { - defaultMessage: - 'Set limits on request headers sizes and timing configurations.', - } + { defaultMessage: 'Custom HTTP headers added to HTTP responses' } ), }, { diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/typings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/typings.ts index 7df1ccf1fb18fa..4f741ceb46f491 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/typings.ts +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/typings.ts @@ -7,12 +7,12 @@ import * as t from 'io-ts'; import { PackagePolicyConfigRecordEntry } from '../../../../../fleet/common'; -export { +export type { PackagePolicyCreateExtensionComponentProps, PackagePolicyEditExtensionComponentProps, } from '../../../../../fleet/public'; -export { +export type { NewPackagePolicy, PackagePolicy, PackagePolicyConfigRecordEntry, diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx index 14bf7de789c98f..7ba0ca625d74c8 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx @@ -42,11 +42,10 @@ export function ApmMainTemplate({ const location = useLocation(); const { services } = useKibana(); - const { http, docLinks } = services; + const { http, docLinks, observability } = services; const basePath = http?.basePath.get(); - const ObservabilityPageTemplate = - services.observability.navigation.PageTemplate; + const ObservabilityPageTemplate = observability.navigation.PageTemplate; const { data } = useFetcher((callApmApi) => { return callApmApi({ endpoint: 'GET /internal/apm/has_data' }); diff --git a/x-pack/plugins/apm/public/components/routing/templates/no_data_config.ts b/x-pack/plugins/apm/public/components/routing/templates/no_data_config.ts index 92b2b1bc3d2298..b376ca3aec2c5f 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/no_data_config.ts +++ b/x-pack/plugins/apm/public/components/routing/templates/no_data_config.ts @@ -35,7 +35,7 @@ export function getNoDataConfig({ 'Use APM agents to collect APM data. We make it easy with agents for many popular languages.', } ), - href: basePath + `/app/integrations/detail/apm`, + href: `${basePath}/app/home#/tutorial/apm`, }, }, docsLink, diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx index 61db277f90d7f6..44e33e6bf419d7 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx @@ -10,8 +10,7 @@ import React from 'react'; import { getRenderedHref } from '../../../../utils/testHelpers'; import { MLExplorerLink } from './MLExplorerLink'; -// FLAKY: https://github.com/elastic/kibana/issues/113695 -describe.skip('MLExplorerLink', () => { +describe('MLExplorerLink', () => { it('should produce the correct URL with jobId', async () => { const href = await getRenderedHref( () => ( diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.tsx index 11b29f00155fcd..72a29a079bc679 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.tsx @@ -10,7 +10,7 @@ import { EuiLink } from '@elastic/eui'; import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref, ML_PAGES } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { TimePickerRefreshInterval } from '../../DatePicker/typings'; interface Props { @@ -37,7 +37,7 @@ export function useExplorerHref({ jobId }: { jobId: string }) { core, plugins: { ml }, } = useApmPluginContext(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const timePickerRefreshIntervalDefaults = core.uiSettings.get( diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx index 5c8d4642040904..eb7b5311217538 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref, ML_PAGES } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { TimePickerRefreshInterval } from '../../DatePicker/typings'; interface Props { @@ -24,7 +24,7 @@ export function MLManageJobsLink({ children, external }: Props) { plugins: { ml }, } = useApmPluginContext(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const timePickerRefreshIntervalDefaults = core.uiSettings.get( diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLSingleMetricLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLSingleMetricLink.tsx index a36beccd16be5c..2964a8e2578c70 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLSingleMetricLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLSingleMetricLink.tsx @@ -10,7 +10,7 @@ import { EuiLink } from '@elastic/eui'; import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref, ML_PAGES } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { TimePickerRefreshInterval } from '../../DatePicker/typings'; interface Props { @@ -53,7 +53,7 @@ export function useSingleMetricHref({ core, plugins: { ml }, } = useApmPluginContext(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const timePickerRefreshIntervalDefaults = core.uiSettings.get( diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx index fbf4dff24fbf49..bcb305bd38ec34 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx @@ -13,7 +13,7 @@ import { useLocation } from 'react-router-dom'; import url from 'url'; import { pickKeys } from '../../../../../common/utils/pick_keys'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams, fromQuery, toQuery } from '../url_helpers'; interface Props extends EuiLinkAnchorProps { @@ -46,7 +46,7 @@ export function useAPMHref({ persistedFilters?: Array; query?: APMQueryParams; }) { - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { basePath } = useApmPluginContext().core.http; const { search } = useLocation(); const nextQuery = { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx index b06de47472a110..ccd9bdff960b60 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps } from './APMLink'; @@ -24,7 +24,7 @@ interface Props extends APMLinkExtendProps { } export function ErrorOverviewLink({ serviceName, query, ...rest }: Props) { - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); return ( showPopover(!isPopoverOpen); diff --git a/x-pack/plugins/apm/public/components/shared/charts/failed_transaction_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/failed_transaction_rate_chart/index.tsx index cf57f618940b4b..a5952a363a9e06 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/failed_transaction_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/failed_transaction_rate_chart/index.tsx @@ -14,7 +14,7 @@ import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { asPercent } from '../../../../../common/utils/formatters'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { TimeseriesChart } from '../timeseries_chart'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { @@ -40,13 +40,11 @@ type ErrorRate = const INITIAL_STATE: ErrorRate = { currentPeriod: { - noHits: true, - transactionErrorRate: [], + timeseries: [], average: null, }, previousPeriod: { - noHits: true, - transactionErrorRate: [], + timeseries: [], average: null, }, }; @@ -60,7 +58,7 @@ export function FailedTransactionRateChart({ const theme = useTheme(); const { urlParams: { transactionName, comparisonEnabled, comparisonType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { rangeFrom, rangeTo }, @@ -116,7 +114,7 @@ export function FailedTransactionRateChart({ const timeseries = [ { - data: data.currentPeriod.transactionErrorRate, + data: data.currentPeriod.timeseries, type: 'linemark', color: theme.eui.euiColorVis7, title: i18n.translate('xpack.apm.errorRate.chart.errorRate', { @@ -126,7 +124,7 @@ export function FailedTransactionRateChart({ ...(comparisonEnabled ? [ { - data: data.previousPeriod.transactionErrorRate, + data: data.previousPeriod.timeseries, type: 'area', color: theme.eui.euiColorMediumShade, title: i18n.translate( diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx index 3cee9c22174adc..e19f78006d9fea 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx @@ -17,7 +17,7 @@ import { useApmServiceContext } from '../../../../context/apm_service/use_apm_se import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { useLicenseContext } from '../../../../context/license/use_license_context'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useTheme } from '../../../../hooks/use_theme'; import { useTransactionLatencyChartsFetcher } from '../../../../hooks/use_transaction_latency_chart_fetcher'; import { TimeseriesChart } from '../../../shared/charts/timeseries_chart'; @@ -52,7 +52,7 @@ export function LatencyChart({ height, kuery, environment }: Props) { const history = useHistory(); const theme = useTheme(); const comparisonChartTheme = getComparisonChartTheme(theme); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { latencyAggregationType, comparisonEnabled } = urlParams; const license = useLicenseContext(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index 079b8455de7ad2..a32f7ededea400 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -6,7 +6,7 @@ */ import { useFetcher } from '../../../../hooks/use_fetcher'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useTimeRange } from '../../../../hooks/use_time_range'; @@ -20,7 +20,7 @@ export function useTransactionBreakdown({ }) { const { urlParams: { transactionName }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { rangeFrom, rangeTo }, diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx index 1a1ea13fa45ec0..dcf52cebaeedac 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx @@ -132,7 +132,7 @@ export function TransactionDistributionChart({ Math.max( ...flatten(data.map((d) => d.histogram)).map((d) => d.doc_count) ) ?? 0; - const yTicks = Math.ceil(Math.log10(yMax)); + const yTicks = Math.max(1, Math.ceil(Math.log10(yMax))); const yAxisDomain = { min: 0.5, max: Math.pow(10, yTicks), diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx index 4dc24567259e67..20484e691d50b8 100644 --- a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx @@ -16,7 +16,7 @@ import { QuerySuggestion, } from '../../../../../../../src/plugins/data/public'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useDynamicDataViewFetcher } from '../../../hooks/use_dynamic_data_view'; import { fromQuery, toQuery } from '../Links/url_helpers'; @@ -52,7 +52,7 @@ export function KueryBar(props: { suggestions: [], isLoadingSuggestions: false, }); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const location = useLocation(); const { data } = useApmPluginContext().plugins; diff --git a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx index f1776abe83e970..5d49f1d9a8f18e 100644 --- a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx @@ -10,7 +10,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { orderBy } from 'lodash'; import React, { ReactNode, useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; // TODO: this should really be imported from EUI @@ -79,7 +79,7 @@ function UnoptimizedManagedTable(props: Props) { sortField = initialSortField, sortDirection = initialSortDirection, }, - } = useUrlParams(); + } = useLegacyUrlParams(); const renderedItems = useMemo(() => { const sortedItems = sortItems diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx index 35a6bc76348136..9d077713ff12ec 100644 --- a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx @@ -13,7 +13,7 @@ import { useHistory } from 'react-router-dom'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useUiTracker } from '../../../../../observability/public'; import { TimeRangeComparisonEnum } from '../../../../common/runtime_types/comparison_type_rt'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; import { useTimeRange } from '../../../hooks/use_time_range'; @@ -127,7 +127,7 @@ export function TimeComparison() { const { urlParams: { comparisonEnabled, comparisonType }, - } = useUrlParams(); + } = useLegacyUrlParams(); const comparisonTypes = getComparisonTypes({ start: exactStart, diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.tsx index a4f7c2b6634846..2f856dce387bfd 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.tsx @@ -21,7 +21,7 @@ import { import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useLicenseContext } from '../../../context/license/use_license_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { CustomLinkMenuSection } from './custom_link_menu_section'; import { getSections } from './sections'; @@ -45,7 +45,7 @@ export function TransactionActionMenu({ transaction }: Props) { const { core } = useApmPluginContext(); const location = useLocation(); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const [isActionPopoverOpen, setIsActionPopoverOpen] = useState(false); diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index d6d955b8ef5abe..6c934cc51e2f7a 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -20,7 +20,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCode } from '@elastic/eui'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { TransactionOverviewLink } from '../Links/apm/transaction_overview_link'; import { getTimeRangeComparison } from '../time_comparison/get_time_range_comparison'; @@ -100,7 +100,7 @@ export function TransactionsTable({ const { transactionType, serviceName } = useApmServiceContext(); const { urlParams: { latencyAggregationType, comparisonType, comparisonEnabled }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { comparisonStart, comparisonEnd } = getTimeRangeComparison({ start, diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index 9d207eee2fbaa6..c99ef519f9e695 100644 --- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -23,14 +23,20 @@ export type APMServiceAlert = ValuesType< APIReturnType<'GET /internal/apm/services/{serviceName}/alerts'>['alerts'] >; -export const APMServiceContext = createContext<{ +export interface APMServiceContextValue { serviceName: string; agentName?: string; transactionType?: string; transactionTypes: string[]; alerts: APMServiceAlert[]; runtimeName?: string; -}>({ serviceName: '', transactionTypes: [], alerts: [] }); +} + +export const APMServiceContext = createContext({ + serviceName: '', + transactionTypes: [], + alerts: [], +}); export function ApmServiceContextProvider({ children, diff --git a/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx index 5e91bfd1549ed1..7c7a00da728ad4 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx @@ -10,7 +10,7 @@ import { useMemo, useContext } from 'react'; import type { ApmUrlParams } from './types'; import { UrlParamsContext } from './url_params_context'; -export function useUrlParams(): Assign< +export function useLegacyUrlParams(): Assign< React.ContextType, { urlParams: ApmUrlParams } > { diff --git a/x-pack/plugins/apm/public/hooks/use_comparison.ts b/x-pack/plugins/apm/public/hooks/use_comparison.ts index 2875d973a5e18a..dbd37c25784cf9 100644 --- a/x-pack/plugins/apm/public/hooks/use_comparison.ts +++ b/x-pack/plugins/apm/public/hooks/use_comparison.ts @@ -9,7 +9,7 @@ import { getComparisonChartTheme, getTimeRangeComparison, } from '../components/shared/time_comparison/get_time_range_comparison'; -import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { useApmParams } from './use_apm_params'; import { useTheme } from './use_theme'; import { useTimeRange } from './use_time_range'; @@ -31,7 +31,7 @@ export function useComparison() { const { urlParams: { comparisonType, comparisonEnabled }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { offset } = getTimeRangeComparison({ start, diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx index 72fb8ac0bb3cf5..b77f6f9cf0fbb1 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useMemo, useState } from 'react'; -import { IHttpFetchError } from 'src/core/public'; +import { IHttpFetchError, ResponseErrorBody } from 'src/core/public'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { useTimeRangeId } from '../context/time_range_id/use_time_range_id'; import { @@ -26,10 +26,12 @@ export enum FETCH_STATUS { export interface FetcherResult { data?: Data; status: FETCH_STATUS; - error?: IHttpFetchError; + error?: IHttpFetchError; } -function getDetailsFromErrorResponse(error: IHttpFetchError) { +function getDetailsFromErrorResponse( + error: IHttpFetchError +) { const message = error.body?.message ?? error.response?.statusText; return ( <> @@ -118,7 +120,7 @@ export function useFetcher( } as FetcherResult>); } } catch (e) { - const err = e as Error | IHttpFetchError; + const err = e as Error | IHttpFetchError; if (!signal.aborted) { const errorDetails = diff --git a/x-pack/plugins/apm/public/hooks/use_search_strategy.ts b/x-pack/plugins/apm/public/hooks/use_search_strategy.ts index 275eddb68ae003..95bc8cb7435a22 100644 --- a/x-pack/plugins/apm/public/hooks/use_search_strategy.ts +++ b/x-pack/plugins/apm/public/hooks/use_search_strategy.ts @@ -31,7 +31,7 @@ import { APM_SEARCH_STRATEGIES, } from '../../common/search_strategies/constants'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { ApmPluginStartDeps } from '../plugin'; @@ -103,7 +103,7 @@ export function useSearchStrategy< query: { kuery, environment, rangeFrom, rangeTo }, } = useApmParams('/services/{serviceName}/transactions/view'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { urlParams } = useUrlParams(); + const { urlParams } = useLegacyUrlParams(); const { transactionName } = urlParams; const [rawResponse, setRawResponse] = useReducer( diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index d44ec853a242cb..0ed3de14c129f8 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -7,7 +7,7 @@ import { useMemo } from 'react'; import { useFetcher } from './use_fetcher'; -import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; import { getLatencyChartSelector } from '../selectors/latency_chart_selectors'; import { useTheme } from './use_theme'; @@ -31,7 +31,7 @@ export function useTransactionLatencyChartsFetcher({ comparisonType, comparisonEnabled, }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { query: { rangeFrom, rangeTo }, diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts index 48c2d555fd43b0..93349d3f955b88 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts @@ -6,7 +6,7 @@ */ import { useFetcher } from './use_fetcher'; -import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; import { useApmParams } from './use_apm_params'; import { useTimeRange } from './use_time_range'; @@ -17,7 +17,6 @@ export interface TraceSample { } const INITIAL_DATA = { - noHits: true, traceSamples: [] as TraceSample[], }; @@ -40,7 +39,7 @@ export function useTransactionTraceSamplesFetcher({ const { urlParams: { transactionId, traceId, sampleRangeFrom, sampleRangeTo }, - } = useUrlParams(); + } = useLegacyUrlParams(); const { data = INITIAL_DATA, @@ -71,16 +70,7 @@ export function useTransactionTraceSamplesFetcher({ }, }); - if (response.noHits) { - return response; - } - - const { traceSamples } = response; - - return { - noHits: false, - traceSamples, - }; + return response; } }, // the samples should not be refetched if the transactionId or traceId changes diff --git a/x-pack/plugins/apm/public/index.ts b/x-pack/plugins/apm/public/index.ts index 2734269b9cff9e..9b2175dd465bcc 100644 --- a/x-pack/plugins/apm/public/index.ts +++ b/x-pack/plugins/apm/public/index.ts @@ -23,4 +23,4 @@ export const plugin: PluginInitializer = ( pluginInitializerContext: PluginInitializerContext ) => new ApmPlugin(pluginInitializerContext); -export { ApmPluginSetup, ApmPluginStart }; +export type { ApmPluginSetup, ApmPluginStart }; diff --git a/x-pack/plugins/apm/public/services/rest/callApi.ts b/x-pack/plugins/apm/public/services/rest/callApi.ts index 9b6d0c383e9a7d..196878a08821e3 100644 --- a/x-pack/plugins/apm/public/services/rest/callApi.ts +++ b/x-pack/plugins/apm/public/services/rest/callApi.ts @@ -66,7 +66,7 @@ export async function callApi( | 'delete' | 'patch'; - const res = await http[lowercaseMethod](pathname, options); + const res = await http[lowercaseMethod](pathname, options); if (isCachable(fetchOptions)) { cache.set(cacheKey, res); diff --git a/x-pack/plugins/apm/scripts/test/api.js b/x-pack/plugins/apm/scripts/test/api.js index 4f0d82d0c11630..1905c8eb7c2dda 100644 --- a/x-pack/plugins/apm/scripts/test/api.js +++ b/x-pack/plugins/apm/scripts/test/api.js @@ -33,9 +33,15 @@ const { argv } = yargs(process.argv.slice(2)) description: 'Run all tests (an instance of Elasticsearch and kibana are needs to be available)', }) + .option('grep', { + alias: 'spec', + default: false, + type: 'string', + description: 'Specify the spec files to run', + }) .help(); -const { trial, server, runner } = argv; +const { trial, server, runner, grep } = argv; const license = trial ? 'trial' : 'basic'; console.log(`License: ${license}`); @@ -46,7 +52,10 @@ if (server) { } else if (runner) { ftrScript = 'functional_test_runner'; } -childProcess.execSync( - `node ../../../../scripts/${ftrScript} --config ../../../../test/apm_api_integration/${license}/config.ts`, - { cwd: path.join(__dirname), stdio: 'inherit' } -); + +const grepArg = grep ? `--grep "${grep}"` : ''; +const cmd = `node ../../../../scripts/${ftrScript} ${grepArg} --config ../../../../test/apm_api_integration/${license}/config.ts`; + +console.log(`Running ${cmd}`); + +childProcess.execSync(cmd, { cwd: path.join(__dirname), stdio: 'inherit' }); diff --git a/x-pack/plugins/apm/scripts/test/e2e.js b/x-pack/plugins/apm/scripts/test/e2e.js index b3ce510a8e569b..13055dce2fec56 100644 --- a/x-pack/plugins/apm/scripts/test/e2e.js +++ b/x-pack/plugins/apm/scripts/test/e2e.js @@ -20,7 +20,7 @@ const { argv } = yargs(process.argv.slice(2)) .option('server', { default: false, type: 'boolean', - description: 'Start Elasticsearch and kibana', + description: 'Start Elasticsearch and Kibana', }) .option('runner', { default: false, @@ -28,14 +28,26 @@ const { argv } = yargs(process.argv.slice(2)) description: 'Run all tests (an instance of Elasticsearch and kibana are needs to be available)', }) + .option('grep', { + alias: 'spec', + default: false, + type: 'string', + description: + 'Specify the spec files to run (use doublequotes for glob matching)', + }) .option('open', { default: false, type: 'boolean', description: 'Opens the Cypress Test Runner', }) + .option('bail', { + default: false, + type: 'boolean', + description: 'stop tests after the first failure', + }) .help(); -const { server, runner, open, kibanaInstallDir } = argv; +const { server, runner, open, grep, bail, kibanaInstallDir } = argv; const e2eDir = path.join(__dirname, '../../ftr_e2e'); @@ -46,9 +58,10 @@ if (server) { ftrScript = 'functional_test_runner'; } -const config = open ? './cypress_open.ts' : './cypress_run.ts'; +const config = open ? './ftr_config_open.ts' : './ftr_config_run.ts'; +const grepArg = grep ? `--grep "${grep}"` : ''; +const bailArg = bail ? `--bail` : ''; +const cmd = `node ../../../../scripts/${ftrScript} --config ${config} ${grepArg} ${bailArg} --kibana-install-dir '${kibanaInstallDir}'`; -childProcess.execSync( - `node ../../../../scripts/${ftrScript} --config ${config} --kibana-install-dir '${kibanaInstallDir}'`, - { cwd: e2eDir, stdio: 'inherit' } -); +console.log(`Running ${cmd}`); +childProcess.execSync(cmd, { cwd: e2eDir, stdio: 'inherit' }); diff --git a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts index 43e8140fb9b3c5..8ab632deec8098 100644 --- a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts +++ b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { kibanaPackageJson } from '@kbn/dev-utils'; + import { GetDeprecationsContext } from '../../../../../src/core/server'; import { CloudSetup } from '../../../cloud/server'; import { getDeprecations } from './'; @@ -19,7 +21,7 @@ const deprecationContext = { describe('getDeprecations', () => { describe('when fleet is disabled', () => { it('returns no deprecations', async () => { - const deprecationsCallback = getDeprecations({ branch: 'master' }); + const deprecationsCallback = getDeprecations({ branch: 'main' }); const deprecations = await deprecationsCallback(deprecationContext); expect(deprecations).toEqual([]); }); @@ -28,7 +30,7 @@ describe('getDeprecations', () => { describe('when running on cloud with legacy apm-server', () => { it('returns deprecations', async () => { const deprecationsCallback = getDeprecations({ - branch: 'master', + branch: 'main', cloudSetup: { isCloudEnabled: true } as unknown as CloudSetup, fleet: { start: () => ({ @@ -38,13 +40,20 @@ describe('getDeprecations', () => { }); const deprecations = await deprecationsCallback(deprecationContext); expect(deprecations).not.toEqual([]); + // TODO: remove when docs support "main" + if (kibanaPackageJson.branch === 'main') { + for (const { documentationUrl } of deprecations) { + expect(documentationUrl).toMatch(/\/master\//); + expect(documentationUrl).not.toMatch(/\/main\//); + } + } }); }); describe('when running on cloud with fleet', () => { it('returns no deprecations', async () => { const deprecationsCallback = getDeprecations({ - branch: 'master', + branch: 'main', cloudSetup: { isCloudEnabled: true } as unknown as CloudSetup, fleet: { start: () => ({ @@ -60,7 +69,7 @@ describe('getDeprecations', () => { describe('when running on prem', () => { it('returns no deprecations', async () => { const deprecationsCallback = getDeprecations({ - branch: 'master', + branch: 'main', cloudSetup: { isCloudEnabled: false } as unknown as CloudSetup, fleet: { start: () => ({ agentPolicyService: { get: () => undefined } }), diff --git a/x-pack/plugins/apm/server/deprecations/index.ts b/x-pack/plugins/apm/server/deprecations/index.ts index 76c90270abb8f8..39e282e76d9a69 100644 --- a/x-pack/plugins/apm/server/deprecations/index.ts +++ b/x-pack/plugins/apm/server/deprecations/index.ts @@ -38,6 +38,8 @@ export function getDeprecations({ const isCloudEnabled = !!cloudSetup?.isCloudEnabled; const hasCloudAgentPolicy = !isEmpty(cloudAgentPolicy); + // TODO: remove when docs support "main" + const docBranch = branch === 'main' ? 'master' : branch; if (isCloudEnabled && !hasCloudAgentPolicy) { deprecations.push({ @@ -48,7 +50,7 @@ export function getDeprecations({ defaultMessage: 'Running the APM Server binary directly is considered a legacy option and is deprecated since 7.16. Switch to APM Server managed by an Elastic Agent instead. Read our documentation to learn more.', }), - documentationUrl: `https://www.elastic.co/guide/en/apm/server/${branch}/apm-integration.html`, + documentationUrl: `https://www.elastic.co/guide/en/apm/server/${docBranch}/apm-integration.html`, level: 'warning', correctiveActions: { manualSteps: [ diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 6bf3b2cb493f12..bd30f9e2126871 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -14,7 +14,7 @@ import { maxSuggestions } from '../../observability/common'; import { SearchAggregatedTransactionSetting } from '../common/aggregated_transactions'; import { APMPlugin } from './plugin'; -// All options should be documented in the APM configuration settings: https://github.com/elastic/kibana/blob/master/docs/settings/apm-settings.asciidoc +// All options should be documented in the APM configuration settings: https://github.com/elastic/kibana/blob/main/docs/settings/apm-settings.asciidoc // and be included on cloud allow list unless there are specific reasons not to const configSchema = schema.object({ serviceMapEnabled: schema.boolean({ defaultValue: true }), @@ -37,7 +37,7 @@ const configSchema = schema.object({ schema.literal(SearchAggregatedTransactionSetting.always), schema.literal(SearchAggregatedTransactionSetting.never), ], - { defaultValue: SearchAggregatedTransactionSetting.never } + { defaultValue: SearchAggregatedTransactionSetting.auto } ), telemetryCollectionEnabled: schema.boolean({ defaultValue: true }), metricsInterval: schema.number({ defaultValue: 30 }), @@ -112,11 +112,11 @@ export const plugin = (initContext: PluginInitializerContext) => export { APM_SERVER_FEATURE_ID } from '../common/alert_types'; export { APMPlugin } from './plugin'; -export { APMPluginSetup } from './types'; -export { +export type { APMPluginSetup } from './types'; +export type { APMServerRouteRepository, APIEndpoint, } from './routes/get_global_apm_server_route_repository'; -export { APMRouteHandlerResources } from './routes/typings'; +export type { APMRouteHandlerResources } from './routes/typings'; export type { ProcessorEvent } from '../common/processor_event'; diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 31c533814e6977..dce8a3f397eaaa 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -80,12 +80,9 @@ export async function getBuckets({ const buckets = (resp.aggregations?.distribution.buckets || []).map( (bucket) => ({ - key: bucket.key, - count: bucket.doc_count, + x: bucket.key, + y: bucket.doc_count, }) ); - - return { - buckets: resp.hits.total.value > 0 ? buckets : [], - }; + return { buckets }; } diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts index 7c2eaf38be6a77..7d5cbe9f95584a 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { Setup } from '../../helpers/setup_request'; import { BUCKET_TARGET_COUNT } from '../../transactions/constants'; import { getBuckets } from './get_buckets'; @@ -64,7 +65,10 @@ export async function getErrorDistribution({ return { currentPeriod: currentPeriod.buckets, - previousPeriod: previousPeriod.buckets, + previousPeriod: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod.buckets, + previousPeriodTimeseries: previousPeriod.buckets, + }), bucketSize, }; } diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.test.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.test.ts new file mode 100644 index 00000000000000..3940fa60a38f4f --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { preprocessLegacyFields } from './get_apm_package_policy_definition'; + +const apmServerSchema = { + 'apm-server.host': '0.0.0.0:8200', + 'apm-server.secret_token': 'asdfkjhasdf', + 'apm-server.read_timeout': 3600, + 'apm-server.rum.event_rate.limit': 100, + 'apm-server.rum.event_rate.lru_size': 100, + 'apm-server.rum.allow_service_names': 'opbeans-test', + 'logging.level': 'error', + 'queue.mem.events': 2000, + 'queue.mem.flush.timeout': '1s', + 'setup.template.settings.index.number_of_jshards': 1, +}; + +describe('get_apm_package_policy_definition', () => { + describe('preprocessLegacyFields', () => { + it('should replace legacy fields with supported fields', () => { + const result = preprocessLegacyFields({ apmServerSchema }); + expect(result).toMatchInlineSnapshot(` + Object { + "apm-server.auth.anonymous.allow_service": "opbeans-test", + "apm-server.auth.anonymous.rate_limit.event_limit": 100, + "apm-server.auth.anonymous.rate_limit.ip_limit": 100, + "apm-server.auth.secret_token": "asdfkjhasdf", + "apm-server.host": "0.0.0.0:8200", + "apm-server.read_timeout": 3600, + "logging.level": "error", + "queue.mem.events": 2000, + "queue.mem.flush.timeout": "1s", + "setup.template.settings.index.number_of_jshards": 1, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts index 98b6a6489c47be..df922dd18fe4db 100644 --- a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts +++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts @@ -45,7 +45,7 @@ export function getApmPackagePolicyDefinition( }; } -function preprocessLegacyFields({ +export function preprocessLegacyFields({ apmServerSchema, }: { apmServerSchema: Record; @@ -64,6 +64,10 @@ function preprocessLegacyFields({ key: 'apm-server.auth.anonymous.allow_service', legacyKey: 'apm-server.rum.allow_service_names', }, + { + key: 'apm-server.auth.secret_token', + legacyKey: 'apm-server.secret_token', + }, ].forEach(({ key, legacyKey }) => { if (!copyOfApmServerSchema[key]) { copyOfApmServerSchema[key] = copyOfApmServerSchema[legacyKey]; diff --git a/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.test.ts b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.test.ts new file mode 100644 index 00000000000000..cdc56ba9794f6d --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.test.ts @@ -0,0 +1,58 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; +import { getUnsupportedApmServerSchema } from './get_unsupported_apm_server_schema'; + +const apmServerSchema = { + 'apm-server.host': '0.0.0.0:8200', + 'apm-server.secret_token': 'asdfkjhasdf', + 'apm-server.read_timeout': 3600, + 'apm-server.rum.event_rate.limit': 100, + 'apm-server.rum.event_rate.lru_size': 100, + 'apm-server.rum.allow_service_names': 'opbeans-test', + 'logging.level': 'error', + 'queue.mem.events': 2000, + 'queue.mem.flush.timeout': '1s', + 'setup.template.settings.index.number_of_jshards': 1, +}; + +const mockSavaedObectsClient = { + get: () => ({ + attributes: { schemaJson: JSON.stringify(apmServerSchema) }, + }), +} as unknown as SavedObjectsClientContract; + +describe('get_unsupported_apm_server_schema', () => { + describe('getUnsupportedApmServerSchema', () => { + it('should return key-value pairs of unsupported configs', async () => { + const result = await getUnsupportedApmServerSchema({ + savedObjectsClient: mockSavaedObectsClient, + }); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "key": "logging.level", + "value": "error", + }, + Object { + "key": "queue.mem.events", + "value": 2000, + }, + Object { + "key": "queue.mem.flush.timeout", + "value": "1s", + }, + Object { + "key": "setup.template.settings.index.number_of_jshards", + "value": 1, + }, + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts index 5fec3c94cf7ac7..2ced15245b5933 100644 --- a/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts +++ b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts @@ -10,7 +10,10 @@ import { APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE, APM_SERVER_SCHEMA_SAVED_OBJECT_ID, } from '../../../common/apm_saved_object_constants'; -import { apmConfigMapping } from './get_apm_package_policy_definition'; +import { + apmConfigMapping, + preprocessLegacyFields, +} from './get_apm_package_policy_definition'; export async function getUnsupportedApmServerSchema({ savedObjectsClient, @@ -24,7 +27,10 @@ export async function getUnsupportedApmServerSchema({ const apmServerSchema: Record = JSON.parse( (attributes as { schemaJson: string }).schemaJson ); - return Object.entries(apmServerSchema) + const preprocessedApmServerSchema = preprocessLegacyFields({ + apmServerSchema, + }); + return Object.entries(preprocessedApmServerSchema) .filter(([name]) => !(name in apmConfigMapping)) .map(([key, value]) => ({ key, value })); } diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts index 41dc33dfa193f4..386372fbf655e3 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts @@ -16,7 +16,5 @@ export function cancelEsRequestOnAbort>( controller.abort(); }); - promise.finally(() => subscription.unsubscribe()); - - return promise; + return promise.finally(() => subscription.unsubscribe()); } diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts index 473d34cd5b6fc0..edaae8cadc1d51 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts @@ -63,18 +63,23 @@ export async function getSearchAggregatedTransactions({ apmEventClient: APMEventClient; kuery: string; }): Promise { - const searchAggregatedTransactions = config.searchAggregatedTransactions; + switch (config.searchAggregatedTransactions) { + case SearchAggregatedTransactionSetting.always: + return kuery + ? getHasAggregatedTransactions({ start, end, apmEventClient, kuery }) + : true; - if ( - kuery || - searchAggregatedTransactions === SearchAggregatedTransactionSetting.auto - ) { - return getHasAggregatedTransactions({ start, end, apmEventClient, kuery }); - } + case SearchAggregatedTransactionSetting.auto: + return getHasAggregatedTransactions({ + start, + end, + apmEventClient, + kuery, + }); - return ( - searchAggregatedTransactions === SearchAggregatedTransactionSetting.always - ); + case SearchAggregatedTransactionSetting.never: + return false; + } } export function getTransactionDurationFieldForTransactions( diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts index 06138931c004ea..fb66cb96490854 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts @@ -123,7 +123,6 @@ export async function fetchAndTransformGcMetrics({ if (!aggregations) { return { ...chartBase, - noHits: true, series: [], }; } @@ -170,7 +169,6 @@ export async function fetchAndTransformGcMetrics({ return { ...chartBase, - noHits: response.hits.total.value === 0, series, }; } diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts index addc17e5b09d88..828ee282604714 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts @@ -107,7 +107,7 @@ export async function getMemoryChartData({ operationName: 'get_cgroup_memory_metrics_charts', }); - if (cgroupResponse.noHits) { + if (cgroupResponse.series.length === 0) { return await fetchAndTransformMetrics({ environment, kuery, diff --git a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts index d626842a5f5dc4..107e2d5774816e 100644 --- a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts +++ b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts @@ -60,7 +60,6 @@ test('transformDataToMetricsChart should transform an ES result into a chart obj expect(chart).toMatchInlineSnapshot(` Object { "key": "test_chart_key", - "noHits": false, "series": Array [ Object { "color": "red", diff --git a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index 999830dabefc47..7eaa90845d6522 100644 --- a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -19,14 +19,13 @@ export function transformDataToMetricsChart( result: ESSearchResponse, chartBase: ChartBase ) { - const { aggregations, hits } = result; + const { aggregations } = result; const timeseriesData = aggregations?.timeseriesData; return { title: chartBase.title, key: chartBase.key, yUnit: chartBase.yUnit, - noHits: hits.total.value === 0, series: Object.keys(chartBase.series).map((seriesKey, i) => { const overallValue = aggregations?.[seriesKey]?.value; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts index f170818d018d46..5fed2f4eb4dc47 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts @@ -151,7 +151,11 @@ export const latencyCorrelationsSearchServiceProvider: LatencyCorrelationsSearch params, percentileAggregationPercents ); - const percentiles = Object.values(percentilesRecords); + + // We need to round the percentiles values + // because the queries we're using based on it + // later on wouldn't allow numbers with decimals. + const percentiles = Object.values(percentilesRecords).map(Math.round); addLogMessage(`Loaded percentiles.`); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.ts index 292be1b5817aad..612225a2348cbf 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.ts @@ -7,6 +7,8 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ES_FIELD_TYPES } from '@kbn/field-types'; + import type { ElasticsearchClient } from 'src/core/server'; import type { SearchStrategyParams } from '../../../../common/search_strategies/types'; @@ -22,6 +24,12 @@ import { hasPrefixToInclude } from '../utils'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; +const SUPPORTED_ES_FIELD_TYPES = [ + ES_FIELD_TYPES.KEYWORD, + ES_FIELD_TYPES.IP, + ES_FIELD_TYPES.BOOLEAN, +]; + export const shouldBeExcluded = (fieldName: string) => { return ( FIELDS_TO_EXCLUDE_AS_CANDIDATE.has(fieldName) || @@ -54,7 +62,7 @@ export const fetchTransactionDurationFieldCandidates = async ( params: SearchStrategyParams ): Promise<{ fieldCandidates: string[] }> => { const { index } = params; - // Get all fields with keyword mapping + // Get all supported fields const respMapping = await esClient.fieldCaps({ index, fields: '*', @@ -64,9 +72,9 @@ export const fetchTransactionDurationFieldCandidates = async ( const acceptableFields: Set = new Set(); Object.entries(respMapping.body.fields).forEach(([key, value]) => { - const fieldTypes = Object.keys(value); - const isSupportedType = fieldTypes.some( - (type) => type === 'keyword' || type === 'ip' + const fieldTypes = Object.keys(value) as ES_FIELD_TYPES[]; + const isSupportedType = fieldTypes.some((type) => + SUPPORTED_ES_FIELD_TYPES.includes(type) ); // Definitely include if field name matches any of the wild card if (hasPrefixToInclude(key) && isSupportedType) { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.ts index 39d6aea2f38bda..e57ef5ee341ee7 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.ts @@ -60,12 +60,15 @@ const fetchTransactionDurationFieldTerms = async ( resp.body.aggregations .attribute_terms as estypes.AggregationsMultiBucketAggregate<{ key: string; + key_as_string?: string; }> )?.buckets; if (buckets?.length >= 1) { return buckets.map((d) => ({ fieldName, - fieldValue: d.key, + // The terms aggregation returns boolean fields as { key: 0, key_as_string: "false" }, + // so we need to pick `key_as_string` if it's present, otherwise searches on boolean fields would fail later on. + fieldValue: d.key_as_string ?? d.key, })); } } catch (e) { diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts index afb88189a5fd25..e064d97bb7aee9 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts @@ -49,8 +49,7 @@ describe('getServiceMapServiceNodeInfo', () => { it('returns data', async () => { jest.spyOn(getErrorRateModule, 'getErrorRate').mockResolvedValueOnce({ average: 0.5, - transactionErrorRate: [], - noHits: false, + timeseries: [{ x: 1634808240000, y: 0 }], }); const setup = { diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 43147684ef3f78..2f089adf70c1c7 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -104,7 +104,7 @@ async function getErrorStats({ end, }: Options) { return withApmSpan('get_error_rate_for_service_map_node', async () => { - const { noHits, average } = await getErrorRate({ + const { average } = await getErrorRate({ environment, setup, serviceName, @@ -113,8 +113,7 @@ async function getErrorStats({ end, kuery: '', }); - - return { avgErrorRate: noHits ? null : average }; + return { avgErrorRate: average }; }); } diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index ac1c2653bf1480..308a1b9a2db26c 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -55,12 +55,12 @@ export function getStoredAnnotations({ try { const response: ESSearchResponse = - await (unwrapEsResponse( + (await unwrapEsResponse( client.search({ index: annotationsClient.index, body, }) - ) as any); + )) as any; return response.hits.hits.map((hit) => { return { diff --git a/x-pack/plugins/apm/server/lib/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts index 686555e7764ab1..ea153a5ddcd4ce 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts @@ -70,7 +70,7 @@ export async function getServiceTransactionDetailedStatistics({ }; const response = await apmEventClient.search( - 'get_service_transaction_stats', + 'get_service_transaction_detail_stats', { apm: { events: [ @@ -82,6 +82,7 @@ export async function getServiceTransactionDetailedStatistics({ query: { bool: { filter: [ + { terms: { [SERVICE_NAME]: serviceNames } }, ...getDocumentTypeFilterForTransactions( searchAggregatedTransactions ), @@ -95,8 +96,6 @@ export async function getServiceTransactionDetailedStatistics({ services: { terms: { field: SERVICE_NAME, - include: serviceNames, - size: serviceNames.length, }, aggs: { transactionType: { diff --git a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index 107493af1a0c05..a37720cbc3790c 100644 --- a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -16,7 +16,7 @@ import { APMRouteHandlerResources } from '../../../routes/typings'; import { withApmSpan } from '../../../utils/with_apm_span'; import { ApmIndicesConfig } from '../../../../../observability/common/typings'; -export { ApmIndicesConfig }; +export type { ApmIndicesConfig }; type ISavedObjectsClient = Pick; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index e85bd61ac440af..b7318e81a84a3e 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -49,8 +49,7 @@ export async function getErrorRate({ start: number; end: number; }): Promise<{ - noHits: boolean; - transactionErrorRate: Coordinate[]; + timeseries: Coordinate[]; average: number | null; }> { const { apmEventClient } = setup; @@ -111,20 +110,16 @@ export async function getErrorRate({ 'get_transaction_group_error_rate', params ); - - const noHits = resp.hits.total.value === 0; - if (!resp.aggregations) { - return { noHits, transactionErrorRate: [], average: null }; + return { timeseries: [], average: null }; } - const transactionErrorRate = getFailedTransactionRateTimeSeries( + const timeseries = getFailedTransactionRateTimeSeries( resp.aggregations.timeseries.buckets ); - const average = calculateFailedTransactionRate(resp.aggregations.outcomes); - return { noHits, transactionErrorRate, average }; + return { timeseries, average }; } export async function getErrorRatePeriods({ @@ -171,22 +166,22 @@ export async function getErrorRatePeriods({ start: comparisonStart, end: comparisonEnd, }) - : { noHits: true, transactionErrorRate: [], average: null }; + : { timeseries: [], average: null }; const [currentPeriod, previousPeriod] = await Promise.all([ currentPeriodPromise, previousPeriodPromise, ]); - const firstCurrentPeriod = currentPeriod.transactionErrorRate; + const currentPeriodTimeseries = currentPeriod.timeseries; return { currentPeriod, previousPeriod: { ...previousPeriod, - transactionErrorRate: offsetPreviousPeriodCoordinates({ - currentPeriodTimeseries: firstCurrentPeriod, - previousPeriodTimeseries: previousPeriod.transactionErrorRate, + timeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries, + previousPeriodTimeseries: previousPeriod.timeseries, }), }, }; diff --git a/x-pack/plugins/apm/server/lib/transactions/trace_samples/get_trace_samples/index.ts b/x-pack/plugins/apm/server/lib/transactions/trace_samples/get_trace_samples/index.ts index b085c0fc4a8396..9b96cf19c516d8 100644 --- a/x-pack/plugins/apm/server/lib/transactions/trace_samples/get_trace_samples/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/trace_samples/get_trace_samples/index.ts @@ -93,19 +93,14 @@ export async function getTraceSamples({ }, }); - return response.hits.hits; + return response.hits.hits.map((hit) => ({ + transactionId: hit._source.transaction.id, + traceId: hit._source.trace.id, + })); } - const samplesForDistributionHits = await getTraceSamplesHits(); - - const traceSamples = samplesForDistributionHits.map((hit) => ({ - transactionId: hit._source.transaction.id, - traceId: hit._source.trace.id, - })); - return { - noHits: samplesForDistributionHits.length === 0, - traceSamples, + traceSamples: await getTraceSamplesHits(), }; }); } diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index 66ff8f5b2c92cc..f04b794091ff29 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -103,7 +103,6 @@ It allows you to monitor the performance of thousands of applications in real ti } ), euiIconType: 'apmApp', - eprPackageOverlap: 'apm', integrationBrowserCategories: ['web'], artifacts, customStatusCheckName: 'apm_fleet_server_status_check', diff --git a/x-pack/plugins/banners/common/index.ts b/x-pack/plugins/banners/common/index.ts index a4c38a58ab5726..348d42adb7d8f3 100644 --- a/x-pack/plugins/banners/common/index.ts +++ b/x-pack/plugins/banners/common/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { BannerInfoResponse, BannerPlacement, BannerConfiguration } from './types'; +export type { BannerInfoResponse, BannerPlacement, BannerConfiguration } from './types'; diff --git a/x-pack/plugins/banners/kibana.json b/x-pack/plugins/banners/kibana.json index ba8cbda6470135..fde775b0ff965f 100644 --- a/x-pack/plugins/banners/kibana.json +++ b/x-pack/plugins/banners/kibana.json @@ -8,7 +8,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["licensing"], + "requiredPlugins": ["licensing", "screenshotMode"], "optionalPlugins": [], "requiredBundles": ["kibanaReact"], "configPath": ["xpack", "banners"] diff --git a/x-pack/plugins/banners/public/plugin.test.tsx b/x-pack/plugins/banners/public/plugin.test.tsx index 8722d9516b9dba..057b4110f20a13 100644 --- a/x-pack/plugins/banners/public/plugin.test.tsx +++ b/x-pack/plugins/banners/public/plugin.test.tsx @@ -7,6 +7,7 @@ import { getBannerInfoMock } from './plugin.test.mocks'; import { coreMock } from '../../../../src/core/public/mocks'; +import { screenshotModePluginMock } from '../../../../src/plugins/screenshot_mode/public/mocks'; import { BannerConfiguration } from '../common/types'; import { BannersPlugin } from './plugin'; @@ -25,11 +26,13 @@ describe('BannersPlugin', () => { let pluginInitContext: ReturnType; let coreSetup: ReturnType; let coreStart: ReturnType; + let screenshotModeStart: ReturnType; beforeEach(() => { pluginInitContext = coreMock.createPluginInitializerContext(); coreSetup = coreMock.createSetup(); coreStart = coreMock.createStart(); + screenshotModeStart = screenshotModePluginMock.createStartContract(); getBannerInfoMock.mockResolvedValue({ allowed: false, @@ -41,7 +44,7 @@ describe('BannersPlugin', () => { pluginInitContext = coreMock.createPluginInitializerContext(); plugin = new BannersPlugin(pluginInitContext); plugin.setup(coreSetup); - plugin.start(coreStart); + plugin.start(coreStart, { screenshotMode: screenshotModeStart }); // await for the `getBannerInfo` promise to resolve await nextTick(); }; @@ -79,6 +82,14 @@ describe('BannersPlugin', () => { expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledTimes(0); }); + + it('does not register the banner in screenshot mode', async () => { + screenshotModeStart.isScreenshotMode.mockReturnValue(true); + + await startPlugin(); + + expect(coreStart.chrome.setHeaderBanner).not.toHaveBeenCalled(); + }); }); describe('when banner is not allowed', () => { @@ -107,5 +118,13 @@ describe('BannersPlugin', () => { expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledTimes(0); }); + + it('does not register the banner in screenshot mode', async () => { + screenshotModeStart.isScreenshotMode.mockReturnValue(true); + + await startPlugin(); + + expect(coreStart.chrome.setHeaderBanner).not.toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/banners/public/plugin.tsx b/x-pack/plugins/banners/public/plugin.tsx index 014d2de58b9ea8..79aeb153879638 100644 --- a/x-pack/plugins/banners/public/plugin.tsx +++ b/x-pack/plugins/banners/public/plugin.tsx @@ -10,27 +10,33 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; import { Banner } from './components'; import { getBannerInfo } from './get_banner_info'; +import { BannerPluginStartDependencies } from './types'; -export class BannersPlugin implements Plugin<{}, {}, {}, {}> { +export class BannersPlugin implements Plugin<{}, {}, {}, BannerPluginStartDependencies> { constructor(context: PluginInitializerContext) {} setup({}: CoreSetup<{}, {}>) { return {}; } - start({ chrome, uiSettings, http }: CoreStart) { - getBannerInfo(http).then( - ({ allowed, banner }) => { - if (allowed && banner.placement === 'top') { - chrome.setHeaderBanner({ - content: toMountPoint(), - }); + start( + { chrome, uiSettings, http }: CoreStart, + { screenshotMode }: BannerPluginStartDependencies + ) { + if (!screenshotMode.isScreenshotMode()) { + getBannerInfo(http).then( + ({ allowed, banner }) => { + if (allowed && banner.placement === 'top') { + chrome.setHeaderBanner({ + content: toMountPoint(), + }); + } + }, + () => { + chrome.setHeaderBanner(undefined); } - }, - () => { - chrome.setHeaderBanner(undefined); - } - ); + ); + } return {}; } diff --git a/x-pack/plugins/banners/public/types.ts b/x-pack/plugins/banners/public/types.ts new file mode 100644 index 00000000000000..b0d6cf788977c4 --- /dev/null +++ b/x-pack/plugins/banners/public/types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ScreenshotModePluginStart } from '../../../../src/plugins/screenshot_mode/public'; + +export interface BannerPluginStartDependencies { + screenshotMode: ScreenshotModePluginStart; +} diff --git a/x-pack/plugins/banners/tsconfig.json b/x-pack/plugins/banners/tsconfig.json index 48767fb4525f50..56c347d985ed27 100644 --- a/x-pack/plugins/banners/tsconfig.json +++ b/x-pack/plugins/banners/tsconfig.json @@ -6,16 +6,11 @@ "declaration": true, "declarationMap": true }, - "include": [ - "public/**/*", - "server/**/*", - "common/**/*", - "../../../typings/**/*" - ], + "include": ["public/**/*", "server/**/*", "common/**/*", "../../../typings/**/*"], "references": [ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/screenshot_mode/tsconfig.json" }, { "path": "../licensing/tsconfig.json" } ] } - diff --git a/x-pack/plugins/canvas/PLUGINS.mdx b/x-pack/plugins/canvas/PLUGINS.mdx index 0f93948d663a07..77fe65f864607e 100644 --- a/x-pack/plugins/canvas/PLUGINS.mdx +++ b/x-pack/plugins/canvas/PLUGINS.mdx @@ -222,7 +222,7 @@ Now, let's try out our new server function. You should now see one random number and one "Server Time in ms" value. -> More information about building Kibana Plugins can be found in [src/core](https://github.com/elastic/kibana/blob/master/src/core/README.md) +> More information about building Kibana Plugins can be found in [src/core](https://github.com/elastic/kibana/blob/main/src/core/README.md) ### My Canvas Plugin stopped working diff --git a/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts b/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts index ac2e8e8babee1e..66cb95a4a210a7 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts @@ -10,7 +10,8 @@ import { EmbeddableInput } from '../../../../../src/plugins/embeddable/common/'; import { EmbeddableTypes } from './embeddable_types'; export const EmbeddableExpressionType = 'embeddable'; -export { EmbeddableTypes, EmbeddableInput }; +export type { EmbeddableInput }; +export { EmbeddableTypes }; export interface EmbeddableExpression { /** diff --git a/x-pack/plugins/canvas/common/index.ts b/x-pack/plugins/canvas/common/index.ts index 5bae69e8601b2b..51a661f6e8d1ee 100644 --- a/x-pack/plugins/canvas/common/index.ts +++ b/x-pack/plugins/canvas/common/index.ts @@ -9,4 +9,5 @@ export const UI_SETTINGS = { ENABLE_LABS_UI: 'labs:canvas:enable_ui', }; -export { CANVAS_APP_LOCATOR, CanvasAppLocator, CanvasAppLocatorParams } from './locator'; +export type { CanvasAppLocator, CanvasAppLocatorParams } from './locator'; +export { CANVAS_APP_LOCATOR } from './locator'; diff --git a/x-pack/plugins/canvas/i18n/README.md b/x-pack/plugins/canvas/i18n/README.md index 45459b4191fae0..42e088abaa3200 100644 --- a/x-pack/plugins/canvas/i18n/README.md +++ b/x-pack/plugins/canvas/i18n/README.md @@ -1,6 +1,6 @@ # Canvas and Internationalization (i18n) -Creating i18n strings in Kibana requires use of the [`@kbn/i18n`](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/GUIDELINE.md) library. The following outlines the strategy for localizing strings in Canvas +Creating i18n strings in Kibana requires use of the [`@kbn/i18n`](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/GUIDELINE.md) library. The following outlines the strategy for localizing strings in Canvas ## Why i18n Dictionaries diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx index 04d3958b68e365..937c9f56f948c7 100644 --- a/x-pack/plugins/canvas/public/application.tsx +++ b/x-pack/plugins/canvas/public/application.tsx @@ -37,6 +37,7 @@ import { services, LegacyServicesProvider, CanvasPluginServices, + pluginServices as canvasServices, } from './services'; import { initFunctions } from './functions'; // @ts-expect-error untyped local @@ -151,7 +152,13 @@ export const initializeCanvas = async ( }, ], content: (domNode) => { - ReactDOM.render(, domNode); + ReactDOM.render( + , + domNode + ); return () => ReactDOM.unmountComponentAtNode(domNode); }, }); diff --git a/x-pack/plugins/canvas/public/components/app/index.tsx b/x-pack/plugins/canvas/public/components/app/index.tsx index 288ecaf83ab694..0cb229608086c6 100644 --- a/x-pack/plugins/canvas/public/components/app/index.tsx +++ b/x-pack/plugins/canvas/public/components/app/index.tsx @@ -6,11 +6,13 @@ */ import React, { FC, useRef, useEffect } from 'react'; +import { Observable } from 'rxjs'; import PropTypes from 'prop-types'; import { History } from 'history'; // @ts-expect-error import createHashStateHistory from 'history-extra/dist/createHashStateHistory'; import { ScopedHistory } from 'kibana/public'; +import { skipWhile, timeout, take } from 'rxjs/operators'; import { useNavLinkService } from '../../services'; // @ts-expect-error import { shortcutManager } from '../../lib/shortcut_manager'; @@ -40,14 +42,50 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => { }); }); - // We are using our own history due to needing pushState functionality not yet available on standard history - // This effect will listen for changes on the scoped history and push that to our history - // This is needed for SavedObject.resolve redirects useEffect(() => { - return history.listen((location) => { - historyRef.current.replace(location.hash.substr(1)); + return history.listen(({ pathname, hash }) => { + // The scoped history could have something that triggers a url change, and that change is not seen by + // our hash router. For example, a scopedHistory.replace() as done as part of the saved object resolve + // alias match flow will do the replace on the scopedHistory, and our app doesn't react appropriately + + // So, to work around this, whenever we see a url on the scoped history, we're going to wait a beat and see + // if it shows up in our hash router. If it doesn't, then we're going to force it onto our hash router + + // I don't like this at all, and to overcome this we should switch away from hash router sooner rather than later + // and just use scopedHistory as our history object + const expectedPath = hash.substr(1); + const action = history.action; + + // Observable of all the path + const hashPaths$ = new Observable((subscriber) => { + subscriber.next(historyRef.current.location.pathname); + + const unsubscribeHashListener = historyRef.current.listen(({ pathname: newPath }) => { + subscriber.next(newPath); + }); + + return unsubscribeHashListener; + }); + + const subscription = hashPaths$ + .pipe( + skipWhile((value) => value !== expectedPath), + timeout(100), + take(1) + ) + .subscribe({ + error: (e) => { + if (action === 'REPLACE') { + historyRef.current.replace(expectedPath); + } else { + historyRef.current.push(expectedPath); + } + }, + }); + + window.setTimeout(() => subscription.unsubscribe(), 150); }); - }, [history]); + }, [history, historyRef]); return ( diff --git a/x-pack/plugins/canvas/public/components/color_manager/index.ts b/x-pack/plugins/canvas/public/components/color_manager/index.ts index 17856a88bcc003..937afeb438006a 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/index.ts +++ b/x-pack/plugins/canvas/public/components/color_manager/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ColorManager, Props } from './color_manager'; +export type { Props } from './color_manager'; +export { ColorManager } from './color_manager'; diff --git a/x-pack/plugins/canvas/public/components/color_picker/index.ts b/x-pack/plugins/canvas/public/components/color_picker/index.ts index ba411db1129e50..709b3535b5d582 100644 --- a/x-pack/plugins/canvas/public/components/color_picker/index.ts +++ b/x-pack/plugins/canvas/public/components/color_picker/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ColorPicker, Props } from './color_picker'; +export type { Props } from './color_picker'; +export { ColorPicker } from './color_picker'; diff --git a/x-pack/plugins/canvas/public/components/color_picker_popover/index.ts b/x-pack/plugins/canvas/public/components/color_picker_popover/index.ts index 76663397ff6953..08bf41f2fbc03a 100644 --- a/x-pack/plugins/canvas/public/components/color_picker_popover/index.ts +++ b/x-pack/plugins/canvas/public/components/color_picker_popover/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ColorPickerPopover, Props } from './color_picker_popover'; +export type { Props } from './color_picker_popover'; +export { ColorPickerPopover } from './color_picker_popover'; diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/index.ts b/x-pack/plugins/canvas/public/components/embeddable_flyout/index.ts index 6acc4051e1ecc1..98943a820e37a1 100644 --- a/x-pack/plugins/canvas/public/components/embeddable_flyout/index.ts +++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/index.ts @@ -6,7 +6,5 @@ */ export { EmbeddableFlyoutPortal, AddEmbeddablePanel } from './flyout'; -export { - AddEmbeddableFlyout as AddEmbeddableFlyoutComponent, - Props as AddEmbeddableFlyoutComponentProps, -} from './flyout.component'; +export type { Props as AddEmbeddableFlyoutComponentProps } from './flyout.component'; +export { AddEmbeddableFlyout as AddEmbeddableFlyoutComponent } from './flyout.component'; diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx index b4d22d8e6e6db3..49b5aaaf1b2099 100644 --- a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx +++ b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx @@ -9,15 +9,16 @@ import React, { FC } from 'react'; import { ExpressionFunction } from 'src/plugins/expressions'; import { EuiButtonEmpty } from '@elastic/eui'; import copy from 'copy-to-clipboard'; -import { useNotifyService } from '../../services'; +import { CanvasPluginServices } from '../../services'; + import { generateFunctionReference } from './generate_function_reference'; interface Props { functionRegistry: Record; + notifyService: CanvasPluginServices['notify']; } -export const FunctionReferenceGenerator: FC = ({ functionRegistry }) => { - const notifyService = useNotifyService(); +export const FunctionReferenceGenerator: FC = ({ functionRegistry, notifyService }) => { const functionDefinitions = Object.values(functionRegistry); const copyDocs = () => { diff --git a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx index af1850beb5290f..9331de3fcad4be 100644 --- a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx +++ b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunction } from 'src/plugins/expressions'; import { KeyboardShortcutsDoc } from '../keyboard_shortcuts_doc'; +import { CanvasPluginServices } from '../../services/'; let FunctionReferenceGenerator: null | React.LazyExoticComponent = null; @@ -31,9 +32,10 @@ const strings = { interface Props { functionRegistry: Record; + notifyService: CanvasPluginServices['notify']; } -export const HelpMenu: FC = ({ functionRegistry }) => { +export const HelpMenu: FC = ({ functionRegistry, notifyService }) => { const [isFlyoutVisible, setFlyoutVisible] = useState(false); const showFlyout = () => { @@ -53,7 +55,10 @@ export const HelpMenu: FC = ({ functionRegistry }) => { {FunctionReferenceGenerator ? ( - + ) : null} diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot b/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot index 81a92ca7139baf..1277505d2d2086 100644 --- a/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot @@ -353,7 +353,7 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = `
= ({ // NOTE: the data-shared-* attributes here are used for reporting return Style.it( - workpadCss, + workpadCss || DEFAULT_WORKPAD_CSS,
{ const renderers: string[] = []; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts index 311ef73e1c9732..bee0e1f3a5177d 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts @@ -9,6 +9,7 @@ import type { RedirectOptions } from 'src/plugins/share/public'; import { CANVAS_APP_LOCATOR } from '../../../../common/locator'; import { CanvasAppLocatorParams } from '../../../../common/locator'; import { CanvasWorkpad } from '../../../../types'; +import { JobAppParamsPDFV2 } from '../../../../../reporting/public'; export interface CanvasWorkpadSharingData { workpad: Pick; @@ -18,7 +19,7 @@ export interface CanvasWorkpadSharingData { export function getPdfJobParams( { workpad: { id, name: title, width, height }, pageCount }: CanvasWorkpadSharingData, version: string -) { +): JobAppParamsPDFV2 { // The viewport in Reporting by specifying the dimensions. In order for things to work, // we need a viewport that will include all of the pages in the workpad. The viewport // also needs to include any offset values from the 0,0 position, otherwise the cropped diff --git a/x-pack/plugins/canvas/public/index.ts b/x-pack/plugins/canvas/public/index.ts index a15b15fcae3337..95d4333c6e9e53 100644 --- a/x-pack/plugins/canvas/public/index.ts +++ b/x-pack/plugins/canvas/public/index.ts @@ -10,7 +10,7 @@ import { CoreStart } from '../../../../src/core/public'; import { CanvasServices } from './services'; import { CanvasSetup, CanvasStart, CanvasStartDeps, CanvasPlugin } from './plugin'; -export { CanvasSetup, CanvasStart }; +export type { CanvasSetup, CanvasStart }; export interface WithKibanaProps { kibana: { diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/plugins/canvas/public/lib/run_interpreter.ts index 6c10b82fae3fd9..9633d91b8b8b28 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.ts @@ -19,11 +19,13 @@ interface Options { */ export async function interpretAst( ast: ExpressionAstExpression, - variables: Record + variables: Record, + input: ExpressionValue = null ): Promise { const context = { variables }; const { execute } = pluginServices.getServices().expressions; - return await execute(ast, null, context).getData().pipe(pluck('result')).toPromise(); + + return await execute(ast, input, context).getData().pipe(pluck('result')).toPromise(); } /** @@ -43,9 +45,9 @@ export async function runInterpreter( options: Options = {} ): Promise { const context = { variables }; - try { const { execute } = pluginServices.getServices().expressions; + const renderable = await execute(ast, input, context) .getData() .pipe(pluck('result')) diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index 723d1afea2860f..5d1f05fdbe8bf5 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -37,7 +37,7 @@ import { getPluginApi, CanvasApi } from './plugin_api'; import { setupExpressions } from './setup_expressions'; import { pluginServiceRegistry } from './services/kibana'; -export { CoreStart, CoreSetup }; +export type { CoreStart, CoreSetup }; /** * These are the private interfaces for the services your plugin depends on. diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts index 9021c6d6c27533..ca66fa227e4eb0 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts @@ -5,6 +5,7 @@ * 2.0. */ import { useContext, useEffect } from 'react'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; import { usePlatformService } from '../../../services'; import { WorkpadRoutingContext } from '..'; @@ -27,4 +28,10 @@ export const useFullscreenPresentationHelper = () => { setFullscreen(true); } }, [isFullscreen, setFullscreen]); + + // Remove fullscreen when component unmounts + useEffectOnce(() => () => { + setFullscreen(true); + document.querySelector('body')?.classList.remove(fullscreenClass); + }); }; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts index 35e79b442a15d7..963a69a8f11f08 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts @@ -35,7 +35,7 @@ export const useWorkpad = ( const [error, setError] = useState(undefined); const [resolveInfo, setResolveInfo] = useState< - { aliasId: string | undefined; outcome: string } | undefined + { id: string; aliasId: string | undefined; outcome: string } | undefined >(undefined); useEffect(() => { @@ -47,15 +47,16 @@ export const useWorkpad = ( workpad: { assets, ...workpad }, } = await workpadResolve(workpadId); - setResolveInfo({ aliasId, outcome }); + setResolveInfo({ aliasId, outcome, id: workpadId }); - if (outcome === 'conflict') { + // If it's an alias match, we know we are going to redirect so don't even dispatch that we got the workpad + if (outcome !== 'aliasMatch') { workpad.aliasId = aliasId; - } - dispatch(setAssets(assets)); - dispatch(setWorkpad(workpad, { loadPages })); - dispatch(setZoomScale(1)); + dispatch(setAssets(assets)); + dispatch(setWorkpad(workpad, { loadPages })); + dispatch(setZoomScale(1)); + } } catch (e) { setError(e as Error | string); } @@ -63,15 +64,21 @@ export const useWorkpad = ( }, [workpadId, dispatch, setError, loadPages, workpadResolve]); useEffect(() => { - (() => { + // If the resolved info is not for the current workpad id, bail out + if (resolveInfo && resolveInfo.id !== workpadId) { + return; + } + + (async () => { if (!resolveInfo) return; const { aliasId, outcome } = resolveInfo; if (outcome === 'aliasMatch' && platformService.redirectLegacyUrl && aliasId) { - platformService.redirectLegacyUrl(`#${getRedirectPath(aliasId)}`, getWorkpadLabel()); + const redirectPath = getRedirectPath(aliasId); + await platformService.redirectLegacyUrl(`#${redirectPath}`, getWorkpadLabel()); } })(); - }, [resolveInfo, getRedirectPath, platformService]); + }, [workpadId, resolveInfo, getRedirectPath, platformService]); return [storedWorkpad.id === workpadId ? storedWorkpad : undefined, error]; }; diff --git a/x-pack/plugins/canvas/public/routes/workpad/index.tsx b/x-pack/plugins/canvas/public/routes/workpad/index.tsx index 0b6153bc06afdc..1b46a5cf78df1b 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/index.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/index.tsx @@ -9,7 +9,8 @@ import { RouteComponentProps } from 'react-router-dom'; export { WorkpadRoute, ExportWorkpadRoute } from './workpad_route'; -export { WorkpadRoutingContext, WorkpadRoutingContextType } from './workpad_routing_context'; +export type { WorkpadRoutingContextType } from './workpad_routing_context'; +export { WorkpadRoutingContext } from './workpad_routing_context'; export interface WorkpadRouteParams { id: string; diff --git a/x-pack/plugins/canvas/public/services/kibana/custom_element.ts b/x-pack/plugins/canvas/public/services/kibana/custom_element.ts index ec3b68d2d0bba7..093373d55a3c50 100644 --- a/x-pack/plugins/canvas/public/services/kibana/custom_element.ts +++ b/x-pack/plugins/canvas/public/services/kibana/custom_element.ts @@ -25,8 +25,8 @@ export const customElementServiceFactory: CanvasCustomElementServiceFactory = ({ create: (customElement) => http.post(apiPath, { body: JSON.stringify(customElement) }), get: (customElementId) => http - .get(`${apiPath}/${customElementId}`) - .then(({ data: element }: { data: CustomElement }) => element), + .get<{ data: CustomElement }>(`${apiPath}/${customElementId}`) + .then(({ data: element }) => element), update: (id, element) => http.put(`${apiPath}/${id}`, { body: JSON.stringify(element) }), remove: (id) => http.delete(`${apiPath}/${id}`), find: async (name) => { diff --git a/x-pack/plugins/canvas/public/services/kibana/notify.ts b/x-pack/plugins/canvas/public/services/kibana/notify.ts index 0082b523d050e2..1752840127fe1c 100644 --- a/x-pack/plugins/canvas/public/services/kibana/notify.ts +++ b/x-pack/plugins/canvas/public/services/kibana/notify.ts @@ -42,7 +42,7 @@ export const notifyServiceFactory: CanvasNotifyServiceFactory = ({ coreStart }) return { /* * @param {(string | Object)} err: message or Error object - * @param {Object} opts: option to override toast title or icon, see https://github.com/elastic/kibana/blob/master/src/legacy/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md + * @param {Object} opts: option to override toast title or icon, see https://github.com/elastic/kibana/blob/main/src/legacy/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md */ error(err, opts) { toasts.addDanger(getToast(err, opts)); diff --git a/x-pack/plugins/canvas/public/services/kibana/workpad.ts b/x-pack/plugins/canvas/public/services/kibana/workpad.ts index 35b82735845d07..9f69d5096237cc 100644 --- a/x-pack/plugins/canvas/public/services/kibana/workpad.ts +++ b/x-pack/plugins/canvas/public/services/kibana/workpad.ts @@ -63,7 +63,7 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart, return { get: async (id: string) => { - const workpad = await coreStart.http.get(`${getApiPath()}/${id}`); + const workpad = await coreStart.http.get(`${getApiPath()}/${id}`); return { css: DEFAULT_WORKPAD_CSS, variables: [], ...workpad }; }, diff --git a/x-pack/plugins/canvas/public/services/legacy/index.ts b/x-pack/plugins/canvas/public/services/legacy/index.ts index fdc4e30cabe51b..82ef3f248f7fdf 100644 --- a/x-pack/plugins/canvas/public/services/legacy/index.ts +++ b/x-pack/plugins/canvas/public/services/legacy/index.ts @@ -10,7 +10,7 @@ import { CoreSetup, CoreStart, AppUpdater } from '../../../../../../src/core/pub import { CanvasSetupDeps, CanvasStartDeps } from '../../plugin'; import { searchServiceFactory } from './search'; -export { SearchService } from './search'; +export type { SearchService } from './search'; export { ExpressionsService } from '../../../../../../src/plugins/expressions/common'; export * from './context'; diff --git a/x-pack/plugins/canvas/public/setup_expressions.ts b/x-pack/plugins/canvas/public/setup_expressions.ts index e182d8efa097fe..54f1c6dbc0d961 100644 --- a/x-pack/plugins/canvas/public/setup_expressions.ts +++ b/x-pack/plugins/canvas/public/setup_expressions.ts @@ -26,7 +26,7 @@ export const setupExpressions = async ({ const loadServerFunctionWrappers = async () => { if (!cached) { cached = (async () => { - const serverFunctionList = await coreSetup.http.get(API_ROUTE_FUNCTIONS); + const serverFunctionList = await coreSetup.http.get(API_ROUTE_FUNCTIONS); const batchedFunction = bfetch.batchedFunction({ url: API_ROUTE_FUNCTIONS }); const { serialize } = serializeProvider(expressions.getTypes()); diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js index a8302cf0940161..c8d322163b54fe 100644 --- a/x-pack/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/plugins/canvas/public/state/actions/elements.js @@ -111,7 +111,8 @@ export const fetchContext = createThunk( ...element.ast, chain: astChain, }, - variables + variables, + prevContextValue ).then((value) => { dispatch( args.setValue({ diff --git a/x-pack/plugins/canvas/public/store.ts b/x-pack/plugins/canvas/public/store.ts index e8821bafbb052d..7fe4eb21ee46f2 100644 --- a/x-pack/plugins/canvas/public/store.ts +++ b/x-pack/plugins/canvas/public/store.ts @@ -32,7 +32,7 @@ export async function createFreshStore(core: CoreSetup) { const basePath = core.http.basePath.get(); // Retrieve server functions - const serverFunctionsResponse = await core.http.get(API_ROUTE_FUNCTIONS); + const serverFunctionsResponse = await core.http.get>(API_ROUTE_FUNCTIONS); const serverFunctions = Object.values(serverFunctionsResponse); initialState.app = { diff --git a/x-pack/plugins/canvas/server/mocks/index.ts b/x-pack/plugins/canvas/server/mocks/index.ts index 1cb39c690df979..f3d3ef955d0e48 100644 --- a/x-pack/plugins/canvas/server/mocks/index.ts +++ b/x-pack/plugins/canvas/server/mocks/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { workpadRouteContextMock, MockWorkpadRouteContext } from './workpad_route_context'; +export type { MockWorkpadRouteContext } from './workpad_route_context'; +export { workpadRouteContextMock } from './workpad_route_context'; diff --git a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts index 96d1789a7cae4d..d891a5405a4247 100644 --- a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts +++ b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts @@ -28,11 +28,16 @@ export function loadSampleData( return savedObject; }); } + const getPath = (objectId: string) => `/app/canvas#/workpad/${objectId}`; addSavedObjectsToSampleDataset('ecommerce', updateCanvasWorkpadTimestamps(ecommerceSavedObjects)); addAppLinksToSampleDataset('ecommerce', [ { - path: '/app/canvas#/workpad/workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e', + sampleObject: { + type: 'canvas-workpad', + id: 'workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e', + }, + getPath, icon: 'canvasApp', label, }, @@ -41,7 +46,11 @@ export function loadSampleData( addSavedObjectsToSampleDataset('flights', updateCanvasWorkpadTimestamps(flightsSavedObjects)); addAppLinksToSampleDataset('flights', [ { - path: '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0', + sampleObject: { + type: 'canvas-workpad', + id: 'workpad-a474e74b-aedc-47c3-894a-db77e62c41e0', + }, + getPath, icon: 'canvasApp', label, }, @@ -50,7 +59,11 @@ export function loadSampleData( addSavedObjectsToSampleDataset('logs', updateCanvasWorkpadTimestamps(webLogsSavedObjects)); addAppLinksToSampleDataset('logs', [ { - path: '/app/canvas#/workpad/workpad-ad72a4e9-b422-480c-be6d-a64a0b79541d', + sampleObject: { + type: 'canvas-workpad', + id: 'workpad-ad72a4e9-b422-480c-be6d-a64a0b79541d', + }, + getPath, icon: 'canvasApp', label, }, diff --git a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js index dc516060ea3608..dee608fcf07027 100644 --- a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js +++ b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js @@ -150,7 +150,7 @@ module.exports = { additionalData(content, loaderContext) { return `@import ${stringifyRequest( loaderContext, - path.resolve(KIBANA_ROOT, 'src/core/public/core_app/styles/_globals_v7light.scss') + path.resolve(KIBANA_ROOT, 'src/core/public/core_app/styles/_globals_v8light.scss') )};\n${content}`; }, implementation: require('node-sass'), diff --git a/x-pack/plugins/canvas/storybook/addon/src/register.tsx b/x-pack/plugins/canvas/storybook/addon/src/register.tsx index 1e99d6bdb74eb8..71a3684b171aaf 100644 --- a/x-pack/plugins/canvas/storybook/addon/src/register.tsx +++ b/x-pack/plugins/canvas/storybook/addon/src/register.tsx @@ -39,7 +39,7 @@ addons.setConfig({ theme: create({ base: 'light', brandTitle: 'Canvas Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas', + brandUrl: 'https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas', }), showPanel: true, isFullscreen: false, diff --git a/x-pack/plugins/cases/README.md b/x-pack/plugins/cases/README.md index f28926eb52052d..a4c3b46ae9973a 100644 --- a/x-pack/plugins/cases/README.md +++ b/x-pack/plugins/cases/README.md @@ -167,7 +167,7 @@ UI component: _***Feature in development, disabled by default**_ -See [Kibana Actions](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions) for more information. +See [Kibana Actions](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions) for more information. ID: `.case` diff --git a/x-pack/plugins/cases/common/api/runtime_types.ts b/x-pack/plugins/cases/common/api/runtime_types.ts index 7edc1162c0e818..c807d4b31b7515 100644 --- a/x-pack/plugins/cases/common/api/runtime_types.ts +++ b/x-pack/plugins/cases/common/api/runtime_types.ts @@ -60,7 +60,7 @@ export const decodeOrThrow = const getExcessProps = (props: rt.Props, r: Record): string[] => { const ex: string[] = []; for (const k of Object.keys(r)) { - if (!props.hasOwnProperty(k)) { + if (!Object.prototype.hasOwnProperty.call(props, k)) { ex.push(k); } } @@ -89,5 +89,5 @@ export function excess | rt.PartialType & { + rule?: Exclude & { uuid: string[] }; + building_block_type?: string[]; + workflow_status?: string[]; +}; + export interface Ecs { _id: string; _index?: string; signal?: SignalEcs; + kibana?: { + alert: SignalEcsAAD; + }; } export type CaseActionConnector = ActionConnector; diff --git a/x-pack/plugins/encrypted_saved_objects/server/audit/index.ts b/x-pack/plugins/cases/common/utils/index.ts similarity index 79% rename from x-pack/plugins/encrypted_saved_objects/server/audit/index.ts rename to x-pack/plugins/cases/common/utils/index.ts index 99bf6adde22e9b..072c2b10dc2257 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/audit/index.ts +++ b/x-pack/plugins/cases/common/utils/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { EncryptedSavedObjectsAuditLogger } from './audit_logger'; +export * from './connectors_api'; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts index 09f0215f5629fc..19276a7afd1b83 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable react/display-name */ + import React from 'react'; import { RecursivePartial } from '@elastic/eui/src/components/common'; diff --git a/x-pack/plugins/cases/public/common/shared_imports.ts b/x-pack/plugins/cases/public/common/shared_imports.ts index 4641fcfa2167cb..21040543fae699 100644 --- a/x-pack/plugins/cases/public/common/shared_imports.ts +++ b/x-pack/plugins/cases/public/common/shared_imports.ts @@ -5,31 +5,33 @@ * 2.0. */ +export type { + FieldHook, + FieldValidateResponse, + FormData, + FormHook, + FormSchema, + ValidationError, + ValidationFunc, + FieldConfig, + ValidationConfig, +} from '../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { getUseField, getFieldValidityAndErrorMessage, - FieldHook, - FieldValidateResponse, FIELD_TYPES, Form, - FormData, FormDataProvider, - FormHook, - FormSchema, UseField, UseMultiFields, useForm, useFormContext, useFormData, - ValidationError, - ValidationFunc, VALIDATION_TYPES, - FieldConfig, - ValidationConfig, } from '../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { Field, SelectField, } from '../../../../../src/plugins/es_ui_shared/static/forms/components'; export { fieldValidators } from '../../../../../src/plugins/es_ui_shared/static/forms/helpers'; -export { ERROR_CODE } from '../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; +export type { ERROR_CODE } from '../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; diff --git a/x-pack/plugins/cases/public/components/__mock__/form.ts b/x-pack/plugins/cases/public/components/__mock__/form.ts index aa40ea0421b4c3..a2bd59d5dd5413 100644 --- a/x-pack/plugins/cases/public/components/__mock__/form.ts +++ b/x-pack/plugins/cases/public/components/__mock__/form.ts @@ -36,7 +36,7 @@ export const mockFormHook = { __readFieldConfigFromSchema: jest.fn(), }; -export const getFormMock = (sampleData: any) => ({ +export const getFormMock = (sampleData: unknown) => ({ ...mockFormHook, submit: () => Promise.resolve({ diff --git a/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx index 59efcf868c9eec..2b43fbf63095e4 100644 --- a/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTable } from '@elastic/eui'; import styled from 'styled-components'; import { Case, SubCase } from '../../containers/types'; import { CasesColumns } from './columns'; @@ -14,7 +14,7 @@ import { AssociationType } from '../../../common'; type ExpandedRowMap = Record | {}; -const EuiBasicTable: any = _EuiBasicTable; +// @ts-expect-error TS2769 const BasicTable = styled(EuiBasicTable)` thead { display: none; diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx index a387c5eae38340..f55c871f4922a6 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -303,7 +303,10 @@ describe('AllCasesGeneric', () => { await waitFor(() => { result.current.map( - (i, key) => i.name != null && !i.hasOwnProperty('actions') && checkIt(`${i.name}`, key) + (i, key) => + i.name != null && + !Object.prototype.hasOwnProperty.call(i, 'actions') && + checkIt(`${i.name}`, key) ); }); }); @@ -378,7 +381,9 @@ describe('AllCasesGeneric', () => { }) ); await waitFor(() => { - result.current.map((i) => i.name != null && !i.hasOwnProperty('actions')); + result.current.map( + (i) => i.name != null && !Object.prototype.hasOwnProperty.call(i, 'actions') + ); expect(wrapper.find(`a[data-test-subj="case-details-link"]`).exists()).toBeFalsy(); }); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx index 4e8334ebceec09..cbfb24f18c97e0 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx @@ -87,5 +87,8 @@ export const AllCasesSelectorModal: React.FC = React ); }); + +AllCasesSelectorModal.displayName = 'AllCasesSelectorModal'; + // eslint-disable-next-line import/no-default-export export { AllCasesSelectorModal as default }; diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx index 876007494d276a..3f80fc8f0d7c47 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx @@ -10,7 +10,7 @@ import { EuiEmptyPrompt, EuiLoadingContent, EuiTableSelectionType, - EuiBasicTable as _EuiBasicTable, + EuiBasicTable, EuiBasicTableProps, } from '@elastic/eui'; import classnames from 'classnames'; @@ -40,12 +40,12 @@ interface CasesTableProps { selection: EuiTableSelectionType; showActions: boolean; sorting: EuiBasicTableProps['sorting']; - tableRef: MutableRefObject<_EuiBasicTable | undefined>; + tableRef: MutableRefObject; tableRowProps: EuiBasicTableProps['rowProps']; userCanCrud: boolean; } -const EuiBasicTable: any = _EuiBasicTable; +// @ts-expect-error TS2769 const BasicTable = styled(EuiBasicTable)` ${({ theme }) => ` .euiTableRow-isExpandedRow.euiTableRow-isSelectable .euiTableCellContent { diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index 1fed1d90689be8..990d44584cf055 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -47,6 +47,7 @@ describe('ConfigureCases', () => { iconClass: 'logoSecurity', }); }); + beforeEach(() => { useActionTypesMock.mockImplementation(() => useActionTypesResponse); }); @@ -451,150 +452,149 @@ describe('ConfigureCases', () => { ).toBe('Update My Connector 2'); }); }); -}); -// Failing: See https://github.com/elastic/kibana/issues/115366 -describe.skip('closure options', () => { - let wrapper: ReactWrapper; - let persistCaseConfigure: jest.Mock; + describe('closure options', () => { + let wrapper: ReactWrapper; + let persistCaseConfigure: jest.Mock; - beforeEach(() => { - persistCaseConfigure = jest.fn(); - useCaseConfigureMock.mockImplementation(() => ({ - ...useCaseConfigureResponse, - mapping: null, - closureType: 'close-by-user', - connector: { - id: 'servicenow-1', - name: 'My connector', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, - currentConfiguration: { + beforeEach(() => { + persistCaseConfigure = jest.fn(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: null, + closureType: 'close-by-user', connector: { - id: 'My connector', + id: 'servicenow-1', name: 'My connector', - type: ConnectorTypes.jira, + type: ConnectorTypes.serviceNowITSM, fields: null, }, - closureType: 'close-by-user', - }, - persistCaseConfigure, - })); - useConnectorsMock.mockImplementation(() => useConnectorsResponse); - useGetUrlSearchMock.mockImplementation(() => searchURL); + currentConfiguration: { + connector: { + id: 'My connector', + name: 'My connector', + type: ConnectorTypes.jira, + fields: null, + }, + closureType: 'close-by-user', + }, + persistCaseConfigure, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useGetUrlSearchMock.mockImplementation(() => searchURL); - wrapper = mount(, { - wrappingComponent: TestProviders, + wrapper = mount(, { + wrappingComponent: TestProviders, + }); }); - }); - test('it submits the configuration correctly when changing closure type', () => { - wrapper.find('input[id="close-by-pushing"]').simulate('change'); - wrapper.update(); + test('it submits the configuration correctly when changing closure type', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); - expect(persistCaseConfigure).toHaveBeenCalled(); - expect(persistCaseConfigure).toHaveBeenCalledWith({ - connector: { - id: 'servicenow-1', - name: 'My connector', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, - closureType: 'close-by-pushing', + expect(persistCaseConfigure).toHaveBeenCalled(); + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connector: { + id: 'servicenow-1', + name: 'My connector', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + closureType: 'close-by-pushing', + }); }); }); -}); -describe('user interactions', () => { - beforeEach(() => { - useCaseConfigureMock.mockImplementation(() => ({ - ...useCaseConfigureResponse, - mapping: null, - closureType: 'close-by-user', - connector: { - id: 'resilient-2', - name: 'unchanged', - type: ConnectorTypes.resilient, - fields: null, - }, - currentConfiguration: { + describe('user interactions', () => { + beforeEach(() => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: null, + closureType: 'close-by-user', connector: { id: 'resilient-2', name: 'unchanged', - type: ConnectorTypes.serviceNowITSM, + type: ConnectorTypes.resilient, fields: null, }, - closureType: 'close-by-user', - }, - })); - useConnectorsMock.mockImplementation(() => useConnectorsResponse); - useGetUrlSearchMock.mockImplementation(() => searchURL); - }); - - test('it show the add flyout when pressing the add connector button', async () => { - const wrapper = mount(, { - wrappingComponent: TestProviders, + currentConfiguration: { + connector: { + id: 'resilient-2', + name: 'unchanged', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + closureType: 'close-by-user', + }, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useGetUrlSearchMock.mockImplementation(() => searchURL); }); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.find('button[data-test-subj="dropdown-connector-add-connector"]').simulate('click'); + test('it show the add flyout when pressing the add connector button', async () => { + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('ConnectorAddFlyout').exists()).toBe(true); - expect(wrapper.find('ConnectorAddFlyout').prop('actionTypes')).toEqual([ - expect.objectContaining({ - id: '.servicenow', - }), - expect.objectContaining({ - id: '.jira', - }), - expect.objectContaining({ - id: '.resilient', - }), - expect.objectContaining({ - id: '.servicenow-sir', - }), - ]); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find('button[data-test-subj="dropdown-connector-add-connector"]').simulate('click'); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('ConnectorAddFlyout').exists()).toBe(true); + expect(wrapper.find('ConnectorAddFlyout').prop('actionTypes')).toEqual([ + expect.objectContaining({ + id: '.servicenow', + }), + expect.objectContaining({ + id: '.jira', + }), + expect.objectContaining({ + id: '.resilient', + }), + expect.objectContaining({ + id: '.servicenow-sir', + }), + ]); + }); }); - }); - test('it show the edit flyout when pressing the update connector button', async () => { - const actionType = actionTypeRegistryMock.createMockActionTypeModel({ - id: '.resilient', - validateConnector: () => { - return Promise.resolve({}); - }, - validateParams: () => { - const validationResult = { errors: {} }; - return Promise.resolve(validationResult); - }, - actionConnectorFields: null, - }); - - useKibanaMock().services.triggersActionsUi.actionTypeRegistry.get = jest - .fn() - .mockReturnValue(actionType); - useKibanaMock().services.triggersActionsUi.actionTypeRegistry.has = jest - .fn() - .mockReturnValue(true); - - const wrapper = mount(, { - wrappingComponent: TestProviders, - }); - wrapper - .find('button[data-test-subj="case-configure-update-selected-connector-button"]') - .simulate('click'); - - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('ConnectorEditFlyout').exists()).toBe(true); - expect(wrapper.find('ConnectorEditFlyout').prop('initialConnector')).toEqual(connectors[1]); - }); + test('it show the edit flyout when pressing the update connector button', async () => { + const actionType = actionTypeRegistryMock.createMockActionTypeModel({ + id: '.resilient', + validateConnector: () => { + return Promise.resolve({}); + }, + validateParams: () => { + const validationResult = { errors: {} }; + return Promise.resolve(validationResult); + }, + actionConnectorFields: null, + }); + + useKibanaMock().services.triggersActionsUi.actionTypeRegistry.get = jest + .fn() + .mockReturnValue(actionType); + useKibanaMock().services.triggersActionsUi.actionTypeRegistry.has = jest + .fn() + .mockReturnValue(true); + + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .simulate('click'); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('ConnectorEditFlyout').exists()).toBe(true); + expect(wrapper.find('ConnectorEditFlyout').prop('initialConnector')).toEqual(connectors[1]); + }); - expect( - wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() - ).toBeFalsy(); + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index 788c6eeb61b32f..cf7962f08db933 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -242,5 +242,7 @@ export const ConfigureCases: React.FC = React.memo((props) ); }); +ConfigureCases.displayName = 'ConfigureCases'; + // eslint-disable-next-line import/no-default-export export default ConfigureCases; diff --git a/x-pack/plugins/cases/public/components/connectors/connectors_registry.ts b/x-pack/plugins/cases/public/components/connectors/connectors_registry.ts index 2e02cb290c3c84..81747333013481 100644 --- a/x-pack/plugins/cases/public/components/connectors/connectors_registry.ts +++ b/x-pack/plugins/cases/public/components/connectors/connectors_registry.ts @@ -9,8 +9,25 @@ import { i18n } from '@kbn/i18n'; import { CaseConnector, CaseConnectorsRegistry } from './types'; export const createCaseConnectorsRegistry = (): CaseConnectorsRegistry => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const connectors: Map> = new Map(); + function assertConnectorExists( + connector: CaseConnector | undefined | null, + id: string + ): asserts connector { + if (!connector) { + throw new Error( + i18n.translate('xpack.cases.connecors.get.missingCaseConnectorErrorMessage', { + defaultMessage: 'Object type "{id}" is not registered.', + values: { + id, + }, + }) + ); + } + } + const registry: CaseConnectorsRegistry = { has: (id: string) => connectors.has(id), register: (connector: CaseConnector) => { @@ -28,17 +45,9 @@ export const createCaseConnectorsRegistry = (): CaseConnectorsRegistry => { connectors.set(connector.id, connector); }, get: (id: string): CaseConnector => { - if (!connectors.has(id)) { - throw new Error( - i18n.translate('xpack.cases.connecors.get.missingCaseConnectorErrorMessage', { - defaultMessage: 'Object type "{id}" is not registered.', - values: { - id, - }, - }) - ); - } - return connectors.get(id)!; + const connector = connectors.get(id); + assertConnectorExists(connector, id); + return connector; }, list: () => { return Array.from(connectors).map(([id, connector]) => connector); diff --git a/x-pack/plugins/cases/public/components/connectors/jira/api.test.ts b/x-pack/plugins/cases/public/components/connectors/jira/api.test.ts index af4883ab4ba96e..593e5fa5497f54 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/api.test.ts +++ b/x-pack/plugins/cases/public/components/connectors/jira/api.test.ts @@ -74,13 +74,33 @@ const fieldsResponse = { }, }; -const issueResponse = { +const issue = { id: '10267', key: 'RJ-107', - fields: { summary: 'Test title' }, + title: 'test title', +}; + +const issueResponse = { + status: 'ok' as const, + connector_id: '1', + data: issue, +}; + +const issuesResponse = { + ...issueResponse, + data: [issue], }; -const issuesResponse = [issueResponse]; +const camelCasedIssueResponse = { + status: 'ok' as const, + actionId: '1', + data: issue, +}; + +const camelCasedIssuesResponse = { + ...camelCasedIssueResponse, + data: [issue], +}; describe('Jira API', () => { const http = httpServiceMock.createStartContract(); @@ -131,7 +151,7 @@ describe('Jira API', () => { title: 'test issue', }); - expect(res).toEqual(issuesResponse); + expect(res).toEqual(camelCasedIssuesResponse); expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { body: '{"params":{"subAction":"issues","subActionParams":{"title":"test issue"}}}', signal: abortCtrl.signal, @@ -142,7 +162,7 @@ describe('Jira API', () => { describe('getIssue', () => { test('should call get fields API', async () => { const abortCtrl = new AbortController(); - http.post.mockResolvedValueOnce(issuesResponse); + http.post.mockResolvedValueOnce(issueResponse); const res = await getIssue({ http, signal: abortCtrl.signal, @@ -150,7 +170,7 @@ describe('Jira API', () => { id: 'RJ-107', }); - expect(res).toEqual(issuesResponse); + expect(res).toEqual(camelCasedIssueResponse); expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { body: '{"params":{"subAction":"issue","subActionParams":{"id":"RJ-107"}}}', signal: abortCtrl.signal, diff --git a/x-pack/plugins/cases/public/components/connectors/jira/api.ts b/x-pack/plugins/cases/public/components/connectors/jira/api.ts index 109d8a6794c540..b4bbec156f04df 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/api.ts +++ b/x-pack/plugins/cases/public/components/connectors/jira/api.ts @@ -7,7 +7,11 @@ import { HttpSetup } from 'kibana/public'; import { ActionTypeExecutorResult } from '../../../../../actions/common'; -import { getExecuteConnectorUrl } from '../../../../common/utils/connectors_api'; +import { getExecuteConnectorUrl } from '../../../../common/utils'; +import { + ConnectorExecutorResult, + rewriteResponseToCamelCase, +} from '../rewrite_response_to_camel_case'; import { IssueTypes, Fields, Issues, Issue } from './types'; export interface GetIssueTypesProps { @@ -17,12 +21,17 @@ export interface GetIssueTypesProps { } export async function getIssueTypes({ http, signal, connectorId }: GetIssueTypesProps) { - return http.post>(getExecuteConnectorUrl(connectorId), { - body: JSON.stringify({ - params: { subAction: 'issueTypes', subActionParams: {} }, - }), - signal, - }); + const res = await http.post>( + getExecuteConnectorUrl(connectorId), + { + body: JSON.stringify({ + params: { subAction: 'issueTypes', subActionParams: {} }, + }), + signal, + } + ); + + return rewriteResponseToCamelCase(res); } export interface GetFieldsByIssueTypeProps { @@ -38,12 +47,16 @@ export async function getFieldsByIssueType({ connectorId, id, }: GetFieldsByIssueTypeProps): Promise> { - return http.post(getExecuteConnectorUrl(connectorId), { - body: JSON.stringify({ - params: { subAction: 'fieldsByIssueType', subActionParams: { id } }, - }), - signal, - }); + const res = await http.post>( + getExecuteConnectorUrl(connectorId), + { + body: JSON.stringify({ + params: { subAction: 'fieldsByIssueType', subActionParams: { id } }, + }), + signal, + } + ); + return rewriteResponseToCamelCase(res); } export interface GetIssuesTypeProps { @@ -59,12 +72,16 @@ export async function getIssues({ connectorId, title, }: GetIssuesTypeProps): Promise> { - return http.post(getExecuteConnectorUrl(connectorId), { - body: JSON.stringify({ - params: { subAction: 'issues', subActionParams: { title } }, - }), - signal, - }); + const res = await http.post>( + getExecuteConnectorUrl(connectorId), + { + body: JSON.stringify({ + params: { subAction: 'issues', subActionParams: { title } }, + }), + signal, + } + ); + return rewriteResponseToCamelCase(res); } export interface GetIssueTypeProps { @@ -80,10 +97,11 @@ export async function getIssue({ connectorId, id, }: GetIssueTypeProps): Promise> { - return http.post(getExecuteConnectorUrl(connectorId), { + const res = await http.post>(getExecuteConnectorUrl(connectorId), { body: JSON.stringify({ params: { subAction: 'issue', subActionParams: { id } }, }), signal, }); + return rewriteResponseToCamelCase(res); } diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx index 686df1022a0d79..d762c9d3aaf20f 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx @@ -6,7 +6,7 @@ */ import { useState, useEffect, useRef } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../common'; import { getFieldsByIssueType } from './api'; import { Fields } from './types'; @@ -14,10 +14,7 @@ import * as i18n from './translations'; interface Props { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; issueType: string | null; connector?: ActionConnector; } diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx index 0d7073f3bf2d48..6f409f1ddef8df 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx @@ -6,7 +6,7 @@ */ import { useState, useEffect, useRef } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../common'; import { getIssueTypes } from './api'; import { IssueTypes } from './types'; @@ -14,10 +14,7 @@ import * as i18n from './translations'; interface Props { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; connector?: ActionConnector; handleIssueType: (options: Array<{ value: string; text: string }>) => void; } diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/api.ts b/x-pack/plugins/cases/public/components/connectors/resilient/api.ts index 301d29866d3027..6d803b6fbe5421 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/api.ts +++ b/x-pack/plugins/cases/public/components/connectors/resilient/api.ts @@ -6,8 +6,11 @@ */ import { HttpSetup } from 'kibana/public'; -import { ActionTypeExecutorResult } from '../../../../../actions/common'; import { getExecuteConnectorUrl } from '../../../../common/utils/connectors_api'; +import { + ConnectorExecutorResult, + rewriteResponseToCamelCase, +} from '../rewrite_response_to_camel_case'; import { ResilientIncidentTypes, ResilientSeverity } from './types'; export const BASE_ACTION_API_PATH = '/api/actions'; @@ -19,7 +22,7 @@ export interface Props { } export async function getIncidentTypes({ http, signal, connectorId }: Props) { - return http.post>( + const res = await http.post>( getExecuteConnectorUrl(connectorId), { body: JSON.stringify({ @@ -28,10 +31,12 @@ export async function getIncidentTypes({ http, signal, connectorId }: Props) { signal, } ); + + return rewriteResponseToCamelCase(res); } export async function getSeverity({ http, signal, connectorId }: Props) { - return http.post>( + const res = await http.post>( getExecuteConnectorUrl(connectorId), { body: JSON.stringify({ @@ -40,4 +45,6 @@ export async function getSeverity({ http, signal, connectorId }: Props) { signal, } ); + + return rewriteResponseToCamelCase(res); } diff --git a/x-pack/plugins/cases/public/components/connectors/rewrite_response_to_camel_case.test.ts b/x-pack/plugins/cases/public/components/connectors/rewrite_response_to_camel_case.test.ts new file mode 100644 index 00000000000000..28f901076759be --- /dev/null +++ b/x-pack/plugins/cases/public/components/connectors/rewrite_response_to_camel_case.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ConnectorExecutorResult, + rewriteResponseToCamelCase, +} from './rewrite_response_to_camel_case'; + +const responseWithSnakeCasedFields: ConnectorExecutorResult<{}> = { + service_message: 'oh noooooo', + connector_id: '1213', + data: {}, + status: 'ok', +}; + +describe('rewriteResponseToCamelCase works correctly', () => { + it('correctly transforms snake case to camel case for ActionTypeExecuteResults', () => { + const camelCasedData = rewriteResponseToCamelCase(responseWithSnakeCasedFields); + + expect(camelCasedData).toEqual({ + serviceMessage: 'oh noooooo', + actionId: '1213', + data: {}, + status: 'ok', + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/connectors/rewrite_response_to_camel_case.ts b/x-pack/plugins/cases/public/components/connectors/rewrite_response_to_camel_case.ts new file mode 100644 index 00000000000000..e73488257d9d2f --- /dev/null +++ b/x-pack/plugins/cases/public/components/connectors/rewrite_response_to_camel_case.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ActionTypeExecutorResult, RewriteResponseCase } from '../../../../actions/common'; + +export type ConnectorExecutorResult = ReturnType< + RewriteResponseCase> +>; + +export const rewriteResponseToCamelCase = ({ + connector_id: actionId, + service_message: serviceMessage, + ...data +}: ConnectorExecutorResult): ActionTypeExecutorResult => ({ + ...data, + actionId, + ...(serviceMessage && { serviceMessage }), +}); diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/api.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/api.ts index 3d9211caa9b9a1..de3ea1f16c3590 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/api.ts +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/api.ts @@ -6,8 +6,11 @@ */ import { HttpSetup } from 'kibana/public'; -import { ActionTypeExecutorResult } from '../../../../../actions/common'; import { getExecuteConnectorUrl } from '../../../../common/utils/connectors_api'; +import { + ConnectorExecutorResult, + rewriteResponseToCamelCase, +} from '../rewrite_response_to_camel_case'; import { Choice } from './types'; export const BASE_ACTION_API_PATH = '/api/actions'; @@ -20,10 +23,14 @@ export interface GetChoicesProps { } export async function getChoices({ http, signal, connectorId, fields }: GetChoicesProps) { - return http.post>(getExecuteConnectorUrl(connectorId), { - body: JSON.stringify({ - params: { subAction: 'getChoices', subActionParams: { fields } }, - }), - signal, - }); + const res = await http.post>( + getExecuteConnectorUrl(connectorId), + { + body: JSON.stringify({ + params: { subAction: 'getChoices', subActionParams: { fields } }, + }), + signal, + } + ); + return rewriteResponseToCamelCase(res); } diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx index 4edf740a60011f..2c6181dd08eb1c 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx @@ -6,7 +6,7 @@ */ import { useState, useEffect, useRef } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../common'; import { getChoices } from './api'; import { Choice } from './types'; @@ -14,10 +14,7 @@ import * as i18n from './translations'; export interface UseGetChoicesProps { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; connector?: ActionConnector; fields: string[]; onSuccess?: (choices: Choice[]) => void; diff --git a/x-pack/plugins/cases/public/components/connectors/types.ts b/x-pack/plugins/cases/public/components/connectors/types.ts index 5bbd77c7909012..8bc978152b7966 100644 --- a/x-pack/plugins/cases/public/components/connectors/types.ts +++ b/x-pack/plugins/cases/public/components/connectors/types.ts @@ -15,7 +15,7 @@ import { } from '../../../common'; import { CaseActionConnector } from '../types'; -export { ThirdPartyField as AllThirdPartyFields } from '../../../common'; +export type { ThirdPartyField as AllThirdPartyFields } from '../../../common'; export interface ThirdPartyField { label: string; diff --git a/x-pack/plugins/cases/public/components/create/description.tsx b/x-pack/plugins/cases/public/components/create/description.tsx index e1bd563a3d7986..c1545a42df3f59 100644 --- a/x-pack/plugins/cases/public/components/create/description.tsx +++ b/x-pack/plugins/cases/public/components/create/description.tsx @@ -21,7 +21,7 @@ const DescriptionComponent: React.FC = ({ isLoading }) => { useLensDraftComment(); const { setFieldValue } = useFormContext(); const [{ title, tags }] = useFormData({ watch: ['title', 'tags'] }); - const editorRef = useRef>(); + const editorRef = useRef>(); useEffect(() => { if (draftComment?.commentId === fieldName && editorRef.current) { diff --git a/x-pack/plugins/cases/public/components/create/index.tsx b/x-pack/plugins/cases/public/components/create/index.tsx index d3eaba1ea0bc44..39f10a89290d8f 100644 --- a/x-pack/plugins/cases/public/components/create/index.tsx +++ b/x-pack/plugins/cases/public/components/create/index.tsx @@ -98,5 +98,8 @@ export const CreateCase: React.FC = React.memo((props) => ( )); + +CreateCase.displayName = 'CreateCase'; + // eslint-disable-next-line import/no-default-export export { CreateCase as default }; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 4bf25b23403e16..e2067d75e843e9 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -96,4 +96,6 @@ const MarkdownEditorComponent = forwardRef + <> {this.renderSearchBar()} {this.renderListing()} - + ); } @@ -481,16 +481,23 @@ export class SavedObjectFinderUi extends React.Component< {items.map((item) => { const currentSavedObjectMetaData = savedObjectMetaData.find( (metaData) => metaData.type === item.type - )!; + ); + + if (currentSavedObjectMetaData == null) { + return null; + } + const fullName = currentSavedObjectMetaData.getTooltipForSavedObject ? currentSavedObjectMetaData.getTooltipForSavedObject(item.savedObject) - : `${item.title} (${currentSavedObjectMetaData!.name})`; + : `${item.title} (${currentSavedObjectMetaData.name})`; + const iconType = ( currentSavedObjectMetaData || ({ getIconForSavedObject: () => 'document', } as Pick, 'getIconForSavedObject'>) ).getIconForSavedObject(item.savedObject); + return ( => { + const MarkdownLinkProcessingComponent: React.FC = memo((props) => ( + + )); + + MarkdownLinkProcessingComponent.displayName = 'MarkdownLinkProcessingComponent'; + + return MarkdownLinkProcessingComponent; +}; + const MarkdownRendererComponent: React.FC = ({ children, disableLinks }) => { const { processingPlugins, parsingPlugins } = usePlugins(); - const MarkdownLinkProcessingComponent: React.FC = useMemo( - () => (props) => , - [disableLinks] - ); // Deep clone of the processing plugins to prevent affecting the markdown editor. const processingPluginList = cloneDeep(processingPlugins); // This line of code is TS-compatible and it will break if [1][1] change in the future. - processingPluginList[1][1].components.a = MarkdownLinkProcessingComponent; + processingPluginList[1][1].components.a = useMemo( + () => withDisabledLinks(disableLinks), + [disableLinks] + ); return ( = React.memo((props) => { ); }); +RecentCases.displayName = 'RecentCases'; + // eslint-disable-next-line import/no-default-export export { RecentCases as default }; diff --git a/x-pack/plugins/cases/public/components/types.ts b/x-pack/plugins/cases/public/components/types.ts index 07ab5814b082b9..71c846eb922d7e 100644 --- a/x-pack/plugins/cases/public/components/types.ts +++ b/x-pack/plugins/cases/public/components/types.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { CaseActionConnector } from '../../common'; +export type { CaseActionConnector } from '../../common'; diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx index 2eb44f91190c61..2419ac0d048e9e 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx @@ -396,6 +396,8 @@ const ActionIcon = React.memo<{ ); }); +ActionIcon.displayName = 'ActionIcon'; + export const getActionAttachment = ({ comment, userCanCrud, diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index 92640a34548e8b..95c4f76eae0a20 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -12,8 +12,10 @@ import { EuiCommentList, EuiCommentProps, } from '@elastic/eui'; +import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; + import classNames from 'classnames'; -import { isEmpty } from 'lodash'; +import { get, isEmpty } from 'lodash'; import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; @@ -23,7 +25,7 @@ import * as i18n from './translations'; import { useUpdateComment } from '../../containers/use_update_comment'; import { useCurrentUser } from '../../common/lib/kibana'; -import { AddComment } from '../add_comment'; +import { AddComment, AddCommentRefObject } from '../add_comment'; import { ActionConnector, ActionsCommentRequestRt, @@ -50,7 +52,7 @@ import { getActionAttachment, } from './helpers'; import { UserActionAvatar } from './user_action_avatar'; -import { UserActionMarkdown } from './user_action_markdown'; +import { UserActionMarkdown, UserActionMarkdownRefObject } from './user_action_markdown'; import { UserActionTimestamp } from './user_action_timestamp'; import { UserActionUsername } from './user_action_username'; import { UserActionContentToolbar } from './user_action_content_toolbar'; @@ -129,6 +131,17 @@ const MyEuiCommentList = styled(EuiCommentList)` const DESCRIPTION_ID = 'description'; const NEW_ID = 'newComment'; +const isAddCommentRef = ( + ref: AddCommentRefObject | UserActionMarkdownRefObject | null | undefined +): ref is AddCommentRefObject => { + const commentRef = ref as AddCommentRefObject; + if (commentRef?.addQuote != null) { + return true; + } + + return false; +}; + export const UserActionTree = React.memo( ({ caseServices, @@ -165,7 +178,9 @@ export const UserActionTree = React.memo( const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); const [manageMarkdownEditIds, setManageMarkdownEditIds] = useState([]); - const commentRefs = useRef>({}); + const commentRefs = useRef< + Record + >({}); const { clearDraftComment, draftComment, hasIncomingLensState, openLensModal } = useLensDraftComment(); @@ -226,8 +241,9 @@ export const UserActionTree = React.memo( const handleManageQuote = useCallback( (quote: string) => { - if (commentRefs.current[NEW_ID]) { - commentRefs.current[NEW_ID].addQuote(quote); + const ref = commentRefs?.current[NEW_ID]; + if (isAddCommentRef(ref)) { + ref.addQuote(quote); } handleOutlineComment('add-comment'); @@ -335,6 +351,8 @@ export const UserActionTree = React.memo( const userActions: EuiCommentProps[] = useMemo( () => caseUserActions.reduce( + // TODO: Decrease complexity. https://github.com/elastic/kibana/issues/115730 + // eslint-disable-next-line complexity (comments, action, index) => { // Comment creation if (action.commentId != null && action.action === 'create') { @@ -421,9 +439,15 @@ export const UserActionTree = React.memo( } const ruleId = - comment?.rule?.id ?? manualAlertsData[alertId]?.signal?.rule?.id?.[0] ?? null; + comment?.rule?.id ?? + manualAlertsData[alertId]?.signal?.rule?.id?.[0] ?? + get(manualAlertsData[alertId], ALERT_RULE_UUID)[0] ?? + null; const ruleName = - comment?.rule?.name ?? manualAlertsData[alertId]?.signal?.rule?.name?.[0] ?? null; + comment?.rule?.name ?? + manualAlertsData[alertId]?.signal?.rule?.name?.[0] ?? + get(manualAlertsData[alertId], ALERT_RULE_NAME)[0] ?? + null; return [ ...comments, @@ -656,15 +680,12 @@ export const UserActionTree = React.memo( return prevManageMarkdownEditIds; }); - if ( - commentRefs.current && - commentRefs.current[draftComment.commentId] && - commentRefs.current[draftComment.commentId].editor?.textarea && - commentRefs.current[draftComment.commentId].editor?.toolbar - ) { - commentRefs.current[draftComment.commentId].setComment(draftComment.comment); + const ref = commentRefs?.current?.[draftComment.commentId]; + + if (isAddCommentRef(ref) && ref.editor?.textarea) { + ref.setComment(draftComment.comment); if (hasIncomingLensState) { - openLensModal({ editorRef: commentRefs.current[draftComment.commentId].editor }); + openLensModal({ editorRef: ref.editor }); } else { clearDraftComment(); } diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx index f7a6932b358563..93212d2b11016a 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx @@ -26,7 +26,7 @@ interface UserActionMarkdownProps { onSaveContent: (content: string) => void; } -interface UserActionMarkdownRefObject { +export interface UserActionMarkdownRefObject { setComment: (newComment: string) => void; } diff --git a/x-pack/plugins/cases/public/components/utils.test.ts b/x-pack/plugins/cases/public/components/utils.test.ts index 496dc8d8c80663..e3cc753e757466 100644 --- a/x-pack/plugins/cases/public/components/utils.test.ts +++ b/x-pack/plugins/cases/public/components/utils.test.ts @@ -7,7 +7,7 @@ import { actionTypeRegistryMock } from '../../../triggers_actions_ui/public/application/action_type_registry.mock'; import { triggersActionsUiMock } from '../../../triggers_actions_ui/public/mocks'; -import { getConnectorIcon } from './utils'; +import { getConnectorIcon, isDeprecatedConnector } from './utils'; describe('Utils', () => { describe('getConnectorIcon', () => { @@ -37,4 +37,51 @@ describe('Utils', () => { expect(getConnectorIcon(mockTriggersActionsUiService, '.not-registered')).toBe(''); }); }); + + describe('isDeprecatedConnector', () => { + const connector = { + id: 'test', + actionTypeId: '.webhook', + name: 'Test', + config: { usesTableApi: false }, + secrets: {}, + isPreconfigured: false, + }; + + it('returns false if the connector is not defined', () => { + expect(isDeprecatedConnector()).toBe(false); + }); + + it('returns false if the connector is not ITSM or SecOps', () => { + expect(isDeprecatedConnector(connector)).toBe(false); + }); + + it('returns false if the connector is .servicenow and the usesTableApi=false', () => { + expect(isDeprecatedConnector({ ...connector, actionTypeId: '.servicenow' })).toBe(false); + }); + + it('returns false if the connector is .servicenow-sir and the usesTableApi=false', () => { + expect(isDeprecatedConnector({ ...connector, actionTypeId: '.servicenow-sir' })).toBe(false); + }); + + it('returns true if the connector is .servicenow and the usesTableApi=true', () => { + expect( + isDeprecatedConnector({ + ...connector, + actionTypeId: '.servicenow', + config: { usesTableApi: true }, + }) + ).toBe(true); + }); + + it('returns true if the connector is .servicenow-sir and the usesTableApi=true', () => { + expect( + isDeprecatedConnector({ + ...connector, + actionTypeId: '.servicenow-sir', + config: { usesTableApi: true }, + }) + ).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/utils.ts b/x-pack/plugins/cases/public/components/utils.ts index 74137789958a46..82d2682e65fadf 100644 --- a/x-pack/plugins/cases/public/components/utils.ts +++ b/x-pack/plugins/cases/public/components/utils.ts @@ -12,11 +12,6 @@ import { StartPlugins } from '../types'; import { connectorValidator as swimlaneConnectorValidator } from './connectors/swimlane/validator'; import { connectorValidator as servicenowConnectorValidator } from './connectors/servicenow/validator'; import { CaseActionConnector } from './types'; -import { - ENABLE_NEW_SN_ITSM_CONNECTOR, - ENABLE_NEW_SN_SIR_CONNECTOR, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../actions/server/constants/connectors'; export const getConnectorById = ( id: string, @@ -80,27 +75,19 @@ export const getConnectorIcon = ( // TODO: Remove when the applications are certified export const isDeprecatedConnector = (connector?: CaseActionConnector): boolean => { if (connector == null) { - return true; + return false; } - if (!ENABLE_NEW_SN_ITSM_CONNECTOR && connector.actionTypeId === '.servicenow') { - return true; + if (connector.actionTypeId === '.servicenow' || connector.actionTypeId === '.servicenow-sir') { + /** + * Connector's prior to the Elastic ServiceNow application + * use the Table API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_TableAPI) + * Connectors after the Elastic ServiceNow application use the + * Import Set API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_ImportSetAPI) + * A ServiceNow connector is considered deprecated if it uses the Table API. + */ + return !!connector.config.usesTableApi; } - if (!ENABLE_NEW_SN_SIR_CONNECTOR && connector.actionTypeId === '.servicenow-sir') { - return true; - } - - /** - * Connector's prior to the Elastic ServiceNow application - * use the Table API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_TableAPI) - * Connectors after the Elastic ServiceNow application use the - * Import Set API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_ImportSetAPI) - * A ServiceNow connector is considered deprecated if it uses the Table API. - * - * All other connectors do not have the usesTableApi config property - * so the function will always return false for them. - */ - - return !!connector.config.usesTableApi; + return false; }; diff --git a/x-pack/plugins/cases/public/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts index 75e8c8f58705dd..14f617b19db52b 100644 --- a/x-pack/plugins/cases/public/containers/api.ts +++ b/x-pack/plugins/cases/public/containers/api.ts @@ -87,7 +87,7 @@ export const resolveCase = async ( signal: AbortSignal ): Promise => { const response = await KibanaServices.get().http.fetch( - getCaseDetailsUrl(caseId) + '/resolve', + `${getCaseDetailsUrl(caseId)}/resolve`, { method: 'GET', query: { diff --git a/x-pack/plugins/cases/public/containers/configure/api.ts b/x-pack/plugins/cases/public/containers/configure/api.ts index f7d0cf1ad9aefb..1fd358e4dae9dc 100644 --- a/x-pack/plugins/cases/public/containers/configure/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.ts @@ -30,10 +30,10 @@ import { import { CaseConfigure } from './types'; export const fetchConnectors = async ({ signal }: ApiProps): Promise => { - const response = await KibanaServices.get().http.fetch(`${CASE_CONFIGURE_CONNECTORS_URL}/_find`, { - method: 'GET', - signal, - }); + const response = await KibanaServices.get().http.fetch( + `${CASE_CONFIGURE_CONNECTORS_URL}/_find`, + { method: 'GET', signal } + ); return response; }; @@ -97,10 +97,10 @@ export const patchCaseConfigure = async ( }; export const fetchActionTypes = async ({ signal }: ApiProps): Promise => { - const response = await KibanaServices.get().http.fetch(getAllConnectorTypesUrl(), { - method: 'GET', - signal, - }); + const response = await KibanaServices.get().http.fetch( + getAllConnectorTypesUrl(), + { method: 'GET', signal } + ); return convertArrayToCamelCase(response) as ActionTypeConnector[]; }; diff --git a/x-pack/plugins/cases/public/containers/configure/types.ts b/x-pack/plugins/cases/public/containers/configure/types.ts index 61c81a8ce97c1e..5ee09add196bd7 100644 --- a/x-pack/plugins/cases/public/containers/configure/types.ts +++ b/x-pack/plugins/cases/public/containers/configure/types.ts @@ -17,7 +17,7 @@ import { ThirdPartyField, } from '../../../common'; -export { +export type { ActionConnector, ActionTypeConnector, ActionType, diff --git a/x-pack/plugins/cases/public/utils/use_mount_appended.ts b/x-pack/plugins/cases/public/utils/use_mount_appended.ts index d43b0455f47da5..48b71e6dbabfed 100644 --- a/x-pack/plugins/cases/public/utils/use_mount_appended.ts +++ b/x-pack/plugins/cases/public/utils/use_mount_appended.ts @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + // eslint-disable-next-line import/no-extraneous-dependencies import { mount } from 'enzyme'; diff --git a/x-pack/plugins/cases/server/authorization/authorization.ts b/x-pack/plugins/cases/server/authorization/authorization.ts index 3598c5b8956fa4..7a474ff4db402d 100644 --- a/x-pack/plugins/cases/server/authorization/authorization.ts +++ b/x-pack/plugins/cases/server/authorization/authorization.ts @@ -231,8 +231,10 @@ export class Authorization { ? Array.from(featureCaseOwners) : privileges.kibana.reduce((authorizedOwners, { authorized, privilege }) => { if (authorized && requiredPrivileges.has(privilege)) { - const owner = requiredPrivileges.get(privilege)!; - authorizedOwners.push(owner); + const owner = requiredPrivileges.get(privilege); + if (owner) { + authorizedOwners.push(owner); + } } return authorizedOwners; diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts index b84a6bd84c43be..159ff3b41aba91 100644 --- a/x-pack/plugins/cases/server/client/attachments/add.ts +++ b/x-pack/plugins/cases/server/client/attachments/add.ts @@ -263,7 +263,7 @@ async function getCombinedCase({ id, }), ] - : [Promise.reject('case connector feature is disabled')]), + : [Promise.reject(new Error('case connector feature is disabled'))]), ]); if (subCasePromise.status === 'fulfilled') { diff --git a/x-pack/plugins/cases/server/client/index.ts b/x-pack/plugins/cases/server/client/index.ts index 7904e65ca62766..5560d535f971ac 100644 --- a/x-pack/plugins/cases/server/client/index.ts +++ b/x-pack/plugins/cases/server/client/index.ts @@ -7,6 +7,6 @@ export { CasesClient } from './client'; export { CasesClientInternal } from './client_internal'; -export { CasesClientArgs } from './types'; +export type { CasesClientArgs } from './types'; export { createCasesClient } from './client'; export { createCasesClientInternal } from './client_internal'; diff --git a/x-pack/plugins/cases/server/common/error.ts b/x-pack/plugins/cases/server/common/error.ts index 1b53eb9fdb2181..3478131f65537e 100644 --- a/x-pack/plugins/cases/server/common/error.ts +++ b/x-pack/plugins/cases/server/common/error.ts @@ -8,10 +8,14 @@ import { Boom, isBoom } from '@hapi/boom'; import { Logger } from 'src/core/server'; +export interface HTTPError extends Error { + statusCode: number; +} + /** * Helper class for wrapping errors while preserving the original thrown error. */ -class CaseError extends Error { +export class CaseError extends Error { public readonly wrappedError?: Error; constructor(message?: string, originalError?: Error) { super(message); @@ -51,6 +55,13 @@ export function isCaseError(error: unknown): error is CaseError { return error instanceof CaseError; } +/** + * Type guard for determining if an error is an HTTPError + */ +export function isHTTPError(error: unknown): error is HTTPError { + return (error as HTTPError)?.statusCode != null; +} + /** * Create a CaseError that wraps the original thrown error. This also logs the message that will be placed in the CaseError * if the logger was defined. diff --git a/x-pack/plugins/cases/server/connectors/case/schema.ts b/x-pack/plugins/cases/server/connectors/case/schema.ts index 79d3bf62e8a9e7..b8e46fdf5aa8cc 100644 --- a/x-pack/plugins/cases/server/connectors/case/schema.ts +++ b/x-pack/plugins/cases/server/connectors/case/schema.ts @@ -83,6 +83,7 @@ const SwimlaneFieldsSchema = schema.object({ const NoneFieldsSchema = schema.nullable(schema.object({})); +// eslint-disable-next-line @typescript-eslint/no-explicit-any const ReducedConnectorFieldsSchema: { [x: string]: any } = { [ConnectorTypes.jira]: JiraFieldsSchema, [ConnectorTypes.resilient]: ResilientFieldsSchema, diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts index b09272d0a5505d..706b9f2f23ab5c 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts @@ -8,7 +8,7 @@ import { CaseResponse } from '../../../common'; import { format } from './sir_format'; -describe('ITSM formatter', () => { +describe('SIR formatter', () => { const theCase = { id: 'case-id', connector: { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts index 9108408c4d089a..02c9fe629f4f81 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts @@ -45,12 +45,16 @@ export const format: ServiceNowSIRFormat = (theCase, alerts) => { if (fieldsToAdd.length > 0) { sirFields = alerts.reduce>((acc, alert) => { + let temp = {}; fieldsToAdd.forEach((alertField) => { const field = get(alertFieldMapping[alertField].alertPath, alert); + if (field && !manageDuplicate[alertFieldMapping[alertField].sirFieldKey].has(field)) { manageDuplicate[alertFieldMapping[alertField].sirFieldKey].add(field); - acc = { + + temp = { ...acc, + ...temp, [alertFieldMapping[alertField].sirFieldKey]: [ ...acc[alertFieldMapping[alertField].sirFieldKey], field, @@ -58,7 +62,8 @@ export const format: ServiceNowSIRFormat = (theCase, alerts) => { }; } }); - return acc; + + return { ...acc, ...temp }; }, sirFields); } diff --git a/x-pack/plugins/cases/server/connectors/types.ts b/x-pack/plugins/cases/server/connectors/types.ts index a8673c12625803..62b2c8e6f1551a 100644 --- a/x-pack/plugins/cases/server/connectors/types.ts +++ b/x-pack/plugins/cases/server/connectors/types.ts @@ -11,7 +11,7 @@ import { CasesClientGetAlertsResponse } from '../client/alerts/types'; import { CasesClientFactory } from '../client/factory'; import { RegisterActionType } from '../types'; -export { +export type { ContextTypeGeneratedAlertType, CommentSchemaType, ContextTypeAlertSchemaType, diff --git a/x-pack/plugins/cases/server/index.ts b/x-pack/plugins/cases/server/index.ts index 5e433b46b80e5a..57db6e5565fff3 100644 --- a/x-pack/plugins/cases/server/index.ts +++ b/x-pack/plugins/cases/server/index.ts @@ -22,4 +22,4 @@ export const config: PluginConfigDescriptor = { export const plugin = (initializerContext: PluginInitializerContext) => new CasePlugin(initializerContext); -export { PluginStartContract } from './plugin'; +export type { PluginStartContract } from './plugin'; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index bef8d45bd86f66..9bbc7089c033c1 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -126,6 +126,11 @@ export class CasePlugin { }, featuresPluginStart: plugins.features, actionsPluginStart: plugins.actions, + /** + * Lens will be always defined as + * it is declared as required plugin in kibana.json + */ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion lensEmbeddableFactory: this.lensEmbeddableFactory!, }); diff --git a/x-pack/plugins/cases/server/routes/api/utils.test.ts b/x-pack/plugins/cases/server/routes/api/utils.test.ts index 3fce38b27446ef..fd7c038f06bc1c 100644 --- a/x-pack/plugins/cases/server/routes/api/utils.test.ts +++ b/x-pack/plugins/cases/server/routes/api/utils.test.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { wrapError } from './utils'; import { isBoom, boomify } from '@hapi/boom'; +import { HTTPError } from '../../common'; +import { wrapError } from './utils'; describe('Utils', () => { describe('wrapError', () => { @@ -25,7 +26,7 @@ describe('Utils', () => { }); it('it set statusCode to errors status code', () => { - const error = new Error('Something happened') as any; + const error = new Error('Something happened') as HTTPError; error.statusCode = 404; const res = wrapError(error); diff --git a/x-pack/plugins/cases/server/routes/api/utils.ts b/x-pack/plugins/cases/server/routes/api/utils.ts index b2b5417ecae0f2..cb4804aab00549 100644 --- a/x-pack/plugins/cases/server/routes/api/utils.ts +++ b/x-pack/plugins/cases/server/routes/api/utils.ts @@ -9,18 +9,21 @@ import { Boom, boomify, isBoom } from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; -import { isCaseError } from '../../common'; +import { CaseError, isCaseError, HTTPError, isHTTPError } from '../../common'; /** * Transforms an error into the correct format for a kibana response. */ -export function wrapError(error: any): CustomHttpResponseOptions { + +export function wrapError( + error: CaseError | Boom | HTTPError | Error +): CustomHttpResponseOptions { let boom: Boom; if (isCaseError(error)) { boom = error.boomify(); } else { - const options = { statusCode: error.statusCode ?? 500 }; + const options = { statusCode: isHTTPError(error) ? error.statusCode : 500 }; boom = isBoom(error) ? error : boomify(error, options); } diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts index a4f50fbfcde5b2..b0f9c7d2145de7 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts @@ -14,7 +14,8 @@ import { SECURITY_SOLUTION_OWNER } from '../../../common'; export { caseMigrations } from './cases'; export { configureMigrations } from './configuration'; export { userActionsMigrations } from './user_actions'; -export { createCommentsMigrations, CreateCommentsMigrationsDeps } from './comments'; +export type { CreateCommentsMigrationsDeps } from './comments'; +export { createCommentsMigrations } from './comments'; export interface SanitizedCaseOwner { owner: string; diff --git a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts index 3cb013bd2e3fd1..28672160a0737d 100644 --- a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts +++ b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts @@ -4,7 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable no-console */ +/* eslint-disable no-process-exit */ + import yargs from 'yargs'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index 6bb2fb3ee3c560..d8a09fe1baf231 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -48,8 +48,6 @@ function isEmptyAlert(alert: AlertInfo): boolean { } export class AlertService { - constructor() {} - public async updateAlertsStatus({ alerts, scopedClusterClient, logger }: UpdateAlertsStatusArgs) { try { const bucketedAlerts = bucketAlertsByIndexAndStatus(alerts, logger); diff --git a/x-pack/plugins/cases/server/services/index.ts b/x-pack/plugins/cases/server/services/index.ts index f910099c0cc206..a1cb5d8138c400 100644 --- a/x-pack/plugins/cases/server/services/index.ts +++ b/x-pack/plugins/cases/server/services/index.ts @@ -12,7 +12,8 @@ export { CasesService } from './cases'; export { CaseConfigureService } from './configure'; export { CaseUserActionService } from './user_actions'; export { ConnectorMappingsService } from './connector_mappings'; -export { AlertService, AlertServiceContract } from './alerts'; +export type { AlertServiceContract } from './alerts'; +export { AlertService } from './alerts'; export { AttachmentService } from './attachments'; export interface ClientArgs { diff --git a/x-pack/plugins/cloud/public/index.ts b/x-pack/plugins/cloud/public/index.ts index 9f4f6b979c344e..d51def6fa66414 100644 --- a/x-pack/plugins/cloud/public/index.ts +++ b/x-pack/plugins/cloud/public/index.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { CloudPlugin } from './plugin'; -export { CloudSetup, CloudConfigType } from './plugin'; +export type { CloudSetup, CloudConfigType } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new CloudPlugin(initializerContext); } diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts index c1c94375d70638..43659d137a6e0a 100644 --- a/x-pack/plugins/cloud/public/plugin.test.ts +++ b/x-pack/plugins/cloud/public/plugin.test.ts @@ -348,7 +348,7 @@ describe('Cloud Plugin', () => { expect(coreStart.chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { - "euiIconType": "arrowLeft", + "euiIconType": "logoCloud", "href": "https://cloud.elastic.co/abc123", "title": "Manage this deployment", }, @@ -370,7 +370,7 @@ describe('Cloud Plugin', () => { expect(coreStart.chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { - "euiIconType": "arrowLeft", + "euiIconType": "logoCloud", "href": "https://cloud.elastic.co/abc123", "title": "Manage this deployment", }, diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index 64b03acdc3ffd7..e71b145c438edf 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -131,7 +131,7 @@ export class CloudPlugin implements Plugin { title: i18n.translate('xpack.cloud.deploymentLinkLabel', { defaultMessage: 'Manage this deployment', }), - euiIconType: 'arrowLeft', + euiIconType: 'logoCloud', href: getFullCloudUrl(baseUrl, deploymentUrl), }); } diff --git a/x-pack/plugins/cloud/server/index.ts b/x-pack/plugins/cloud/server/index.ts index 8d212bb4e15938..eda2fa0a7324e5 100644 --- a/x-pack/plugins/cloud/server/index.ts +++ b/x-pack/plugins/cloud/server/index.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext } from 'src/core/server'; import { CloudPlugin } from './plugin'; -export { CloudSetup } from './plugin'; +export type { CloudSetup } from './plugin'; export { config } from './config'; export const plugin = (initializerContext: PluginInitializerContext) => { return new CloudPlugin(initializerContext); diff --git a/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/index.ts index dae19cf02e230b..fde3b5b06de2da 100644 --- a/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/index.ts +++ b/x-pack/plugins/dashboard_enhanced/common/drilldowns/dashboard_drilldown/index.ts @@ -7,4 +7,4 @@ export { createExtract, createInject } from './dashboard_drilldown_persistable_state'; export { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants'; -export { DrilldownConfig } from './types'; +export type { DrilldownConfig } from './types'; diff --git a/x-pack/plugins/dashboard_enhanced/public/index.ts b/x-pack/plugins/dashboard_enhanced/public/index.ts index 82b0fc322bb834..545696abb0ac57 100644 --- a/x-pack/plugins/dashboard_enhanced/public/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/index.ts @@ -8,18 +8,18 @@ import { PluginInitializerContext } from 'src/core/public'; import { DashboardEnhancedPlugin } from './plugin'; -export { +export type { SetupContract as DashboardEnhancedSetupContract, SetupDependencies as DashboardEnhancedSetupDependencies, StartContract as DashboardEnhancedStartContract, StartDependencies as DashboardEnhancedStartDependencies, } from './plugin'; -export { - AbstractDashboardDrilldown as DashboardEnhancedAbstractDashboardDrilldown, +export type { AbstractDashboardDrilldownConfig as DashboardEnhancedAbstractDashboardDrilldownConfig, AbstractDashboardDrilldownParams as DashboardEnhancedAbstractDashboardDrilldownParams, } from './services/drilldowns/abstract_dashboard_drilldown'; +export { AbstractDashboardDrilldown as DashboardEnhancedAbstractDashboardDrilldown } from './services/drilldowns/abstract_dashboard_drilldown'; export function plugin(context: PluginInitializerContext) { return new DashboardEnhancedPlugin(context); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/index.ts index ffdce9e91d1564..9215f067fc8510 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/index.ts @@ -5,7 +5,5 @@ * 2.0. */ -export { - CollectConfigContainer, - DashboardDrilldownCollectConfigProps, -} from './collect_config_container'; +export type { DashboardDrilldownCollectConfigProps } from './collect_config_container'; +export { CollectConfigContainer } from './collect_config_container'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/index.ts index 3acea2f76d73cb..3b76303bda388b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -export { - AbstractDashboardDrilldown, - Params as AbstractDashboardDrilldownParams, -} from './abstract_dashboard_drilldown'; -export { Config as AbstractDashboardDrilldownConfig } from './types'; +export type { Params as AbstractDashboardDrilldownParams } from './abstract_dashboard_drilldown'; +export { AbstractDashboardDrilldown } from './abstract_dashboard_drilldown'; +export type { Config as AbstractDashboardDrilldownConfig } from './types'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts index 3a0389ac2b546c..f7d98d7681396e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts @@ -5,8 +5,5 @@ * 2.0. */ -export { - FlyoutCreateDrilldownAction, - OpenFlyoutAddDrilldownParams, - OPEN_FLYOUT_ADD_DRILLDOWN, -} from './flyout_create_drilldown'; +export type { OpenFlyoutAddDrilldownParams } from './flyout_create_drilldown'; +export { FlyoutCreateDrilldownAction, OPEN_FLYOUT_ADD_DRILLDOWN } from './flyout_create_drilldown'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index c150c55120e594..76d61a7367d7e2 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -5,8 +5,5 @@ * 2.0. */ -export { - FlyoutEditDrilldownAction, - FlyoutEditDrilldownParams, - OPEN_FLYOUT_EDIT_DRILLDOWN, -} from './flyout_edit_drilldown'; +export type { FlyoutEditDrilldownParams } from './flyout_edit_drilldown'; +export { FlyoutEditDrilldownAction, OPEN_FLYOUT_EDIT_DRILLDOWN } from './flyout_edit_drilldown'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/index.ts index 423007d548db5d..3a34de6299fc41 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/index.ts @@ -6,7 +6,5 @@ */ export { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants'; -export { - EmbeddableToDashboardDrilldown, - Params as EmbeddableToDashboardDrilldownParams, -} from './embeddable_to_dashboard_drilldown'; +export type { Params as EmbeddableToDashboardDrilldownParams } from './embeddable_to_dashboard_drilldown'; +export { EmbeddableToDashboardDrilldown } from './embeddable_to_dashboard_drilldown'; diff --git a/x-pack/plugins/dashboard_enhanced/server/index.ts b/x-pack/plugins/dashboard_enhanced/server/index.ts index 1f4659c2c781a8..3351cd95479036 100644 --- a/x-pack/plugins/dashboard_enhanced/server/index.ts +++ b/x-pack/plugins/dashboard_enhanced/server/index.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext } from 'src/core/server'; import { DashboardEnhancedPlugin } from './plugin'; -export { +export type { SetupContract as DashboardEnhancedSetupContract, SetupDependencies as DashboardEnhancedSetupDependencies, StartContract as DashboardEnhancedStartContract, diff --git a/x-pack/plugins/data_enhanced/public/index.ts b/x-pack/plugins/data_enhanced/public/index.ts index c3adf19fabe138..38b8d7d5c5fdf4 100644 --- a/x-pack/plugins/data_enhanced/public/index.ts +++ b/x-pack/plugins/data_enhanced/public/index.ts @@ -12,7 +12,7 @@ import { ConfigSchema } from '../config'; export const plugin = (initializerContext: PluginInitializerContext) => new DataEnhancedPlugin(initializerContext); -export { DataEnhancedSetup, DataEnhancedStart }; +export type { DataEnhancedSetup, DataEnhancedStart }; export { ENHANCED_ES_SEARCH_STRATEGY, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx index f57ad73c3c760a..2970c75e651d6e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx @@ -9,7 +9,8 @@ import { EuiLinkProps, EuiText, EuiTextProps } from '@elastic/eui'; import React from 'react'; import extendSessionIcon from '../icons/extend_session.svg'; -export { OnActionComplete, PopoverActionsMenu } from './actions'; +export type { OnActionComplete } from './actions'; +export { PopoverActionsMenu } from './actions'; export const TableText = ({ children, ...props }: EuiTextProps) => { return ( diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/index.ts b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/index.ts index 122321691439d0..fec61f81154864 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/index.ts @@ -5,7 +5,5 @@ * 2.0. */ -export { - SearchSessionIndicatorDeps, - createConnectedSearchSessionIndicator, -} from './connected_search_session_indicator'; +export type { SearchSessionIndicatorDeps } from './connected_search_session_indicator'; +export { createConnectedSearchSessionIndicator } from './connected_search_session_indicator'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/combined_fields/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/combined_fields/index.ts index 862128f2e856bf..834813ddc24112 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/combined_fields/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/combined_fields/index.ts @@ -13,4 +13,4 @@ export { export { CombinedFieldsReadOnlyForm } from './combined_fields_read_only_form'; export { CombinedFieldsForm } from './combined_fields_form'; -export { CombinedField } from './types'; +export type { CombinedField } from './types'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_chart/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_chart/index.ts index 97b42053b34e6f..1b69f9b825165e 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_chart/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_chart/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { DocumentCountChart, DocumentCountChartPoint } from './document_count_chart'; +export type { DocumentCountChartPoint } from './document_count_chart'; +export { DocumentCountChart } from './document_count_chart'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/multi_select_picker/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/multi_select_picker/index.ts index 9d32228e1c4bc9..a42ce68d9aa10c 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/multi_select_picker/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/multi_select_picker/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { MultiSelectPicker, Option } from './multi_select_picker'; +export type { Option } from './multi_select_picker'; +export { MultiSelectPicker } from './multi_select_picker'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/index.ts index 1dca4b7bf22541..24c36f97d76339 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ResultsLinks, ResultLink } from './results_links'; +export type { ResultLink } from './results_links'; +export { ResultsLinks } from './results_links'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx index f1de0b0b8b8fa2..ed6ab29748a86c 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx @@ -58,7 +58,13 @@ export const ResultsLinks: FC = ({ additionalLinks, }) => { const { - services: { fileUpload }, + services: { + fileUpload, + application: { getUrlForApp, capabilities }, + share: { + urlGenerators: { getUrlGenerator }, + }, + }, } = useDataVisualizerKibana(); const [duration, setDuration] = useState({ @@ -72,15 +78,6 @@ export const ResultsLinks: FC = ({ const [indexPatternManagementLink, setIndexPatternManagementLink] = useState(''); const [generatedLinks, setGeneratedLinks] = useState>({}); - const { - services: { - application: { getUrlForApp, capabilities }, - share: { - urlGenerators: { getUrlGenerator }, - }, - }, - } = useDataVisualizerKibana(); - useEffect(() => { let unmounted = false; @@ -137,11 +134,14 @@ export const ResultsLinks: FC = ({ setIndexManagementLink( getUrlForApp('management', { path: '/data/index_management/indices' }) ); - setIndexPatternManagementLink( - getUrlForApp('management', { - path: `/kibana/indexPatterns${createIndexPattern ? `/patterns/${indexPatternId}` : ''}`, - }) - ); + + if (capabilities.indexPatterns.save === true) { + setIndexPatternManagementLink( + getUrlForApp('management', { + path: `/kibana/indexPatterns${createIndexPattern ? `/patterns/${indexPatternId}` : ''}`, + }) + ); + } } return () => { diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_count_stats/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_count_stats/index.ts index d841ee2959f620..ccd7c6b73a5770 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_count_stats/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_count_stats/index.ts @@ -5,9 +5,7 @@ * 2.0. */ -export { TotalFieldsCount, TotalFieldsCountProps, TotalFieldsStats } from './total_fields_count'; -export { - MetricFieldsCount, - MetricFieldsCountProps, - MetricFieldsStats, -} from './metric_fields_count'; +export type { TotalFieldsCountProps, TotalFieldsStats } from './total_fields_count'; +export { TotalFieldsCount } from './total_fields_count'; +export type { MetricFieldsCountProps, MetricFieldsStats } from './metric_fields_count'; +export { MetricFieldsCount } from './metric_fields_count'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/metric_distribution_chart/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/metric_distribution_chart/index.ts index 72947f2953cb83..5012426e8f7ca5 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/metric_distribution_chart/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/metric_distribution_chart/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { MetricDistributionChart, MetricDistributionChartData } from './metric_distribution_chart'; +export type { MetricDistributionChartData } from './metric_distribution_chart'; +export { MetricDistributionChart } from './metric_distribution_chart'; export { buildChartDataFromStats } from './metric_distribution_chart_data_builder'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/index.ts index 3009470af48589..a8aae70ccfc63b 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { DataVisualizerTable, ItemIdToExpandedRowMap } from './data_visualizer_stats_table'; +export type { ItemIdToExpandedRowMap } from './data_visualizer_stats_table'; +export { DataVisualizerTable } from './data_visualizer_stats_table'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/index.ts index 171d029482e270..00f8ac0c74eb90 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/index.ts @@ -5,11 +5,10 @@ * 2.0. */ -export { FieldDataRowProps } from './field_data_row'; -export { +export type { FieldDataRowProps } from './field_data_row'; +export type { FieldVisConfig, FileBasedFieldVisConfig, MetricFieldVisStats, - isFileBasedFieldVisConfig, - isIndexBasedFieldVisConfig, } from './field_vis_config'; +export { isFileBasedFieldVisConfig, isIndexBasedFieldVisConfig } from './field_vis_config'; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js index 054416ad7ba36e..fa437cce292689 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js @@ -295,12 +295,7 @@ export class FileDataVisualizerView extends Component {
{mode === MODE.READ && ( <> - {!loading && !loaded && ( - - )} + {!loading && !loaded && } {loading && } @@ -373,6 +368,7 @@ export class FileDataVisualizerView extends Component { savedObjectsClient={this.savedObjectsClient} fileUpload={this.props.fileUpload} resultsLinks={this.props.resultsLinks} + capabilities={this.props.capabilities} /> {bottomBarVisible && ( diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_progress/index.ts b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_progress/index.ts index fe2d6ab7d826b3..65ebab329a83c2 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_progress/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_progress/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ImportProgress, IMPORT_STATUS, Statuses } from './import_progress'; +export type { Statuses } from './import_progress'; +export { ImportProgress, IMPORT_STATUS } from './import_progress'; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx index 83e7c556f033f1..23ad2b967bc282 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/advanced.tsx @@ -21,6 +21,7 @@ import { import { CombinedField, CombinedFieldsForm } from '../../../common/components/combined_fields'; import { JsonEditor, EDITOR_MODE } from '../json_editor'; import { FindFileStructureResponse } from '../../../../../../file_upload/common'; +import { CreateDataViewToolTip } from './create_data_view_tooltip'; const EDITOR_HEIGHT = '300px'; interface Props { @@ -42,6 +43,7 @@ interface Props { combinedFields: CombinedField[]; onCombinedFieldsChange(combinedFields: CombinedField[]): void; results: FindFileStructureResponse; + canCreateDataView: boolean; } export const AdvancedSettings: FC = ({ @@ -63,6 +65,7 @@ export const AdvancedSettings: FC = ({ combinedFields, onCombinedFieldsChange, results, + canCreateDataView, }) => { return ( @@ -98,18 +101,20 @@ export const AdvancedSettings: FC = ({ - - } - checked={createIndexPattern === true} - disabled={initialized === true} - onChange={onCreateIndexPatternChange} - /> + + + } + checked={createIndexPattern === true} + disabled={initialized === true || canCreateDataView === false} + onChange={onCreateIndexPatternChange} + /> + diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/create_data_view_tooltip.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/create_data_view_tooltip.tsx new file mode 100644 index 00000000000000..84af5b08b3d495 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/create_data_view_tooltip.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiToolTip } from '@elastic/eui'; + +interface Props { + children?: React.ReactElement; + showTooltip: boolean; +} + +export const CreateDataViewToolTip: FC = ({ children, showTooltip }) => { + return ( + + ) : null + } + > + {children} + + ); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/import_settings.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/import_settings.tsx index 4e36dc42b54a58..c2b9779f3624d0 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/import_settings.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/import_settings.tsx @@ -14,6 +14,7 @@ import { SimpleSettings } from './simple'; import { AdvancedSettings } from './advanced'; import { CombinedField } from '../../../common/components/combined_fields'; import { FindFileStructureResponse } from '../../../../../../file_upload/common'; +import { useDataVisualizerKibana } from '../../../kibana_context'; interface Props { index: string; @@ -56,6 +57,15 @@ export const ImportSettings: FC = ({ onCombinedFieldsChange, results, }) => { + const { + services: { + application: { capabilities }, + }, + } = useDataVisualizerKibana(); + + const canCreateDataView = + capabilities.savedObjectsManagement.edit === true || capabilities.indexPatterns.save === true; + const tabs = [ { id: 'simple-settings', @@ -74,6 +84,7 @@ export const ImportSettings: FC = ({ onCreateIndexPatternChange={onCreateIndexPatternChange} indexNameError={indexNameError} combinedFields={combinedFields} + canCreateDataView={canCreateDataView} /> ), @@ -106,6 +117,7 @@ export const ImportSettings: FC = ({ combinedFields={combinedFields} onCombinedFieldsChange={onCombinedFieldsChange} results={results} + canCreateDataView={canCreateDataView} /> ), diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/simple.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/simple.tsx index 284a5aa3d4f3f0..a080f62f54fc1f 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/simple.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_settings/simple.tsx @@ -14,6 +14,7 @@ import { CombinedField, CombinedFieldsReadOnlyForm, } from '../../../common/components/combined_fields'; +import { CreateDataViewToolTip } from './create_data_view_tooltip'; interface Props { index: string; @@ -23,6 +24,7 @@ interface Props { onCreateIndexPatternChange(): void; indexNameError: string; combinedFields: CombinedField[]; + canCreateDataView: boolean; } export const SimpleSettings: FC = ({ @@ -33,6 +35,7 @@ export const SimpleSettings: FC = ({ onCreateIndexPatternChange, indexNameError, combinedFields, + canCreateDataView, }) => { return ( @@ -69,19 +72,21 @@ export const SimpleSettings: FC = ({ - - } - checked={createIndexPattern === true} - disabled={initialized === true} - onChange={onCreateIndexPatternChange} - data-test-subj="dataVisualizerFileCreateIndexPatternCheckbox" - /> + + + } + checked={createIndexPattern === true} + disabled={initialized === true || canCreateDataView === false} + onChange={onCreateIndexPatternChange} + data-test-subj="dataVisualizerFileCreateIndexPatternCheckbox" + /> + diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js index 3b3a11a5dff22e..b65e2c35ff4ff6 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js @@ -76,7 +76,7 @@ export class ImportView extends Component { constructor(props) { super(props); - this.state = getDefaultState(DEFAULT_STATE, this.props.results); + this.state = getDefaultState(DEFAULT_STATE, this.props.results, this.props.capabilities); this.savedObjectsClient = props.savedObjectsClient; } @@ -85,7 +85,7 @@ export class ImportView extends Component { } clickReset = () => { - const state = getDefaultState(this.state, this.props.results); + const state = getDefaultState(this.state, this.props.results, this.props.capabilities); this.setState(state, () => { this.loadIndexPatternNames(); }); @@ -640,7 +640,7 @@ async function createKibanaIndexPattern(indexPatternName, indexPatterns, timeFie } } -function getDefaultState(state, results) { +function getDefaultState(state, results, capabilities) { const indexSettingsString = state.indexSettingsString === '' ? JSON.stringify(DEFAULT_INDEX_SETTINGS, null, 2) @@ -666,6 +666,11 @@ function getDefaultState(state, results) { const timeFieldName = results.timestamp_field; + const createIndexPattern = + capabilities.savedObjectsManagement.edit === false && capabilities.indexPatterns.save === false + ? false + : state.createIndexPattern; + return { ...DEFAULT_STATE, indexSettingsString, @@ -673,6 +678,7 @@ function getDefaultState(state, results) { pipelineString, timeFieldName, combinedFields, + createIndexPattern, }; } diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx index 3644f7053f1e81..a82a2b7e25d858 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx @@ -39,6 +39,7 @@ export const FileDataVisualizer: FC = ({ additionalLinks }) => { http={coreStart.http} fileUpload={fileUpload} resultsLinks={additionalLinks} + capabilities={coreStart.application.capabilities} /> ); diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/index.ts b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/index.ts index ca87d73b6a758c..4cd3755726ad28 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { FileDataVisualizer, FileDataVisualizerSpec } from './file_data_visualizer'; +export type { FileDataVisualizerSpec } from './file_data_visualizer'; +export { FileDataVisualizer } from './file_data_visualizer'; diff --git a/x-pack/plugins/data_visualizer/public/application/index.ts b/x-pack/plugins/data_visualizer/public/application/index.ts index 6229f61be85e9a..7e84bd84adf6f7 100644 --- a/x-pack/plugins/data_visualizer/public/application/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/index.ts @@ -5,9 +5,10 @@ * 2.0. */ -export { FileDataVisualizer, FileDataVisualizerSpec } from './file_data_visualizer'; -export { - IndexDataVisualizer, +export type { FileDataVisualizerSpec } from './file_data_visualizer'; +export { FileDataVisualizer } from './file_data_visualizer'; +export type { IndexDataVisualizerSpec, IndexDataVisualizerViewProps, } from './index_data_visualizer'; +export { IndexDataVisualizer } from './index_data_visualizer'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/index.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/index.tsx index c79df59ee3f69f..14ad77e2adc3ad 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/index.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/index.tsx @@ -6,4 +6,5 @@ */ export { FullTimeRangeSelector } from './full_time_range_selector'; -export { getTimeFilterRange, TimeRange } from './full_time_range_selector_service'; +export type { TimeRange } from './full_time_range_selector_service'; +export { getTimeFilterRange } from './full_time_range_selector_service'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index.ts index bcdef0966039d4..9bb579f7af55db 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index.ts @@ -5,7 +5,5 @@ * 2.0. */ -export { - IndexDataVisualizerViewProps, - IndexDataVisualizerView, -} from './index_data_visualizer_view'; +export type { IndexDataVisualizerViewProps } from './index_data_visualizer_view'; +export { IndexDataVisualizerView } from './index_data_visualizer_view'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index.ts index 77b6f9b5ab18c6..82abdf185f9ab6 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { IndexDataVisualizer, IndexDataVisualizerSpec } from './index_data_visualizer'; +export type { IndexDataVisualizerSpec } from './index_data_visualizer'; +export { IndexDataVisualizer } from './index_data_visualizer'; export type { IndexDataVisualizerViewProps } from './components/index_data_visualizer_view'; diff --git a/x-pack/plugins/data_visualizer/public/index.ts b/x-pack/plugins/data_visualizer/public/index.ts index 1a045f144c0152..4a579f4e3abcc4 100644 --- a/x-pack/plugins/data_visualizer/public/index.ts +++ b/x-pack/plugins/data_visualizer/public/index.ts @@ -11,7 +11,7 @@ export function plugin() { return new DataVisualizerPlugin(); } -export { DataVisualizerPluginStart } from './plugin'; +export type { DataVisualizerPluginStart } from './plugin'; export type { FileDataVisualizerSpec, diff --git a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/lazy/index.ts index a895a0eb98385d..0001f912bff0db 100644 --- a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/lazy/index.ts +++ b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/lazy/index.ts @@ -5,9 +5,5 @@ * 2.0. */ -export { - FileDataVisualizer, - IndexDataVisualizer, - FileDataVisualizerSpec, - IndexDataVisualizerSpec, -} from '../../application'; +export type { FileDataVisualizerSpec, IndexDataVisualizerSpec } from '../../application'; +export { FileDataVisualizer, IndexDataVisualizer } from '../../application'; diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx index 4c46b840087668..163a2130f657cc 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx @@ -93,7 +93,6 @@ export class UrlDrilldown implements Drilldown { - // eslint-disable-next-line react-hooks/rules-of-hooks const variables = React.useMemo(() => this.getVariableList(context), [context]); return ( diff --git a/x-pack/plugins/embeddable_enhanced/public/index.ts b/x-pack/plugins/embeddable_enhanced/public/index.ts index 0512bedd2a57af..8c64f376754a57 100644 --- a/x-pack/plugins/embeddable_enhanced/public/index.ts +++ b/x-pack/plugins/embeddable_enhanced/public/index.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext } from 'src/core/public'; import { EmbeddableEnhancedPlugin } from './plugin'; -export { +export type { SetupContract as EmbeddableEnhancedSetupContract, SetupDependencies as EmbeddableEnhancedSetupDependencies, StartContract as EmbeddableEnhancedStartContract, @@ -19,6 +19,6 @@ export function plugin(context: PluginInitializerContext) { return new EmbeddableEnhancedPlugin(context); } -export { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; +export type { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; export { isEnhancedEmbeddable } from './embeddables'; export { drilldownGrouping as embeddableEnhancedDrilldownGrouping } from './actions'; diff --git a/x-pack/plugins/encrypted_saved_objects/README.md b/x-pack/plugins/encrypted_saved_objects/README.md index 97e1ea5b657b32..41e2dce75da157 100644 --- a/x-pack/plugins/encrypted_saved_objects/README.md +++ b/x-pack/plugins/encrypted_saved_objects/README.md @@ -3,7 +3,7 @@ ## Overview The purpose of this plugin is to provide a way to encrypt/decrypt attributes on the custom Saved Objects that works with -security and spaces filtering as well as performing audit logging. +security and spaces filtering. [RFC #2: Encrypted Saved Objects Attributes](../../../rfcs/text/0002_encrypted_attributes.md). diff --git a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.test.ts b/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.test.ts deleted file mode 100644 index 695dafe77d3bcf..00000000000000 --- a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.test.ts +++ /dev/null @@ -1,173 +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 { mockAuthenticatedUser } from '../../../security/common/model/authenticated_user.mock'; -import { EncryptedSavedObjectsAuditLogger } from './audit_logger'; - -it('properly logs audit events', () => { - const mockInternalAuditLogger = { log: jest.fn() }; - const audit = new EncryptedSavedObjectsAuditLogger(mockInternalAuditLogger); - - audit.encryptAttributesSuccess(['one', 'two'], { - type: 'known-type', - id: 'object-id', - }); - audit.encryptAttributesSuccess(['one', 'two'], { - type: 'known-type-ns', - id: 'object-id-ns', - namespace: 'object-ns', - }); - audit.encryptAttributesSuccess( - ['one', 'two'], - { type: 'known-type-ns', id: 'object-id-ns', namespace: 'object-ns' }, - mockAuthenticatedUser() - ); - - audit.decryptAttributesSuccess(['three', 'four'], { - type: 'known-type-1', - id: 'object-id-1', - }); - audit.decryptAttributesSuccess(['three', 'four'], { - type: 'known-type-1-ns', - id: 'object-id-1-ns', - namespace: 'object-ns', - }); - audit.decryptAttributesSuccess( - ['three', 'four'], - { type: 'known-type-1-ns', id: 'object-id-1-ns', namespace: 'object-ns' }, - mockAuthenticatedUser() - ); - - audit.encryptAttributeFailure('five', { - type: 'known-type-2', - id: 'object-id-2', - }); - audit.encryptAttributeFailure('five', { - type: 'known-type-2-ns', - id: 'object-id-2-ns', - namespace: 'object-ns', - }); - audit.encryptAttributeFailure( - 'five', - { type: 'known-type-2-ns', id: 'object-id-2-ns', namespace: 'object-ns' }, - mockAuthenticatedUser() - ); - - audit.decryptAttributeFailure('six', { - type: 'known-type-3', - id: 'object-id-3', - }); - audit.decryptAttributeFailure('six', { - type: 'known-type-3-ns', - id: 'object-id-3-ns', - namespace: 'object-ns', - }); - audit.decryptAttributeFailure( - 'six', - { type: 'known-type-3-ns', id: 'object-id-3-ns', namespace: 'object-ns' }, - mockAuthenticatedUser() - ); - - expect(mockInternalAuditLogger.log).toHaveBeenCalledTimes(12); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'encrypt_success', - 'Successfully encrypted attributes "[one,two]" for saved object "[known-type,object-id]".', - { id: 'object-id', type: 'known-type', attributesNames: ['one', 'two'] } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'encrypt_success', - 'Successfully encrypted attributes "[one,two]" for saved object "[object-ns,known-type-ns,object-id-ns]".', - { - id: 'object-id-ns', - type: 'known-type-ns', - namespace: 'object-ns', - attributesNames: ['one', 'two'], - } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'encrypt_success', - 'Successfully encrypted attributes "[one,two]" for saved object "[object-ns,known-type-ns,object-id-ns]".', - { - id: 'object-id-ns', - type: 'known-type-ns', - namespace: 'object-ns', - attributesNames: ['one', 'two'], - username: 'user', - } - ); - - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'decrypt_success', - 'Successfully decrypted attributes "[three,four]" for saved object "[known-type-1,object-id-1]".', - { id: 'object-id-1', type: 'known-type-1', attributesNames: ['three', 'four'] } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'decrypt_success', - 'Successfully decrypted attributes "[three,four]" for saved object "[object-ns,known-type-1-ns,object-id-1-ns]".', - { - id: 'object-id-1-ns', - type: 'known-type-1-ns', - namespace: 'object-ns', - attributesNames: ['three', 'four'], - } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'decrypt_success', - 'Successfully decrypted attributes "[three,four]" for saved object "[object-ns,known-type-1-ns,object-id-1-ns]".', - { - id: 'object-id-1-ns', - type: 'known-type-1-ns', - namespace: 'object-ns', - attributesNames: ['three', 'four'], - username: 'user', - } - ); - - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'encrypt_failure', - 'Failed to encrypt attribute "five" for saved object "[known-type-2,object-id-2]".', - { id: 'object-id-2', type: 'known-type-2', attributeName: 'five' } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'encrypt_failure', - 'Failed to encrypt attribute "five" for saved object "[object-ns,known-type-2-ns,object-id-2-ns]".', - { id: 'object-id-2-ns', type: 'known-type-2-ns', namespace: 'object-ns', attributeName: 'five' } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'encrypt_failure', - 'Failed to encrypt attribute "five" for saved object "[object-ns,known-type-2-ns,object-id-2-ns]".', - { - id: 'object-id-2-ns', - type: 'known-type-2-ns', - namespace: 'object-ns', - attributeName: 'five', - username: 'user', - } - ); - - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'decrypt_failure', - 'Failed to decrypt attribute "six" for saved object "[known-type-3,object-id-3]".', - { id: 'object-id-3', type: 'known-type-3', attributeName: 'six' } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'decrypt_failure', - 'Failed to decrypt attribute "six" for saved object "[object-ns,known-type-3-ns,object-id-3-ns]".', - { id: 'object-id-3-ns', type: 'known-type-3-ns', namespace: 'object-ns', attributeName: 'six' } - ); - expect(mockInternalAuditLogger.log).toHaveBeenCalledWith( - 'decrypt_failure', - 'Failed to decrypt attribute "six" for saved object "[object-ns,known-type-3-ns,object-id-3-ns]".', - { - id: 'object-id-3-ns', - type: 'known-type-3-ns', - namespace: 'object-ns', - attributeName: 'six', - username: 'user', - } - ); -}); diff --git a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.ts b/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.ts deleted file mode 100644 index e6290e4cc4dd8e..00000000000000 --- a/x-pack/plugins/encrypted_saved_objects/server/audit/audit_logger.ts +++ /dev/null @@ -1,73 +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 type { AuthenticatedUser, LegacyAuditLogger } from '../../../security/server'; -import type { SavedObjectDescriptor } from '../crypto'; -import { descriptorToArray } from '../crypto'; - -/** - * Represents all audit events the plugin can log. - */ -export class EncryptedSavedObjectsAuditLogger { - constructor(private readonly logger: LegacyAuditLogger = { log() {} }) {} - - public encryptAttributeFailure( - attributeName: string, - descriptor: SavedObjectDescriptor, - user?: AuthenticatedUser - ) { - this.logger.log( - 'encrypt_failure', - `Failed to encrypt attribute "${attributeName}" for saved object "[${descriptorToArray( - descriptor - )}]".`, - { ...descriptor, attributeName, username: user?.username } - ); - } - - public decryptAttributeFailure( - attributeName: string, - descriptor: SavedObjectDescriptor, - user?: AuthenticatedUser - ) { - this.logger.log( - 'decrypt_failure', - `Failed to decrypt attribute "${attributeName}" for saved object "[${descriptorToArray( - descriptor - )}]".`, - { ...descriptor, attributeName, username: user?.username } - ); - } - - public encryptAttributesSuccess( - attributesNames: readonly string[], - descriptor: SavedObjectDescriptor, - user?: AuthenticatedUser - ) { - this.logger.log( - 'encrypt_success', - `Successfully encrypted attributes "[${attributesNames}]" for saved object "[${descriptorToArray( - descriptor - )}]".`, - { ...descriptor, attributesNames, username: user?.username } - ); - } - - public decryptAttributesSuccess( - attributesNames: readonly string[], - descriptor: SavedObjectDescriptor, - user?: AuthenticatedUser - ) { - this.logger.log( - 'decrypt_success', - `Successfully decrypted attributes "[${attributesNames}]" for saved object "[${descriptorToArray( - descriptor - )}]".`, - { ...descriptor, attributesNames, username: user?.username } - ); - } -} diff --git a/x-pack/plugins/encrypted_saved_objects/server/audit/index.mock.ts b/x-pack/plugins/encrypted_saved_objects/server/audit/index.mock.ts deleted file mode 100644 index a74ef1cb4fd2dc..00000000000000 --- a/x-pack/plugins/encrypted_saved_objects/server/audit/index.mock.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EncryptedSavedObjectsAuditLogger } from './audit_logger'; - -export const encryptedSavedObjectsAuditLoggerMock = { - create() { - return { - encryptAttributesSuccess: jest.fn(), - encryptAttributeFailure: jest.fn(), - decryptAttributesSuccess: jest.fn(), - decryptAttributeFailure: jest.fn(), - } as unknown as jest.Mocked; - }, -}; diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts index 15de21999fba32..3b87362b6dea17 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts @@ -11,8 +11,6 @@ import nodeCrypto from '@elastic/node-crypto'; import { loggingSystemMock } from 'src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../security/common/model/authenticated_user.mock'; -import type { EncryptedSavedObjectsAuditLogger } from '../audit'; -import { encryptedSavedObjectsAuditLoggerMock } from '../audit/index.mock'; import { EncryptedSavedObjectsService } from './encrypted_saved_objects_service'; import { EncryptionError } from './encryption_error'; @@ -44,15 +42,12 @@ function createNodeCryptMock(encryptionKey: string) { let mockNodeCrypto: jest.Mocked; let service: EncryptedSavedObjectsService; -let mockAuditLogger: jest.Mocked; beforeEach(() => { mockNodeCrypto = createNodeCryptMock('encryption-key-abc'); - mockAuditLogger = encryptedSavedObjectsAuditLoggerMock.create(); service = new EncryptedSavedObjectsService({ primaryCrypto: mockNodeCrypto, logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -148,13 +143,6 @@ describe('#stripOrDecryptAttributes', () => { { user: mockUser } ) ).resolves.toEqual({ attributes: { attrTwo: 'two', attrThree: 'three' } }); - - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('exposes values with `dangerouslyExposeValue` set to `true` using original attributes if provided', async () => { @@ -180,9 +168,6 @@ describe('#stripOrDecryptAttributes', () => { attributes ) ).resolves.toEqual({ attributes: { attrTwo: 'two', attrThree: 'three' } }); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).not.toHaveBeenCalled(); }); it('strips attributes with `dangerouslyExposeValue` set to `true` if failed to decrypt', async () => { @@ -218,13 +203,6 @@ describe('#stripOrDecryptAttributes', () => { expect(decryptedAttributes).toEqual({ attrZero: 'zero', attrTwo: 'two', attrFour: 'four' }); expect(error).toMatchInlineSnapshot(`[Error: Unable to decrypt attribute "attrThree"]`); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); @@ -232,7 +210,6 @@ describe('#stripOrDecryptAttributes', () => { beforeEach(() => { service = new EncryptedSavedObjectsService({ logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -284,13 +261,6 @@ describe('#stripOrDecryptAttributes', () => { expect(encryptionError.cause).toEqual( new Error('Decryption is disabled because of missing decryption keys.') ); - - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); }); @@ -352,13 +322,6 @@ describe('#stripOrDecryptAttributesSync', () => { { user: mockUser } ) ).toEqual({ attributes: { attrTwo: 'two', attrThree: 'three' } }); - - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('exposes values with `dangerouslyExposeValue` set to `true` using original attributes if provided', () => { @@ -384,9 +347,6 @@ describe('#stripOrDecryptAttributesSync', () => { attributes ) ).toEqual({ attributes: { attrTwo: 'two', attrThree: 'three' } }); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).not.toHaveBeenCalled(); }); it('strips attributes with `dangerouslyExposeValue` set to `true` if failed to decrypt', () => { @@ -422,13 +382,6 @@ describe('#stripOrDecryptAttributesSync', () => { expect(decryptedAttributes).toEqual({ attrZero: 'zero', attrTwo: 'two', attrFour: 'four' }); expect(error).toMatchInlineSnapshot(`[Error: Unable to decrypt attribute "attrThree"]`); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); @@ -436,7 +389,6 @@ describe('#stripOrDecryptAttributesSync', () => { beforeEach(() => { service = new EncryptedSavedObjectsService({ logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -488,13 +440,6 @@ describe('#stripOrDecryptAttributesSync', () => { expect(encryptionError.cause).toEqual( new Error('Decryption is disabled because of missing decryption keys.') ); - - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); }); @@ -508,7 +453,6 @@ describe('#encryptAttributes', () => { service = new EncryptedSavedObjectsService({ primaryCrypto: mockNodeCrypto, logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -522,7 +466,6 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); }); it('does not encrypt attributes for known, but not registered types', async () => { @@ -535,7 +478,6 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); }); it('does not encrypt attributes that are not supposed to be encrypted', async () => { @@ -550,7 +492,6 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); }); it('encrypts only attributes that are supposed to be encrypted', async () => { @@ -572,12 +513,6 @@ describe('#encryptAttributes', () => { attrThree: '|three|["known-type-1","object-id",{"attrTwo":"two"}]|', attrFour: null, }); - expect(mockAuditLogger.encryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.encryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('encrypts only using primary crypto', async () => { @@ -588,7 +523,6 @@ describe('#encryptAttributes', () => { primaryCrypto: mockNodeCrypto, decryptionOnlyCryptos: [decryptionOnlyCrypto], logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ type: 'known-type-1', @@ -625,12 +559,6 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: '|three|["known-type-1","object-id",{"attrTwo":"two"}]|', }); - expect(mockAuditLogger.encryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.encryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('includes `namespace` into AAD if provided', async () => { @@ -652,12 +580,6 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: '|three|["object-ns","known-type-1","object-id",{"attrTwo":"two"}]|', }); - expect(mockAuditLogger.encryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.encryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); it('does not include specified attributes to AAD', async () => { @@ -728,20 +650,12 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); describe('without encryption key', () => { beforeEach(() => { service = new EncryptedSavedObjectsService({ logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -757,7 +671,6 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); }); it('fails if needs to encrypt any attribute', async () => { @@ -779,13 +692,6 @@ describe('#encryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledWith( - 'attrOne', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); }); @@ -801,7 +707,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); }); it('does not decrypt attributes for known, but not registered types', async () => { @@ -814,7 +719,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); }); it('does not decrypt attributes that are not supposed to be decrypted', async () => { @@ -829,7 +733,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); }); it('decrypts only attributes that are supposed to be decrypted', async () => { @@ -862,12 +765,6 @@ describe('#decryptAttributes', () => { attrThree: 'three', attrFour: null, }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('decrypts only attributes that are supposed to be encrypted even if not all provided', async () => { @@ -896,12 +793,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('decrypts if all attributes that contribute to AAD are present', async () => { @@ -934,12 +825,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('decrypts even if attributes in AAD are defined in a different order', async () => { @@ -978,12 +863,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('decrypts if correct namespace is provided', async () => { @@ -1016,12 +895,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); describe('with isTypeBeingConverted option', () => { @@ -1066,12 +939,6 @@ describe('#decryptAttributes', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); it('retries decryption without originId (old object ID)', async () => { @@ -1115,12 +982,6 @@ describe('#decryptAttributes', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: undefined }, - mockUser - ); }); it('retries decryption without namespace *and* without originId (old object ID)', async () => { @@ -1174,12 +1035,6 @@ describe('#decryptAttributes', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); }); @@ -1208,12 +1063,6 @@ describe('#decryptAttributes', () => { attrOne: 'one', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('decrypts non-string attributes and restores their original type', async () => { @@ -1257,12 +1106,6 @@ describe('#decryptAttributes', () => { attrFive: { nested: 'five' }, attrSix: 6, }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree', 'attrFive', 'attrSix'], - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); describe('decryption failures', () => { @@ -1296,13 +1139,6 @@ describe('#decryptAttributes', () => { { user: mockUser } ) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('fails to decrypt if ID does not match', async () => { @@ -1312,13 +1148,6 @@ describe('#decryptAttributes', () => { user: mockUser, }) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id*' }, - mockUser - ); }); it('fails to decrypt if type does not match', async () => { @@ -1328,13 +1157,6 @@ describe('#decryptAttributes', () => { user: mockUser, }) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-2', id: 'object-id' }, - mockUser - ); }); it('fails to decrypt if namespace does not match', async () => { @@ -1351,13 +1173,6 @@ describe('#decryptAttributes', () => { { user: mockUser } ) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id', namespace: 'object-NS' }, - mockUser - ); }); it('fails to decrypt if namespace is expected, but is not provided', async () => { @@ -1372,13 +1187,6 @@ describe('#decryptAttributes', () => { user: mockUser, }) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('fails if retry decryption without namespace is not correct', async () => { @@ -1413,13 +1221,6 @@ describe('#decryptAttributes', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); it('fails to decrypt if encrypted attribute is defined, but not a string', async () => { @@ -1433,13 +1234,6 @@ describe('#decryptAttributes', () => { ).rejects.toThrowError( 'Encrypted "attrThree" attribute should be a string, but found number' ); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('fails to decrypt if encrypted attribute is not correct', async () => { @@ -1451,13 +1245,6 @@ describe('#decryptAttributes', () => { { user: mockUser } ) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('fails to decrypt if the AAD attribute has changed', async () => { @@ -1469,20 +1256,12 @@ describe('#decryptAttributes', () => { { user: mockUser } ) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); it('fails if encrypted with another encryption key', async () => { service = new EncryptedSavedObjectsService({ primaryCrypto: nodeCrypto({ encryptionKey: 'encryption-key-abc*' }), logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ @@ -1496,13 +1275,6 @@ describe('#decryptAttributes', () => { user: mockUser, }) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); @@ -1512,7 +1284,6 @@ describe('#decryptAttributes', () => { primaryCrypto, decryptionOnlyCryptos, logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); esoService.registerType({ @@ -1543,12 +1314,6 @@ describe('#decryptAttributes', () => { await expect( service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, encryptedAttributes) ).resolves.toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); expect(decryptionOnlyCryptoOne.decrypt).not.toHaveBeenCalled(); expect(decryptionOnlyCryptoTwo.decrypt).not.toHaveBeenCalled(); @@ -1563,12 +1328,6 @@ describe('#decryptAttributes', () => { await expect( service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, encryptedAttributes) ).resolves.toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); // One call per attributes, we have 2 of them. expect(mockNodeCrypto.decrypt).toHaveBeenCalledTimes(2); @@ -1585,12 +1344,6 @@ describe('#decryptAttributes', () => { await expect( service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, encryptedAttributes) ).resolves.toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); // One call per attributes, we have 2 of them. expect(mockNodeCrypto.decrypt).toHaveBeenCalledTimes(2); @@ -1609,12 +1362,6 @@ describe('#decryptAttributes', () => { omitPrimaryEncryptionKey: true, }) ).resolves.toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); // One call per attributes, we have 2 of them. expect(mockNodeCrypto.decrypt).not.toHaveBeenCalled(); @@ -1627,7 +1374,6 @@ describe('#decryptAttributes', () => { beforeEach(() => { service = new EncryptedSavedObjectsService({ logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -1637,7 +1383,6 @@ describe('#decryptAttributes', () => { service = new EncryptedSavedObjectsService({ decryptionOnlyCryptos: [], logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) }); @@ -1648,7 +1393,6 @@ describe('#decryptAttributes', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); }); it('does not fail if can decrypt attributes with decryption only keys', async () => { @@ -1660,7 +1404,6 @@ describe('#decryptAttributes', () => { service = new EncryptedSavedObjectsService({ decryptionOnlyCryptos: [decryptionOnlyCryptoOne], logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ type: 'known-type-1', @@ -1676,12 +1419,6 @@ describe('#decryptAttributes', () => { attrThree: 'three||["known-type-1","object-id",{"attrTwo":"two"}]', attrFour: null, }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); }); it('fails if needs to decrypt any attribute', async () => { @@ -1695,13 +1432,6 @@ describe('#decryptAttributes', () => { user: mockUser, }) ).rejects.toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrOne', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); }); @@ -1715,7 +1445,6 @@ describe('#encryptAttributesSync', () => { service = new EncryptedSavedObjectsService({ primaryCrypto: mockNodeCrypto, logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -1759,7 +1488,6 @@ describe('#encryptAttributesSync', () => { primaryCrypto: mockNodeCrypto, decryptionOnlyCryptos: [decryptionOnlyCrypto], logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ type: 'known-type-1', @@ -1893,7 +1621,6 @@ describe('#encryptAttributesSync', () => { beforeEach(() => { service = new EncryptedSavedObjectsService({ logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -1909,7 +1636,6 @@ describe('#encryptAttributesSync', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); }); it('fails if needs to encrypt any attribute', () => { @@ -1931,13 +1657,6 @@ describe('#encryptAttributesSync', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledWith( - 'attrOne', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); }); @@ -2154,12 +1873,6 @@ describe('#decryptAttributesSync', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); it('retries decryption without originId (old object ID)', () => { @@ -2203,12 +1916,6 @@ describe('#decryptAttributesSync', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: undefined }, - mockUser - ); }); it('retries decryption without namespace *and* without originId (old object ID)', () => { @@ -2262,12 +1969,6 @@ describe('#decryptAttributesSync', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrThree'], - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); }); @@ -2448,13 +2149,6 @@ describe('#decryptAttributesSync', () => { expect.anything(), `["known-type-1","object-id",{"attrOne":"one","attrTwo":"two"}]` ); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrThree', - { type: 'known-type-1', id: 'object-id', namespace: 'object-ns' }, - mockUser - ); }); it('fails to decrypt if encrypted attribute is defined, but not a string', () => { @@ -2497,7 +2191,6 @@ describe('#decryptAttributesSync', () => { service = new EncryptedSavedObjectsService({ primaryCrypto: nodeCrypto({ encryptionKey: 'encryption-key-abc*' }), logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ @@ -2520,7 +2213,6 @@ describe('#decryptAttributesSync', () => { primaryCrypto, decryptionOnlyCryptos, logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); esoService.registerType({ @@ -2554,12 +2246,6 @@ describe('#decryptAttributesSync', () => { encryptedAttributes ) ).toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); expect(decryptionOnlyCryptoOne.decryptSync).not.toHaveBeenCalled(); expect(decryptionOnlyCryptoTwo.decryptSync).not.toHaveBeenCalled(); @@ -2577,12 +2263,6 @@ describe('#decryptAttributesSync', () => { encryptedAttributes ) ).toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); // One call per attributes, we have 2 of them. expect(mockNodeCrypto.decryptSync).toHaveBeenCalledTimes(2); @@ -2602,12 +2282,6 @@ describe('#decryptAttributesSync', () => { encryptedAttributes ) ).toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); // One call per attributes, we have 2 of them. expect(mockNodeCrypto.decryptSync).toHaveBeenCalledTimes(2); @@ -2628,12 +2302,6 @@ describe('#decryptAttributesSync', () => { { omitPrimaryEncryptionKey: true } ) ).toEqual({ attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); // One call per attributes, we have 2 of them. expect(mockNodeCrypto.decryptSync).not.toHaveBeenCalled(); @@ -2646,7 +2314,6 @@ describe('#decryptAttributesSync', () => { beforeEach(() => { service = new EncryptedSavedObjectsService({ logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); }); @@ -2656,7 +2323,6 @@ describe('#decryptAttributesSync', () => { service = new EncryptedSavedObjectsService({ decryptionOnlyCryptos: [], logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) }); @@ -2667,7 +2333,6 @@ describe('#decryptAttributesSync', () => { attrTwo: 'two', attrThree: 'three', }); - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); }); it('does not fail if can decrypt attributes with decryption only keys', () => { @@ -2679,7 +2344,6 @@ describe('#decryptAttributesSync', () => { service = new EncryptedSavedObjectsService({ decryptionOnlyCryptos: [decryptionOnlyCryptoOne], logger: loggingSystemMock.create().get(), - audit: mockAuditLogger, }); service.registerType({ type: 'known-type-1', @@ -2695,12 +2359,6 @@ describe('#decryptAttributesSync', () => { attrThree: 'three||["known-type-1","object-id",{"attrTwo":"two"}]', attrFour: null, }); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith( - ['attrOne', 'attrThree'], - { type: 'known-type-1', id: 'object-id' }, - undefined - ); }); it('fails if needs to decrypt any attribute', () => { @@ -2714,13 +2372,6 @@ describe('#decryptAttributesSync', () => { user: mockUser, }) ).toThrowError(EncryptionError); - - expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled(); - expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith( - 'attrOne', - { type: 'known-type-1', id: 'object-id' }, - mockUser - ); }); }); }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts index 4980817393c5fe..1e1f09dabcf539 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts @@ -12,7 +12,6 @@ import typeDetect from 'type-detect'; import type { Logger } from 'src/core/server'; import type { AuthenticatedUser } from '../../../security/common/model'; -import type { EncryptedSavedObjectsAuditLogger } from '../audit'; import { EncryptedSavedObjectAttributesDefinition } from './encrypted_saved_object_type_definition'; import { EncryptionError, EncryptionErrorOperation } from './encryption_error'; @@ -84,11 +83,6 @@ interface EncryptedSavedObjectsServiceOptions { */ logger: Logger; - /** - * Audit logger instance. - */ - audit: EncryptedSavedObjectsAuditLogger; - /** * NodeCrypto instance used for both encryption and decryption. */ @@ -294,7 +288,6 @@ export class EncryptedSavedObjectsService { this.options.logger.error( `Failed to encrypt "${attributeName}" attribute: ${err.message || err}` ); - this.options.audit.encryptAttributeFailure(attributeName, descriptor, params?.user); throw new EncryptionError( `Unable to encrypt attribute "${attributeName}"`, @@ -323,8 +316,6 @@ export class EncryptedSavedObjectsService { return attributes; } - this.options.audit.encryptAttributesSuccess(encryptedAttributesKeys, descriptor, params?.user); - return { ...attributes, ...encryptedAttributes, @@ -523,7 +514,6 @@ export class EncryptedSavedObjectsService { } if (typeof attributeValue !== 'string') { - this.options.audit.decryptAttributeFailure(attributeName, descriptor, params?.user); throw new Error( `Encrypted "${attributeName}" attribute should be a string, but found ${typeDetect( attributeValue @@ -556,7 +546,6 @@ export class EncryptedSavedObjectsService { this.options.logger.error( `Failed to decrypt "${attributeName}" attribute: ${err.message || err}` ); - this.options.audit.decryptAttributeFailure(attributeName, descriptor, params?.user); throw new EncryptionError( `Unable to decrypt attribute "${attributeName}"`, @@ -584,8 +573,6 @@ export class EncryptedSavedObjectsService { return attributes; } - this.options.audit.decryptAttributesSuccess(decryptedAttributesKeys, descriptor, params?.user); - return { ...attributes, ...decryptedAttributes, diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/index.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/index.ts index 31086f56c3b867..532702f2131920 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/index.ts @@ -5,12 +5,11 @@ * 2.0. */ -export { - EncryptedSavedObjectsService, +export type { EncryptedSavedObjectTypeRegistration, - descriptorToArray, SavedObjectDescriptor, } from './encrypted_saved_objects_service'; +export { EncryptedSavedObjectsService, descriptorToArray } from './encrypted_saved_objects_service'; export { EncryptionError, EncryptionErrorOperation } from './encryption_error'; export { EncryptedSavedObjectAttributesDefinition } from './encrypted_saved_object_type_definition'; export { EncryptionKeyRotationService } from './encryption_key_rotation_service'; diff --git a/x-pack/plugins/encrypted_saved_objects/server/index.ts b/x-pack/plugins/encrypted_saved_objects/server/index.ts index d462f06939f6b3..873c8c0d52cb55 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/index.ts @@ -10,9 +10,10 @@ import type { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/ import { ConfigSchema } from './config'; import { EncryptedSavedObjectsPlugin } from './plugin'; -export { EncryptedSavedObjectTypeRegistration, EncryptionError } from './crypto'; -export { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; -export { EncryptedSavedObjectsClient } from './saved_objects'; +export type { EncryptedSavedObjectTypeRegistration } from './crypto'; +export { EncryptionError } from './crypto'; +export type { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; +export type { EncryptedSavedObjectsClient } from './saved_objects'; export type { IsMigrationNeededPredicate } from './create_migration'; export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts index 565d4379014cc0..23738026e0ab69 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts @@ -10,7 +10,6 @@ import nodeCrypto from '@elastic/node-crypto'; import type { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; import type { SecurityPluginSetup } from '../../security/server'; -import { EncryptedSavedObjectsAuditLogger } from './audit'; import type { ConfigType } from './config'; import type { CreateEncryptedSavedObjectsMigrationFn } from './create_migration'; import { getCreateMigration } from './create_migration'; @@ -72,15 +71,11 @@ export class EncryptedSavedObjectsPlugin const decryptionOnlyCryptos = config.keyRotation.decryptionOnlyKeys.map((decryptionKey) => nodeCrypto({ encryptionKey: decryptionKey }) ); - const auditLogger = new EncryptedSavedObjectsAuditLogger( - deps.security?.audit.getLogger('encryptedSavedObjects') - ); const service = Object.freeze( new EncryptedSavedObjectsService({ primaryCrypto, decryptionOnlyCryptos, logger: this.logger, - audit: auditLogger, }) ); @@ -116,7 +111,6 @@ export class EncryptedSavedObjectsPlugin primaryCrypto, decryptionOnlyCryptos, logger: this.logger, - audit: auditLogger, }); serviceForMigration.registerType(typeRegistration); return serviceForMigration; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts index 3f6436424a63b1..68916164f31898 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts @@ -160,7 +160,7 @@ export const AnalyticsLogic = kea(url, { query }); actions.onAnalyticsDataLoad(response); } catch (e) { flashAPIErrors(e); @@ -180,7 +180,7 @@ export const AnalyticsLogic = kea(url, { query: queryParams }); actions.onQueryDataLoad(response); } catch (e) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx index 0b65c59a184197..e7c98332ee6518 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx @@ -86,7 +86,7 @@ export const ACTIONS_COLUMN = { try { const query = (item as Query).key || (item as RecentQuery).query_string || '""'; - const response = await http.get( + const response = await http.get<{ id: string }>( `/internal/app_search/engines/${engineName}/curations/find_or_create`, { query: { query } } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts index 86c8ec8c5fbd18..800e038cc1923b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts @@ -104,14 +104,17 @@ export const ApiLogsLogic = kea>({ const { engineName } = EngineLogic.values; try { - const response = await http.get(`/internal/app_search/engines/${engineName}/api_logs`, { - query: { - 'page[current]': values.meta.page.current, - 'filters[date][from]': getDateString(-1), - 'filters[date][to]': getDateString(), - sort_direction: 'desc', - }, - }); + const response = await http.get( + `/internal/app_search/engines/${engineName}/api_logs`, + { + query: { + 'page[current]': values.meta.page.current, + 'filters[date][from]': getDateString(-1), + 'filters[date][to]': getDateString(), + sort_direction: 'desc', + }, + } + ); // Manual fetches (e.g. page load, user pagination) should update the view immediately, // while polls are stored in-state until the user manually triggers the 'Refresh' action diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.ts index ab85f8a585d1aa..494d123cae1254 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.ts @@ -23,6 +23,7 @@ import { CrawlerDomain, CrawlerDomainValidationResult, CrawlerDomainValidationResultChange, + CrawlerDomainValidationResultFromServer, CrawlerDomainValidationStepName, } from '../../types'; import { crawlDomainValidationToResult, crawlerDataServerToClient } from '../../utils'; @@ -207,7 +208,7 @@ export const AddDomainLogic = kea(route, { body: JSON.stringify({ url: values.addDomainFormInputValue.trim(), checks }), }); const result = crawlDomainValidationToResult(data); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts index 5b9960ddf54e00..d1530c79a6821a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts @@ -12,7 +12,14 @@ import { flashAPIErrors } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; import { EngineLogic } from '../engine'; -import { CrawlerData, CrawlerDomain, CrawlEvent, CrawlRequest, CrawlerStatus } from './types'; +import { + CrawlerData, + CrawlerDomain, + CrawlEvent, + CrawlRequest, + CrawlerStatus, + CrawlerDataFromServer, +} from './types'; import { crawlerDataServerToClient } from './utils'; const POLLING_DURATION = 1000; @@ -104,7 +111,9 @@ export const CrawlerLogic = kea>({ const { engineName } = EngineLogic.values; try { - const response = await http.get(`/internal/app_search/engines/${engineName}/crawler`); + const response = await http.get( + `/internal/app_search/engines/${engineName}/crawler` + ); const crawlerData = crawlerDataServerToClient(response); actions.onReceiveCrawlerData(crawlerData); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts index c6a26e50a6758f..605d45effaa24a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts @@ -13,7 +13,7 @@ import { HttpLogic } from '../../../shared/http'; import { EngineLogic } from '../engine'; import { CrawlerLogic } from './crawler_logic'; -import { CrawlerDomain } from './types'; +import { CrawlerDataFromServer, CrawlerDomain } from './types'; import { crawlerDataServerToClient, getDeleteDomainSuccessMessage } from './utils'; interface CrawlerOverviewActions { @@ -31,7 +31,7 @@ export const CrawlerOverviewLogic = kea( `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}`, { query: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts index 9452ae1d578ed9..64687e24ccb297 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts @@ -16,7 +16,7 @@ import { EngineLogic, generateEnginePath } from '../engine'; import { CrawlerLogic } from './crawler_logic'; -import { CrawlerDomain, EntryPoint, Sitemap, CrawlRule } from './types'; +import { CrawlerDomain, EntryPoint, Sitemap, CrawlRule, CrawlerDomainFromServer } from './types'; import { crawlerDomainServerToClient, getDeleteDomainSuccessMessage } from './utils'; export interface CrawlerSingleDomainValues { @@ -92,7 +92,7 @@ export const CrawlerSingleDomainLogic = kea< const { engineName } = EngineLogic.values; try { - const response = await http.get( + const response = await http.get( `/internal/app_search/engines/${engineName}/crawler/domains/${domainId}` ); @@ -113,7 +113,7 @@ export const CrawlerSingleDomainLogic = kea< }; try { - const response = await http.put( + const response = await http.put( `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}`, { body: JSON.stringify(payload), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts index 52e1b1825b1809..30380b8b41e3b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts @@ -239,7 +239,10 @@ export const CredentialsLogic = kea({ 'page[current]': meta.page.current, 'page[size]': meta.page.size, }; - const response = await http.get('/internal/app_search/credentials', { query }); + const response = await http.get<{ meta: Meta; results: ApiToken[] }>( + '/internal/app_search/credentials', + { query } + ); actions.setCredentialsData(response.meta, response.results); } catch (e) { flashAPIErrors(e); @@ -248,7 +251,9 @@ export const CredentialsLogic = kea({ fetchDetails: async () => { try { const { http } = HttpLogic.values; - const response = await http.get('/internal/app_search/credentials/details'); + const response = await http.get( + '/internal/app_search/credentials/details' + ); actions.setCredentialsDetails(response); } catch (e) { @@ -287,11 +292,13 @@ export const CredentialsLogic = kea({ const body = JSON.stringify(data); if (id) { - const response = await http.put(`/internal/app_search/credentials/${name}`, { body }); + const response = await http.put(`/internal/app_search/credentials/${name}`, { + body, + }); actions.onApiTokenUpdateSuccess(response); flashSuccessToast(UPDATE_MESSAGE(name)); } else { - const response = await http.post('/internal/app_search/credentials', { body }); + const response = await http.post('/internal/app_search/credentials', { body }); actions.onApiTokenCreateSuccess(response); flashSuccessToast(CREATE_MESSAGE(name)); } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx index 4248eb62e33f15..3e12aa7b629f00 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx @@ -122,7 +122,7 @@ describe('SuggestionsLogic', () => { await nextTick(); expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify({ page: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx index 074d2114ee8cb4..16c32c9bb0545e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx @@ -74,8 +74,8 @@ export const SuggestionsLogic = kea( + `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, { body: JSON.stringify({ page: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.test.tsx index b7d1b6f9ed8092..cd8ba123b58431 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.test.tsx @@ -17,7 +17,7 @@ describe('AutomatedCurationHistory', () => { it('renders', () => { const wrapper = shallow(); expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual( - 'appsearch.search_relevance_suggestions.query: some text and event.kind: event and event.dataset: search-relevance-suggestions and appsearch.search_relevance_suggestions.engine: foo and event.action: curation_suggestion and appsearch.search_relevance_suggestions.suggestion.new_status: automated' + 'appsearch.adaptive_relevance.query: some text and event.kind: event and event.dataset: search-relevance-suggestions and appsearch.adaptive_relevance.engine: foo and event.action: curation_suggestion and appsearch.adaptive_relevance.suggestion.new_status: automated' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.tsx index f523beeb0a8212..7fb91daf2e590f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.tsx @@ -19,12 +19,12 @@ interface Props { export const AutomatedCurationHistory: React.FC = ({ query, engineName }) => { const filters = [ - `appsearch.search_relevance_suggestions.query: ${query}`, + `appsearch.adaptive_relevance.query: ${query}`, 'event.kind: event', 'event.dataset: search-relevance-suggestions', - `appsearch.search_relevance_suggestions.engine: ${engineName}`, + `appsearch.adaptive_relevance.engine: ${engineName}`, 'event.action: curation_suggestion', - 'appsearch.search_relevance_suggestions.suggestion.new_status: automated', + 'appsearch.adaptive_relevance.suggestion.new_status: automated', ]; return ( @@ -35,7 +35,7 @@ export const AutomatedCurationHistory: React.FC = ({ query, engineName }) {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableTitle', { - defaultMessage: 'Automated curation changes', + defaultMessage: 'Adaptive relevance changes', } )} @@ -43,7 +43,8 @@ export const AutomatedCurationHistory: React.FC = ({ query, engineName }) subtitle={i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableDescription', { - defaultMessage: 'A detailed log of recent changes to your automated curation.', + defaultMessage: + 'A detailed log of recent changes to curations powered by adaptive relevance.', } )} hasBorder diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index b1f16944c985b1..2b51cbb884ff9c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -295,7 +295,7 @@ describe('CurationLogic', () => { await nextTick(); expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify([ { @@ -412,6 +412,7 @@ describe('CurationLogic', () => { expect(http.put).toHaveBeenCalledWith( '/internal/app_search/engines/some-engine/curations/cur-123456789', { + query: { skip_record_analytics: 'true' }, body: '{"queries":["a","b","c"],"query":"b","promoted":["d","e","f"],"hidden":["g"]}', // Uses state currently in CurationLogic } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts index 6393ccf9742254..08bf8cfd179eba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts @@ -194,15 +194,18 @@ export const CurationLogic = kea( `/internal/app_search/engines/${engineName}/curations/${props.curationId}`, { query: { skip_record_analytics: 'true' } } ); @@ -248,9 +251,10 @@ export const CurationLogic = kea( `/internal/app_search/engines/${engineName}/curations/${props.curationId}`, { + query: { skip_record_analytics: 'true' }, body: JSON.stringify({ queries: values.queries, query: values.activeQuery, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.test.tsx index b1f02b960aa8a1..4fdce0bcd02997 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.test.tsx @@ -28,7 +28,7 @@ const MOCK_VALUES = { }, // EngineLogic engine: { - search_relevance_suggestions_active: true, + adaptive_relevance_suggestions_active: true, }, }; @@ -53,7 +53,7 @@ describe('SuggestedDocumentsCallout', () => { }); it('is empty when suggestions are not active', () => { - const values = set('engine.search_relevance_suggestions_active', false, MOCK_VALUES); + const values = set('engine.adaptive_relevance_suggestions_active', false, MOCK_VALUES); setMockValues(values); const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx index af76ebee16bad3..ec296089a10864 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx @@ -22,13 +22,13 @@ export const SuggestedDocumentsCallout: React.FC = () => { curation: { suggestion, queries }, } = useValues(CurationLogic); const { - engine: { search_relevance_suggestions_active: searchRelevanceSuggestionsActive }, + engine: { adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive }, } = useValues(EngineLogic); if ( typeof suggestion === 'undefined' || suggestion.status !== 'pending' || - searchRelevanceSuggestionsActive === false + adaptiveRelevanceSuggestionsActive === false ) { return null; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.ts index 487072584583ff..422c2e85751623 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.ts @@ -85,12 +85,15 @@ export const CurationsLogic = kea( + `/internal/app_search/engines/${engineName}/curations`, + { + query: { + 'page[current]': meta.page.current, + 'page[size]': meta.page.size, + }, + } + ); actions.onCurationsLoad(response); } catch (e) { flashAPIErrors(e); @@ -118,9 +121,10 @@ export const CurationsLogic = kea( + `/internal/app_search/engines/${engineName}/curations`, + { body: JSON.stringify({ queries }) } + ); navigateToUrl(generateEnginePath(ENGINE_CURATION_PATH, { curationId: response.id })); } catch (e) { flashAPIErrors(e); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts index e6a847f6e9ec67..171c774d8add27 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts @@ -241,7 +241,7 @@ describe('CurationSuggestionLogic', () => { await nextTick(); expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions/foo-query', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions/foo-query', { query: { type: 'curation', @@ -297,7 +297,7 @@ describe('CurationSuggestionLogic', () => { await nextTick(); expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify([ { @@ -380,7 +380,7 @@ describe('CurationSuggestionLogic', () => { await nextTick(); expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify([ { @@ -463,7 +463,7 @@ describe('CurationSuggestionLogic', () => { await nextTick(); expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify([ { @@ -508,7 +508,7 @@ describe('CurationSuggestionLogic', () => { await nextTick(); expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify([ { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts index 8e6c3a9c6a6ae5..0e774d811f3be4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts @@ -79,8 +79,9 @@ export const CurationSuggestionLogic = kea< const { engineName } = EngineLogic.values; try { - const suggestionResponse = await http.get( - `/internal/app_search/engines/${engineName}/search_relevance_suggestions/${props.query}`, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const suggestionResponse = await http.get( + `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions/${props.query}`, { query: { type: 'curation', @@ -250,7 +251,7 @@ const updateSuggestion = async ( status: string ) => { const response = await http.put<{ results: Array }>( - `/internal/app_search/engines/${engineName}/search_relevance_suggestions`, + `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, { body: JSON.stringify([ { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.test.tsx index 49d48c8c05ba65..ba3ac33be3c47a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.test.tsx @@ -55,7 +55,7 @@ describe('Curations', () => { }, // EngineLogic engine: { - search_relevance_suggestions_active: true, + adaptive_relevance_suggestions_active: true, }, }; @@ -89,7 +89,7 @@ describe('Curations', () => { }); it('renders less tabs when suggestions are not active', () => { - setMockValues(set('engine.search_relevance_suggestions_active', false, values)); + setMockValues(set('engine.adaptive_relevance_suggestions_active', false, values)); const wrapper = shallow(); expect(getPageTitle(wrapper)).toEqual('Curated results'); @@ -99,7 +99,7 @@ describe('Curations', () => { }); it('renders a New! badge when suggestions are not active', () => { - setMockValues(set('engine.search_relevance_suggestions_active', false, values)); + setMockValues(set('engine.adaptive_relevance_suggestions_active', false, values)); const wrapper = shallow(); expect(getPageTitle(wrapper)).toEqual('Curated results'); @@ -109,7 +109,7 @@ describe('Curations', () => { }); it('hides the badge when suggestions are active', () => { - setMockValues(set('engine.search_relevance_suggestions_active', true, values)); + setMockValues(set('engine.adaptive_relevance_suggestions_active', true, values)); const wrapper = shallow(); expect(getPageTitle(wrapper)).toEqual('Curated results'); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx index b0f4f03789af22..048566409ab9f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx @@ -30,10 +30,10 @@ export const Curations: React.FC = () => { const { dataLoading, meta, selectedPageTab } = useValues(CurationsLogic); const { loadCurations, onSelectPageTab } = useActions(CurationsLogic); const { - engine: { search_relevance_suggestions_active: searchRelevanceSuggestionsActive }, + engine: { adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive }, } = useValues(EngineLogic); - const suggestionsEnabled = searchRelevanceSuggestionsActive; + const suggestionsEnabled = adaptiveRelevanceSuggestionsActive; const OVERVIEW_TAB = { label: i18n.translate( @@ -72,7 +72,7 @@ export const Curations: React.FC = () => { ), }; - const pageTabs = searchRelevanceSuggestionsActive + const pageTabs = adaptiveRelevanceSuggestionsActive ? [OVERVIEW_TAB, HISTORY_TAB, SETTINGS_TAB] : [OVERVIEW_TAB, SETTINGS_TAB]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.test.tsx index 044637ff1c8234..36703dc0d0d859 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.test.tsx @@ -29,7 +29,7 @@ describe('AutomatedCurationsHistoryPanel', () => { expect(wrapper.is(DataPanel)).toBe(true); expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual( - 'event.kind: event and event.dataset: search-relevance-suggestions and appsearch.search_relevance_suggestions.engine: some-engine and event.action: curation_suggestion and appsearch.search_relevance_suggestions.suggestion.new_status: automated' + 'event.kind: event and event.dataset: search-relevance-suggestions and appsearch.adaptive_relevance.engine: some-engine and event.action: curation_suggestion and appsearch.adaptive_relevance.suggestion.new_status: automated' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx index c3c5747b299540..04f786b1ee1e1b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx @@ -21,9 +21,9 @@ export const AutomatedCurationsHistoryPanel: React.FC = () => { const filters = [ 'event.kind: event', 'event.dataset: search-relevance-suggestions', - `appsearch.search_relevance_suggestions.engine: ${engineName}`, + `appsearch.adaptive_relevance.engine: ${engineName}`, 'event.action: curation_suggestion', - 'appsearch.search_relevance_suggestions.suggestion.new_status: automated', + 'appsearch.adaptive_relevance.suggestion.new_status: automated', ]; return ( @@ -34,7 +34,7 @@ export const AutomatedCurationsHistoryPanel: React.FC = () => { {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableTitle', { - defaultMessage: 'Automated curation changes', + defaultMessage: 'Adaptive relevance changes', } )} @@ -42,7 +42,8 @@ export const AutomatedCurationsHistoryPanel: React.FC = () => { subtitle={i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableDecription', { - defaultMessage: 'A detailed log of recent changes to your automated curations.', + defaultMessage: + 'A detailed log of recent changes to curations powered by adaptive relevance.', } )} hasBorder @@ -53,7 +54,7 @@ export const AutomatedCurationsHistoryPanel: React.FC = () => { columns={[ { type: 'field', - field: 'appsearch.search_relevance_suggestions.query', + field: 'appsearch.adaptive_relevance.query', header: i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.queryColumnHeader', { defaultMessage: 'Query' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts index 83a200943256b2..8c2545fad651ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts @@ -114,7 +114,7 @@ describe('IgnoredQueriesLogic', () => { await nextTick(); expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify({ page: { @@ -170,7 +170,7 @@ describe('IgnoredQueriesLogic', () => { await nextTick(); expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions', + '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', { body: JSON.stringify([ { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.ts index e36b5bc156b468..798117ec353d44 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.ts @@ -89,7 +89,7 @@ export const IgnoredQueriesLogic = kea; - }>(`/internal/app_search/engines/${engineName}/search_relevance_suggestions`, { + }>(`/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, { body: JSON.stringify([ { query: ignoredQuery, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx index 28bb317941e1c8..58bf89a36d5ee6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx @@ -29,7 +29,7 @@ describe('RejectedCurationsHistoryPanel', () => { expect(wrapper.is(DataPanel)).toBe(true); expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual( - 'event.kind: event and event.dataset: search-relevance-suggestions and appsearch.search_relevance_suggestions.engine: some-engine and event.action: curation_suggestion and appsearch.search_relevance_suggestions.suggestion.new_status: rejected' + 'event.kind: event and event.dataset: search-relevance-suggestions and appsearch.adaptive_relevance.engine: some-engine and event.action: curation_suggestion and appsearch.adaptive_relevance.suggestion.new_status: rejected' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx index 275083f91c0fb8..e7d66fc35a5062 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx @@ -21,9 +21,9 @@ export const RejectedCurationsHistoryPanel: React.FC = () => { const filters = [ 'event.kind: event', 'event.dataset: search-relevance-suggestions', - `appsearch.search_relevance_suggestions.engine: ${engineName}`, + `appsearch.adaptive_relevance.engine: ${engineName}`, 'event.action: curation_suggestion', - 'appsearch.search_relevance_suggestions.suggestion.new_status: rejected', + 'appsearch.adaptive_relevance.suggestion.new_status: rejected', ]; return ( @@ -53,7 +53,7 @@ export const RejectedCurationsHistoryPanel: React.FC = () => { columns={[ { type: 'field', - field: 'appsearch.search_relevance_suggestions.query', + field: 'appsearch.adaptive_relevance.query', header: i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.queryColumnHeader', { defaultMessage: 'Query' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.test.tsx index 43ef9dfd7ad2bb..b7da6d64b6a8a0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.test.tsx @@ -36,7 +36,7 @@ const MOCK_VALUES = { ], // EngineLogic engine: { - search_relevance_suggestions_active: true, + adaptive_relevance_suggestions_active: true, }, }; @@ -71,14 +71,14 @@ describe('CurationsOverview', () => { }); it('renders a suggestions table when suggestions are active', () => { - setMockValues(set('engine.search_relevance_suggestions_active', true, MOCK_VALUES)); + setMockValues(set('engine.adaptive_relevance_suggestions_active', true, MOCK_VALUES)); const wrapper = shallow(); expect(wrapper.find(SuggestionsTable).exists()).toBe(true); }); it('doesn\t render a suggestions table when suggestions are not active', () => { - setMockValues(set('engine.search_relevance_suggestions_active', false, MOCK_VALUES)); + setMockValues(set('engine.adaptive_relevance_suggestions_active', false, MOCK_VALUES)); const wrapper = shallow(); expect(wrapper.find(SuggestionsTable).exists()).toBe(false); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.tsx index a611ca88cefd43..f763c297ea1dec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.tsx @@ -19,10 +19,10 @@ import { CurationsLogic } from '../curations_logic'; export const CurationsOverview: React.FC = () => { const { curations } = useValues(CurationsLogic); const { - engine: { search_relevance_suggestions_active: searchRelevanceSuggestionsActive }, + engine: { adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive }, } = useValues(EngineLogic); - const shouldShowSuggestions = searchRelevanceSuggestionsActive; + const shouldShowSuggestions = adaptiveRelevanceSuggestionsActive; return ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx index de669298b11d95..29fbee8fa0242a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx @@ -79,7 +79,7 @@ export const CurationsSettings: React.FC = () => { {i18n.translate( 'xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTATitle', { - defaultMessage: 'Introducing automated curations', + defaultMessage: 'Introducing curations powered by adaptive relevance', } )} @@ -87,7 +87,7 @@ export const CurationsSettings: React.FC = () => { subtitle={ @@ -135,7 +135,7 @@ export const CurationsSettings: React.FC = () => { {i18n.translate( 'xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsTitle', { - defaultMessage: 'Automated Curations', + defaultMessage: 'Curations powered by adaptive relevance', } )} @@ -159,7 +159,7 @@ export const CurationsSettings: React.FC = () => { 'xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutDescription', { defaultMessage: - 'Automated curations require analytics to be enabled on your account.', + 'Adaptive relevance requires analytics to be enabled on your account.', } )}

@@ -178,7 +178,7 @@ export const CurationsSettings: React.FC = () => { 'xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsDescription', { defaultMessage: - "Suggested curations will monitor your engine's analytics and make automatic suggestions to help you deliver the most relevant results. Each suggested curation can be accepted, rejected, or modified.", + "App Search will monitor your engine's analytics and suggest changes to your curations to help you deliver the most relevant results. Each suggestion can be accepted, rejected, or modified.", } )} @@ -189,7 +189,7 @@ export const CurationsSettings: React.FC = () => { label={i18n.translate( 'xpack.enterpriseSearch.appSearch.curations.settings.enableautomaticCurationsSwitchLabel', { - defaultMessage: 'Enable automation suggestions', + defaultMessage: 'Enable suggestions', } )} checked={enabled} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts index b8aae9c39174d0..0d09f2d28f396b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts @@ -97,7 +97,7 @@ describe('CurationsSettingsLogic', () => { await nextTick(); expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions/settings' + '/internal/app_search/engines/some-engine/adaptive_relevance/settings' ); expect(CurationsSettingsLogic.actions.onCurationsSettingsLoad).toHaveBeenCalledWith({ enabled: true, @@ -204,7 +204,7 @@ describe('CurationsSettingsLogic', () => { await nextTick(); expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/search_relevance_suggestions/settings', + '/internal/app_search/engines/some-engine/adaptive_relevance/settings', { body: JSON.stringify({ curation: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.ts index 3984cbd024da46..692d893a8e22fd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.ts @@ -71,8 +71,8 @@ export const CurationsSettingsLogic = kea< const { engineName } = EngineLogic.values; try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/search_relevance_suggestions/settings` + const response = await http.get<{ curation: CurationsSettings }>( + `/internal/app_search/engines/${engineName}/adaptive_relevance/settings` ); actions.onCurationsSettingsLoad(response.curation); } catch (e) { @@ -95,8 +95,8 @@ export const CurationsSettingsLogic = kea< const { http } = HttpLogic.values; const { engineName } = EngineLogic.values; try { - const response = await http.put( - `/internal/app_search/engines/${engineName}/search_relevance_suggestions/settings`, + const response = await http.put<{ curation: CurationsSettings }>( + `/internal/app_search/engines/${engineName}/adaptive_relevance/settings`, { body: JSON.stringify({ curation: currationsSetting }), } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts index e92bc068824b02..5ea2f0fe7cf73d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts @@ -181,7 +181,10 @@ export const DocumentCreationLogic = kea< const promises = chunk(documents, CHUNK_SIZE).map((documentsChunk) => { const body = JSON.stringify({ documents: documentsChunk }); - return http.post(`/internal/app_search/engines/${engineName}/documents`, { body }); + return http.post( + `/internal/app_search/engines/${engineName}/documents`, + { body } + ); }); try { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts index 25c16a7df2c503..9c41c55f5033c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts @@ -59,7 +59,7 @@ export const DocumentDetailLogic = kea({ try { const { http } = HttpLogic.values; - const response = await http.get( + const response = await http.get<{ fields: FieldDetails[] }>( `/internal/app_search/engines/${engineName}/documents/${documentId}` ); actions.setFields(response.fields); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index bb30190833dd31..adcc6bc546629d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -43,7 +43,7 @@ describe('EngineLogic', () => { schema: { test: SchemaType.Text }, apiTokens: [], apiKey: 'some-key', - search_relevance_suggestions_active: true, + adaptive_relevance_suggestions_active: true, }; const DEFAULT_VALUES = { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts index aa5e15a3265b16..0cfe8d0c2f933d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts @@ -132,7 +132,9 @@ export const EngineLogic = kea>({ const { http } = HttpLogic.values; try { - const response = await http.get(`/internal/app_search/engines/${engineName}`); + const response = await http.get( + `/internal/app_search/engines/${engineName}` + ); actions.setEngineData(response); } catch (error) { if (error?.response?.status >= 400 && error?.response?.status < 500) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts index 0bfbc185b85f32..6faa749f958646 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts @@ -53,8 +53,8 @@ export interface EngineDetails extends Engine { isMeta: boolean; engine_count?: number; includedEngines?: EngineDetails[]; - search_relevance_suggestions?: SearchRelevanceSuggestionDetails; - search_relevance_suggestions_active: boolean; + adaptive_relevance_suggestions?: SearchRelevanceSuggestionDetails; + adaptive_relevance_suggestions_active: boolean; } interface ResultField { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.test.tsx index c65d95a5254ee4..2fb9bb255110dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.test.tsx @@ -19,12 +19,12 @@ import { SuggestedCurationsCallout } from './suggested_curations_callout'; const MOCK_VALUES = { engine: { - search_relevance_suggestions: { + adaptive_relevance_suggestions: { curation: { pending: 1, }, }, - search_relevance_suggestions_active: true, + adaptive_relevance_suggestions_active: true, }, }; @@ -44,7 +44,7 @@ describe('SuggestedCurationsCallout', () => { setMockValues({ ...MOCK_VALUES, engine: { - search_relevance_suggestions_active: true, + adaptive_relevance_suggestions_active: true, }, }); @@ -54,7 +54,7 @@ describe('SuggestedCurationsCallout', () => { }); it('is empty when suggestions are not active', () => { - const values = set('engine.search_relevance_suggestions_active', false, MOCK_VALUES); + const values = set('engine.adaptive_relevance_suggestions_active', false, MOCK_VALUES); setMockValues(values); const wrapper = shallow(); @@ -63,7 +63,7 @@ describe('SuggestedCurationsCallout', () => { }); it('is empty when no pending curations', () => { - const values = set('engine.search_relevance_suggestions.curation.pending', 0, MOCK_VALUES); + const values = set('engine.adaptive_relevance_suggestions.curation.pending', 0, MOCK_VALUES); setMockValues(values); const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx index 04b2d2b207e94a..e1f984581438fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx @@ -17,17 +17,17 @@ import { EngineLogic, generateEnginePath } from '../../engine'; export const SuggestedCurationsCallout: React.FC = () => { const { engine: { - search_relevance_suggestions: searchRelevanceSuggestions, - search_relevance_suggestions_active: searchRelevanceSuggestionsActive, + adaptive_relevance_suggestions: adaptiveRelevanceSuggestions, + adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive, }, } = useValues(EngineLogic); - const pendingCount = searchRelevanceSuggestions?.curation.pending; + const pendingCount = adaptiveRelevanceSuggestions?.curation.pending; if ( - typeof searchRelevanceSuggestions === 'undefined' || + typeof adaptiveRelevanceSuggestions === 'undefined' || pendingCount === 0 || - searchRelevanceSuggestionsActive === false + adaptiveRelevanceSuggestionsActive === false ) { return null; } @@ -46,7 +46,7 @@ export const SuggestedCurationsCallout: React.FC = () => { } )} buttonTo={generateEnginePath(ENGINE_CURATIONS_PATH)} - lastUpdatedTimestamp={searchRelevanceSuggestions.curation.last_updated} + lastUpdatedTimestamp={adaptiveRelevanceSuggestions.curation.last_updated} /> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts index 73cfef8530521a..878681a728e2b9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts @@ -84,7 +84,9 @@ export const EngineOverviewLogic = kea( + `/internal/app_search/engines/${engineName}/overview` + ); actions.onOverviewMetricsLoad(response); } catch (e) { flashAPIErrors(e); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts index 507b83cc8b5cfe..c6fb1a401c5913 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts @@ -119,7 +119,7 @@ export const EnginesLogic = kea>({ const { enginesMeta } = values; try { - const response = await http.get('/internal/app_search/engines', { + const response = await http.get('/internal/app_search/engines', { query: { type: 'indexed', 'page[current]': enginesMeta.page.current, @@ -136,7 +136,7 @@ export const EnginesLogic = kea>({ const { metaEnginesMeta } = values; try { - const response = await http.get('/internal/app_search/engines', { + const response = await http.get('/internal/app_search/engines', { query: { type: 'meta', 'page[current]': metaEnginesMeta.page.current, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index c94de74b5ab22e..fd2ed0693257d7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -246,7 +246,7 @@ export const RelevanceTuningLogic = kea< const url = `/internal/app_search/engines/${engineName}/search_settings/details`; try { - const response = await http.get(url); + const response = await http.get(url); actions.onInitializeRelevanceTuning({ ...response, searchSettings: { @@ -278,7 +278,7 @@ export const RelevanceTuningLogic = kea< const filteredBoosts = removeEmptyValueBoosts(boosts); try { - const response = await http.post(url, { + const response = await http.post<{ results: Result[] }>(url, { query: { query, }, @@ -313,7 +313,7 @@ export const RelevanceTuningLogic = kea< const url = `/internal/app_search/engines/${engineName}/search_settings`; try { - const response = await http.put(url, { + const response = await http.put(url, { body: JSON.stringify(removeBoostStateProps(values.searchSettings)), }); flashSuccessToast(UPDATE_SUCCESS_MESSAGE, { text: SUCCESS_CHANGES_MESSAGE }); @@ -337,7 +337,7 @@ export const RelevanceTuningLogic = kea< const url = `/internal/app_search/engines/${engineName}/search_settings/reset`; try { - const response = await http.post(url); + const response = await http.post(url); flashSuccessToast(DELETE_SUCCESS_MESSAGE, { text: SUCCESS_CHANGES_MESSAGE }); actions.onSearchSettingsSuccess(response); } catch (e) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts index 5ff153c3beb64c..99b3e6157e227d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts @@ -12,13 +12,14 @@ import { i18n } from '@kbn/i18n'; import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; -import { Schema, SchemaConflicts } from '../../../shared/schema/types'; +import { Schema, SchemaConflicts, SchemaType } from '../../../shared/schema/types'; import { EngineLogic } from '../engine'; import { DEFAULT_SNIPPET_SIZE } from './constants'; import { FieldResultSetting, FieldResultSettingObject, + ServerFieldResultSetting, ServerFieldResultSettingObject, } from './types'; @@ -299,7 +300,11 @@ export const ResultSettingsLogic = kea; + schemaConflicts?: SchemaConflicts; + searchSettings: { result_fields: Record }; + }>(url); actions.initializeResultFields(serverFieldResultSettings, schema, schemaConflicts); } catch (e) { @@ -322,7 +327,9 @@ export const ResultSettingsLogic = kea; + }>(url, { body: JSON.stringify({ result_fields: values.reducedServerResultFields, }), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts index d4c5a842daac9b..34f1478ad24fe0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts @@ -14,6 +14,7 @@ import { flashAPIErrors } from '../../../../shared/flash_messages'; import { HttpLogic } from '../../../../shared/http'; import { EngineLogic } from '../../engine'; +import { FieldValue } from '../../result/types'; import { SampleSearchResponse, ServerFieldResultSettingObject } from '../types'; const NO_RESULTS_MESSAGE = i18n.translate( @@ -71,7 +72,7 @@ export const SampleResponseLogic = kea> }>(url, { query: { query }, body: JSON.stringify({ page: { @@ -84,6 +85,7 @@ export const SampleResponseLogic = kea(route); actions.setRoleMappings(response); } catch (e) { flashAPIErrors(e); @@ -367,7 +367,7 @@ export const RoleMappingsLogic = kea(route); actions.setRoleMappingsData(response); } catch (e) { flashAPIErrors(e); @@ -466,7 +466,10 @@ export const RoleMappingsLogic = kea( + '/internal/app_search/single_user_role_mapping', + { body } + ); actions.setSingleUserRoleMapping(response); actions.setUserCreated(); actions.initializeRoleMappings(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.ts index fb50c9390a8d9d..e8ac47dbecbf2e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.ts @@ -53,7 +53,7 @@ export const ReindexJobLogic = kea( `/internal/app_search/engines/${engineName}/reindex_job/${id}` ); actions.onLoadSuccess(response); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts index b9107666a881bf..b299f827676d6c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts @@ -56,7 +56,9 @@ export const SchemaBaseLogic = kea( + `/internal/app_search/engines/${engineName}/schema` + ); actions.onSchemaLoad(response); } catch (e) { flashAPIErrors(e); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts index b26fd920645824..536a63700c5cd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts @@ -146,9 +146,10 @@ export const SchemaLogic = kea>({ clearFlashMessages(); try { - const response = await http.post(`/internal/app_search/engines/${engineName}/schema`, { - body: JSON.stringify(schema), - }); + const response = await http.post( + `/internal/app_search/engines/${engineName}/schema`, + { body: JSON.stringify(schema) } + ); actions.onSchemaLoad(response); flashSuccessToast(successMessage || UPDATE_SCHEMA_SUCCESS); } catch (e) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts index 424baedf210f50..1b14b89f81a4ba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts @@ -61,9 +61,10 @@ export const SearchLogic = kea>({ const { engineName } = EngineLogic.values; try { - const response = await http.post(`/internal/app_search/engines/${engineName}/search`, { - query: { query }, - }); + const response = await http.post<{ results: Result[] }>( + `/internal/app_search/engines/${engineName}/search`, + { query: { query } } + ); actions.onSearch(response); } catch (e) { flashAPIErrors(e); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts index 7a3e429d842f8b..1466cfa1ff9b73 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts @@ -106,7 +106,11 @@ export const SearchUILogic = kea> const url = `/internal/app_search/engines/${engineName}/search_ui/field_config`; try { - const initialFieldValues = await http.get(url); + const initialFieldValues = await http.get< + InitialFieldValues & { + defaultValues: Pick; + } + >(url); const { defaultValues: { urlField, titleField }, validFields, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts index 123a2e50fdf2f5..906877e79fedf3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts @@ -115,12 +115,15 @@ export const SynonymsLogic = kea> const { engineName } = EngineLogic.values; try { - const response = await http.get(`/internal/app_search/engines/${engineName}/synonyms`, { - query: { - 'page[current]': meta.page.current, - 'page[size]': meta.page.size, - }, - }); + const response = await http.get( + `/internal/app_search/engines/${engineName}/synonyms`, + { + query: { + 'page[current]': meta.page.current, + 'page[size]': meta.page.size, + }, + } + ); actions.onSynonymsLoad(response); } catch (e) { flashAPIErrors(e); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts index ca3e67129846bb..0efa8880cca222 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts @@ -6,5 +6,5 @@ */ export * from '../../../common/types/app_search'; -export { Role, RoleTypes, AbilityTypes, ASRoleMapping, AdvanceRoleType } from './utils/role'; -export { Engine } from './components/engine/types'; +export type { Role, RoleTypes, AbilityTypes, ASRoleMapping, AdvanceRoleType } from './utils/role'; +export type { Engine } from './components/engine/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 04fa3ae681045f..fef40ed00bfc13 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -96,7 +96,7 @@ export const renderApp = ( * Render function for Kibana's header action menu chrome - * reusable by any Enterprise Search plugin simply by passing in * a custom HeaderActions component (e.g., WorkplaceSearchHeaderActions) - * @see https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md + * @see https://github.com/elastic/kibana/blob/main/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md */ export const renderHeaderActions = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts index 38a5d6e8b0b30f..097d38e0691c59 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts @@ -7,7 +7,7 @@ export { FlashMessages, Toasts } from './flash_messages'; export { FlashMessagesLogic, mountFlashMessagesLogic } from './flash_messages_logic'; -export { IFlashMessage } from './types'; +export type { IFlashMessage } from './types'; export { flashAPIErrors } from './handle_api_errors'; export { setSuccessMessage, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts index ca24be5864b84b..650aa00d1801d9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts @@ -13,7 +13,7 @@ import { /** * Generate a document title that generally follows our breadcrumb trails - * https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.chromedoctitle.md + * https://github.com/elastic/kibana/blob/main/docs/development/core/public/kibana-plugin-core-public.chromedoctitle.md */ type Title = string[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index e639f9d22fb4be..0d7e14f063248e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -22,7 +22,7 @@ import { enterpriseSearchTitle, appSearchTitle, workplaceSearchTitle } from './g /** * Helpers for setting Kibana chrome (breadcrumbs, doc titles) on React view mount - * @see https://github.com/elastic/kibana/blob/master/src/core/public/chrome/chrome_service.tsx + * @see https://github.com/elastic/kibana/blob/main/src/core/public/chrome/chrome_service.tsx * * Example usage (don't forget to i18n.translate() page titles!): * diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts index 41f8869ad5f61b..79919e925c625e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { EnterpriseSearchPageTemplate, PageTemplateProps } from './page_template'; +export type { PageTemplateProps } from './page_template'; +export { EnterpriseSearchPageTemplate } from './page_template'; export { generateNavLink } from './nav_link_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx index 5b02756e44b524..8d480b69b3fe53 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx @@ -79,7 +79,7 @@ describe('EnterpriseSearchPageTemplate', () => { expect(wrapper.find('.emptyState').exists()).toBe(true); expect(wrapper.find('.test').exists()).toBe(false); - // @see https://github.com/elastic/kibana/blob/master/dev_docs/tutorials/kibana_page_template.mdx#isemptystate + // @see https://github.com/elastic/kibana/blob/main/dev_docs/tutorials/kibana_page_template.mdx#isemptystate // if you want to use KibanaPageTemplate's `isEmptyState` without a custom emptyState }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index affec119215455..7528fa14b7ae4f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -32,7 +32,7 @@ import './page_template.scss'; * WorkplaceSearchPageTemplate sitting on top of this template (:nesting_dolls:), * which in turn manages individual product-specific concerns (e.g. side navs, telemetry, etc.) * - * @see https://github.com/elastic/kibana/tree/master/src/plugins/kibana_react/public/page_template + * @see https://github.com/elastic/kibana/tree/main/src/plugins/kibana_react/public/page_template * @see https://elastic.github.io/eui/#/layout/page */ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx index e5dabdd51e543b..c1f4262881bd23 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx @@ -17,7 +17,7 @@ import { LOGS_SOURCE_ID } from '../../../../common/constants'; * default for timestamps. All other props get passed as-is to the underlying LogStream. * * Documentation links for reference: - * - https://github.com/elastic/kibana/blob/master/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx + * - https://github.com/elastic/kibana/blob/main/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx * - Run `yarn storybook infra` for live docs */ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts index fe76c13c2b7071..a05792d5fb5450 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts @@ -6,8 +6,10 @@ */ export { letBrowserHandleEvent } from './link_events'; -export { createHref, CreateHrefOptions } from './create_href'; -export { generateReactRouterProps, ReactRouterProps } from './generate_react_router_props'; +export type { CreateHrefOptions } from './create_href'; +export { createHref } from './create_href'; +export type { ReactRouterProps } from './generate_react_router_props'; +export { generateReactRouterProps } from './generate_react_router_props'; export { EuiLinkTo, EuiButtonTo, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts index d2229b428932f4..0a99b0991f4ed6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts @@ -382,7 +382,7 @@ export const INVITATION_PENDING_LABEL = i18n.translate( export const ROLE_MODAL_TEXT = i18n.translate('xpack.enterpriseSearch.roleMapping.roleModalText', { defaultMessage: - 'Removing a role mapping revokes access to any user corresponding to the mapping attributes, but may not take effect immediately for SAML-governed roles. Users with an active SAML session will retain access until it expires.', + 'Removing a role mapping could revoke access to the currently logged-in user. Before proceeding, verify that the currently logged-in user has the appropriate access level via a different role mapping to avoid undesired behavior. This action may not take effect immediately for SAML-governed roles. Users with an active SAML session will retain access until it expires.', }); export const USER_MODAL_TITLE = (username: string) => diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts index 9cb1061993dc31..71c993dca9cb95 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts @@ -83,7 +83,9 @@ export const GenericEndpointInlineEditableTableLogic = kea< const { addRoute, onAdd, dataProperty } = props; try { - const response = await http.post(addRoute, { body: JSON.stringify(item) }); + const response = await http.post>(addRoute, { + body: JSON.stringify(item), + }); const itemsFromResponse = response[dataProperty]; onAdd(item, itemsFromResponse); @@ -99,7 +101,7 @@ export const GenericEndpointInlineEditableTableLogic = kea< const { deleteRoute, onDelete, dataProperty } = props; try { - const response = await http.delete(deleteRoute(item)); + const response = await http.delete>(deleteRoute(item)); const itemsFromResponse = response[dataProperty]; onDelete(item, itemsFromResponse); @@ -116,7 +118,7 @@ export const GenericEndpointInlineEditableTableLogic = kea< const dataToSubmit = stripIdAndCreatedAtFromItem(item); try { - const response = await http.put(updateRoute(item), { + const response = await http.put>(updateRoute(item), { body: JSON.stringify(dataToSubmit), }); const itemsFromResponse = response[dataProperty]; @@ -141,7 +143,7 @@ export const GenericEndpointInlineEditableTableLogic = kea< try { actions.setLoading(); - const response = await http.put(reorderRoute, { + const response = await http.put>(reorderRoute, { body: JSON.stringify({ [dataProperty]: reorderedItemIds }), }); const itemsFromResponse = response[dataProperty]; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/index.ts index 9bc16553b1a0cb..7b0ba4206cff49 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { SourceRow, ISourceRow } from './source_row'; +export type { ISourceRow } from './source_row'; +export { SourceRow } from './source_row'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index 6f09cbb15c7db0..05a5fd5a73fe8f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -392,7 +392,7 @@ export const AddSourceLogic = kea(route); actions.setSourceConfigData(response); } catch (e) { flashAPIErrors(e); @@ -415,7 +415,7 @@ export const AddSourceLogic = kea(route, { query }); actions.setSourceConnectData(response); successCallback(response.oauthUrl); } catch (e) { @@ -435,7 +435,7 @@ export const AddSourceLogic = kea(route, { query }); actions.setSourceConnectData(response); } catch (e) { flashAPIErrors(e); @@ -449,7 +449,7 @@ export const AddSourceLogic = kea(route); actions.setPreContentSourceConfigData(response); } catch (e) { flashAPIErrors(e); @@ -482,7 +482,7 @@ export const AddSourceLogic = kea(route, { body: JSON.stringify(params), }); if (successCallback) successCallback(); @@ -527,7 +527,13 @@ export const AddSourceLogic = kea(route, { query }); const { serviceName, indexPermissions, serviceType, preContentSourceId, hasConfigureStep } = response; @@ -540,8 +546,8 @@ export const AddSourceLogic = kea { @@ -574,7 +580,7 @@ export const AddSourceLogic = kea params[key] === undefined && delete params[key]); try { - const response = await HttpLogic.values.http.post(route, { + const response = await HttpLogic.values.http.post(route, { body: JSON.stringify({ ...params }), }); actions.setCustomSourceData(response); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts index 2d7667e08d8f85..d62bd6252f1306 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts @@ -381,8 +381,10 @@ export const DisplaySettingsLogic = kea< : `/internal/workplace_search/account/sources/${sourceId}/display_settings/config`; try { - const response = await HttpLogic.values.http.get(route); + const response = await HttpLogic.values.http.get(route); actions.onInitializeDisplaySettings({ + // isOrganization is not typed + // @ts-expect-error TS2345 isOrganization, sourceId, serverRoute: route, @@ -396,9 +398,10 @@ export const DisplaySettingsLogic = kea< const { searchResultConfig, serverRoute } = values; try { - const response = await HttpLogic.values.http.post(serverRoute, { - body: JSON.stringify({ ...searchResultConfig }), - }); + const response = await HttpLogic.values.http.post( + serverRoute, + { body: JSON.stringify({ ...searchResultConfig }) } + ); actions.setServerResponseData(response); } catch (e) { flashAPIErrors(e); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts index d664197afaa265..218bb368e848f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts @@ -272,7 +272,9 @@ export const SchemaLogic = kea>({ : `/internal/workplace_search/account/sources/${sourceId}/schemas`; try { - const response = await http.get(route); + const response = await http.get(route); + // TODO: fix + // @ts-expect-error TS2783 actions.onInitializeSchema({ sourceId, ...response }); } catch (e) { flashAPIErrors(e); @@ -287,7 +289,7 @@ export const SchemaLogic = kea>({ try { await actions.initializeSchema(); - const response = await http.get(route); + const response = await http.get(route); actions.onInitializeSchemaFieldErrors({ fieldCoercionErrors: response.fieldCoercionErrors, }); @@ -339,7 +341,7 @@ export const SchemaLogic = kea>({ actions.resetMostRecentIndexJob(emptyReindexJob); try { - const response = await http.post(route, { + const response = await http.post(route, { body: JSON.stringify({ ...updatedSchema }), }); actions.onSchemaSetSuccess(response); 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 87a55f0e7dd3aa..2f4fdca44d4415 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 @@ -20,10 +20,12 @@ import { BLOCKED_TIME_WINDOWS_PATH, getContentSourcePath, } from '../../../../routes'; + import { BlockedWindow, DayOfWeek, IndexingSchedule, + ContentSourceFullData, SyncJobType, TimeUnit, } from '../../../../types'; @@ -313,7 +315,7 @@ export const SynchronizationLogic = kea< const route = `/internal/workplace_search/org/sources/${sourceId}/settings`; try { - const response = await HttpLogic.values.http.patch(route, { + const response = await HttpLogic.values.http.patch(route, { body: JSON.stringify(body), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 0b67e3f2da79bc..e97d48889d8092 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -159,7 +159,9 @@ export const SourceLogic = kea>({ : `/internal/workplace_search/account/sources/${sourceId}`; try { - const response = await HttpLogic.values.http.get(route); + const response = await HttpLogic.values.http.get< + ContentSourceFullData & { errors?: string } + >(route); actions.setContentSource(response); if (response.isFederatedSource) { actions.initializeFederatedSummary(sourceId); @@ -186,7 +188,7 @@ export const SourceLogic = kea>({ initializeFederatedSummary: async ({ sourceId }) => { const route = `/internal/workplace_search/account/sources/${sourceId}/federated_summary`; try { - const response = await HttpLogic.values.http.get(route); + const response = await HttpLogic.values.http.get<{ summary: DocumentSummaryItem[] }>(route); actions.onUpdateSummary(response.summary); } catch (e) { flashAPIErrors(e); @@ -206,7 +208,7 @@ export const SourceLogic = kea>({ } = values; try { - const response = await HttpLogic.values.http.post(route, { + const response = await HttpLogic.values.http.post(route, { body: JSON.stringify({ query, page }), }); actions.setSearchResults(response); @@ -221,7 +223,7 @@ export const SourceLogic = kea>({ : `/internal/workplace_search/account/sources/${sourceId}/settings`; try { - const response = await HttpLogic.values.http.patch(route, { + const response = await HttpLogic.values.http.patch<{ name: string }>(route, { body: JSON.stringify({ content_source: source }), }); if (source.name) { @@ -239,7 +241,7 @@ export const SourceLogic = kea>({ : `/internal/workplace_search/account/sources/${sourceId}`; try { - const response = await HttpLogic.values.http.delete(route); + const response = await HttpLogic.values.http.delete<{ name: string }>(route); KibanaLogic.values.navigateToUrl(getSourcesPath(SOURCES_PATH, isOrganization)); flashSuccessToast( i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts index 53f16a303f70a4..8518485c98b242 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts @@ -26,6 +26,8 @@ describe('SourcesLogic', () => { const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; const { mount, unmount } = new LogicMounter(SourcesLogic); + const mockBreakpoint = jest.fn(); + const contentSource = contentSources[0]; const defaultValues = { @@ -65,6 +67,10 @@ describe('SourcesLogic', () => { mount(); }); + afterEach(() => { + jest.clearAllTimers(); + }); + it('has expected default values', () => { expect(SourcesLogic.values).toEqual(defaultValues); }); @@ -193,6 +199,30 @@ describe('SourcesLogic', () => { expect(flashAPIErrors).toHaveBeenCalledWith(error); }); + + it('handles early logic unmount gracefully in org context', async () => { + AppLogic.values.isOrganization = true; + const promise = Promise.resolve(contentSource); + http.get.mockReturnValue(promise); + + SourcesLogic.actions.initializeSources(); + unmount(); + await promise; + + expect(flashAPIErrors).not.toHaveBeenCalled(); + }); + + it('handles early logic unmount gracefully in account context', async () => { + AppLogic.values.isOrganization = false; + const promise = Promise.resolve(contentSource); + http.get.mockReturnValue(promise); + + SourcesLogic.actions.initializeSources(); + unmount(); + await promise; + + expect(flashAPIErrors).not.toHaveBeenCalled(); + }); }); describe('setSourceSearchability', () => { @@ -246,7 +276,25 @@ describe('SourcesLogic', () => { }); describe('pollForSourceStatusChanges', () => { - it('calls API and sets values', async () => { + it('calls API and sets values if there are no server statuses yet in the logic', async () => { + AppLogic.values.isOrganization = true; + + const setServerSourceStatusesSpy = jest.spyOn( + SourcesLogic.actions, + 'setServerSourceStatuses' + ); + const promise = Promise.resolve(contentSources); + http.get.mockReturnValue(promise); + SourcesLogic.actions.pollForSourceStatusChanges(); + + jest.advanceTimersByTime(POLLING_INTERVAL); + + expect(http.get).toHaveBeenCalledWith('/internal/workplace_search/org/sources/status'); + await promise; + expect(setServerSourceStatusesSpy).toHaveBeenCalledWith(contentSources); + }); + + it('calls API and sets values if server statuses are already in the logic', async () => { AppLogic.values.isOrganization = true; SourcesLogic.actions.setServerSourceStatuses(serverStatuses); @@ -264,6 +312,20 @@ describe('SourcesLogic', () => { await promise; expect(setServerSourceStatusesSpy).toHaveBeenCalledWith(contentSources); }); + + it('handles early logic unmount gracefully', async () => { + AppLogic.values.isOrganization = true; + SourcesLogic.actions.setServerSourceStatuses(serverStatuses); + const promise = Promise.resolve(contentSources); + http.get.mockReturnValue(promise); + + SourcesLogic.actions.pollForSourceStatusChanges(); + jest.advanceTimersByTime(POLLING_INTERVAL); + unmount(); + await promise; + + expect(flashAPIErrors).not.toHaveBeenCalled(); + }); }); it('resetSourcesState', () => { @@ -290,7 +352,7 @@ describe('SourcesLogic', () => { ); const promise = Promise.resolve(contentSources); http.get.mockReturnValue(promise); - fetchSourceStatuses(true); + fetchSourceStatuses(true, mockBreakpoint); expect(http.get).toHaveBeenCalledWith('/internal/workplace_search/org/sources/status'); await promise; @@ -300,7 +362,7 @@ describe('SourcesLogic', () => { it('calls API (account)', async () => { const promise = Promise.resolve(contentSource); http.get.mockReturnValue(promise); - fetchSourceStatuses(false); + fetchSourceStatuses(false, mockBreakpoint); expect(http.get).toHaveBeenCalledWith('/internal/workplace_search/account/sources/status'); }); @@ -314,7 +376,7 @@ describe('SourcesLogic', () => { }; const promise = Promise.reject(error); http.get.mockReturnValue(promise); - fetchSourceStatuses(true); + fetchSourceStatuses(true, mockBreakpoint); await expectedAsyncError(promise); expect(flashAPIErrors).toHaveBeenCalledWith(error); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts index 9a88f6238bcc41..90b1f83281e942 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { kea, MakeLogicType } from 'kea'; +import { kea, MakeLogicType, isBreakpoint } from 'kea'; +import type { BreakPointFunction } from 'kea'; import { cloneDeep, findIndex } from 'lodash'; import { i18n } from '@kbn/i18n'; @@ -155,34 +156,39 @@ export const SourcesLogic = kea>( ], }), listeners: ({ actions, values }) => ({ - initializeSources: async () => { + initializeSources: async (_, breakpoint) => { const { isOrganization } = AppLogic.values; const route = isOrganization ? '/internal/workplace_search/org/sources' : '/internal/workplace_search/account/sources'; try { - const response = await HttpLogic.values.http.get(route); + const response = await HttpLogic.values.http.get(route); + breakpoint(); // Prevents errors if logic unmounts while fetching actions.pollForSourceStatusChanges(); actions.onInitializeSources(response); } catch (e) { - flashAPIErrors(e); + if (isBreakpoint(e)) { + return; // do not continue if logic is unmounted + } else { + flashAPIErrors(e); + } } if (isOrganization && !values.serverStatuses) { // We want to get the initial statuses from the server to compare our polling results to. - const sourceStatuses = await fetchSourceStatuses(isOrganization); + const sourceStatuses = await fetchSourceStatuses(isOrganization, breakpoint); actions.setServerSourceStatuses(sourceStatuses); } }, // We poll the server and if the status update, we trigger a new fetch of the sources. - pollForSourceStatusChanges: () => { + pollForSourceStatusChanges: (_, breakpoint) => { const { isOrganization } = AppLogic.values; if (!isOrganization) return; const serverStatuses = values.serverStatuses; pollingInterval = window.setInterval(async () => { - const sourceStatuses = await fetchSourceStatuses(isOrganization); + const sourceStatuses = await fetchSourceStatuses(isOrganization, breakpoint); sourceStatuses.some((source: ContentSourceStatus) => { if (serverStatuses && serverStatuses[source.id] !== source.status.status) { @@ -240,20 +246,29 @@ export const SourcesLogic = kea>( }), }); -export const fetchSourceStatuses = async (isOrganization: boolean) => { +export const fetchSourceStatuses = async ( + isOrganization: boolean, + breakpoint: BreakPointFunction +) => { const route = isOrganization ? '/internal/workplace_search/org/sources/status' : '/internal/workplace_search/account/sources/status'; let response; try { - response = await HttpLogic.values.http.get(route); + response = await HttpLogic.values.http.get(route); + breakpoint(); SourcesLogic.actions.setServerSourceStatuses(response); } catch (e) { - flashAPIErrors(e); + if (isBreakpoint(e)) { + // Do nothing, silence the error + } else { + flashAPIErrors(e); + } } - return response; + // TODO: remove casting. return type should be ContentSourceStatus[] | undefined + return response as ContentSourceStatus[]; }; const updateSourcesOnToggle = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts index 3ba7d68d0b3e94..6e465854ff44f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts @@ -174,7 +174,7 @@ export const GroupLogic = kea>({ listeners: ({ actions, values }) => ({ initializeGroup: async ({ groupId }) => { try { - const response = await HttpLogic.values.http.get( + const response = await HttpLogic.values.http.get( `/internal/workplace_search/groups/${groupId}` ); actions.onInitializeGroup(response); @@ -220,7 +220,7 @@ export const GroupLogic = kea>({ } = values; try { - const response = await HttpLogic.values.http.put( + const response = await HttpLogic.values.http.put( `/internal/workplace_search/groups/${id}`, { body: JSON.stringify({ group: { name: groupNameInputValue } }), @@ -247,7 +247,7 @@ export const GroupLogic = kea>({ } = values; try { - const response = await HttpLogic.values.http.post( + const response = await HttpLogic.values.http.post( `/internal/workplace_search/groups/${id}/share`, { body: JSON.stringify({ content_source_ids: selectedGroupSources }), @@ -279,7 +279,7 @@ export const GroupLogic = kea>({ ); try { - const response = await HttpLogic.values.http.put( + const response = await HttpLogic.values.http.put( `/internal/workplace_search/groups/${id}/boosts`, { body: JSON.stringify({ content_source_boosts: boosts }), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.ts index 19c16f6147dcca..c14538346ad31c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.ts @@ -255,7 +255,9 @@ export const GroupsLogic = kea>({ listeners: ({ actions, values }) => ({ initializeGroups: async () => { try { - const response = await HttpLogic.values.http.get('/internal/workplace_search/groups'); + const response = await HttpLogic.values.http.get( + '/internal/workplace_search/groups' + ); actions.onInitializeGroups(response); } catch (e) { flashAPIErrors(e); @@ -288,7 +290,7 @@ export const GroupsLogic = kea>({ }; try { - const response = await HttpLogic.values.http.post( + const response = await HttpLogic.values.http.post( '/internal/workplace_search/groups/search', { body: JSON.stringify({ @@ -307,7 +309,7 @@ export const GroupsLogic = kea>({ fetchGroupUsers: async ({ groupId }) => { actions.setAllGroupLoading(true); try { - const response = await HttpLogic.values.http.get( + const response = await HttpLogic.values.http.get( `/internal/workplace_search/groups/${groupId}/group_users` ); actions.setGroupUsers(response); @@ -317,10 +319,13 @@ export const GroupsLogic = kea>({ }, saveNewGroup: async () => { try { - const response = await HttpLogic.values.http.post('/internal/workplace_search/groups', { - body: JSON.stringify({ group_name: values.newGroupName }), - headers, - }); + const response = await HttpLogic.values.http.post( + '/internal/workplace_search/groups', + { + body: JSON.stringify({ group_name: values.newGroupName }), + headers, + } + ); actions.getSearchResults(true); const SUCCESS_MESSAGE = i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts index e21cde02481a0c..95a8dd4b3fbad6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts @@ -96,7 +96,7 @@ export const OAuthAuthorizeLogic = kea(oauthAuthorizeRoute, { query }); if (response.status === 'redirect') { window.location.replace(response.redirect_uri); @@ -113,7 +113,7 @@ export const OAuthAuthorizeLogic = kea(oauthAuthorizeRoute, { body: JSON.stringify({ client_id: cachedPreAuth.clientId, response_type: cachedPreAuth.responseType, @@ -135,7 +135,7 @@ export const OAuthAuthorizeLogic = kea(oauthAuthorizeRoute, { body: JSON.stringify({ client_id: cachedPreAuth.clientId, response_type: cachedPreAuth.responseType, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts index 1d79e66e778fb0..494f46a8efb3ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts @@ -97,7 +97,9 @@ export const OverviewLogic = kea> listeners: ({ actions }) => ({ initializeOverview: async () => { try { - const response = await HttpLogic.values.http.get('/internal/workplace_search/overview'); + const response = await HttpLogic.values.http.get( + '/internal/workplace_search/overview' + ); actions.setServerData(response); } catch (e) { flashAPIErrors(e); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts index 29103aa0c39af6..092b70e1f4ae88 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts @@ -356,7 +356,9 @@ export const RoleMappingsLogic = kea(route); actions.setRoleMappings(response); } catch (e) { flashAPIErrors(e); @@ -367,7 +369,7 @@ export const RoleMappingsLogic = kea(route); actions.setRoleMappingsData(response); } catch (e) { flashAPIErrors(e); @@ -466,11 +468,9 @@ export const RoleMappingsLogic = kea( '/internal/workplace_search/org/single_user_role_mapping', - { - body, - } + { body } ); actions.setSingleUserRoleMapping(response); actions.setUserCreated(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts index 852d0370e00b37..9b80882ff41505 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts @@ -71,7 +71,7 @@ export const SearchAuthorizeLogic = kea< }; try { - const response = await http.get(oauthAuthorizeRoute, { query }); + const response = await http.get(oauthAuthorizeRoute, { query }); if (response.status === 'redirect') { window.location.replace(response.redirect_uri); @@ -91,7 +91,7 @@ export const SearchAuthorizeLogic = kea< const { cachedPreAuth } = values; try { - const response = await http.post(oauthAuthorizeRoute, { + const response = await http.post<{ redirect_uri: string }>(oauthAuthorizeRoute, { body: JSON.stringify({ client_id: cachedPreAuth.clientId, response_type: cachedPreAuth.responseType, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.ts index e06504edbf0acf..6484677b6df6ba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.ts @@ -138,7 +138,7 @@ export const SecurityLogic = kea> const { http } = HttpLogic.values; try { - const response = await http.get(route); + const response = await http.get(route); actions.setServerProps(response); } catch (e) { flashAPIErrors(e); @@ -151,7 +151,7 @@ export const SecurityLogic = kea> const { http } = HttpLogic.values; try { - const response = await http.patch(route, { body }); + const response = await http.patch(route, { body }); actions.setSourceRestrictionsUpdated(response); flashSuccessToast(SOURCE_RESTRICTIONS_SUCCESS_MESSAGE); AppLogic.actions.setSourceRestriction(isEnabled); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.ts index 4faaca58ab8a07..64dfa3f8e13bb6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.ts @@ -200,7 +200,7 @@ export const SettingsLogic = kea> const route = '/internal/workplace_search/org/settings'; try { - const response = await http.get(route); + const response = await http.get(route); actions.setServerProps(response); } catch (e) { flashAPIErrors(e); @@ -211,7 +211,7 @@ export const SettingsLogic = kea> const route = '/internal/workplace_search/org/settings/connectors'; try { - const response = await http.get(route); + const response = await http.get(route); actions.onInitializeConnectors(response); } catch (e) { flashAPIErrors(e); @@ -225,7 +225,9 @@ export const SettingsLogic = kea> const body = JSON.stringify({ name }); try { - const response = await http.put(route, { body }); + const response = await http.put<{ + organizationName: string; + }>(route, { body }); actions.setUpdatedName(response); flashSuccessToast(ORG_UPDATED_MESSAGE); AppLogic.actions.setOrgName(name); @@ -240,7 +242,7 @@ export const SettingsLogic = kea> const body = JSON.stringify({ logo }); try { - const response = await http.put(imageRoute, { body }); + const response = await http.put<{ logo: string | null }>(imageRoute, { body }); actions.setLogo(response.logo); flashSuccessToast(ORG_UPDATED_MESSAGE); } catch (e) { @@ -255,7 +257,7 @@ export const SettingsLogic = kea> const body = JSON.stringify({ icon }); try { - const response = await http.put(imageRoute, { body }); + const response = await http.put<{ icon: string | null }>(imageRoute, { body }); actions.setIcon(response.icon); flashSuccessToast(ORG_UPDATED_MESSAGE); } catch (e) { @@ -275,7 +277,9 @@ export const SettingsLogic = kea> clearFlashMessages(); try { - const response = await http.put(route, { body }); + const response = await http.put<{ + oauthApplication: IOauthApplication; + }>(route, { body }); actions.setUpdatedOauthApplication(response); flashSuccessToast(OAUTH_APP_UPDATED_MESSAGE); } catch (e) { diff --git a/x-pack/plugins/enterprise_search/server/integrations.ts b/x-pack/plugins/enterprise_search/server/integrations.ts index eee5cdc3aaec3e..633f5638cc05c6 100644 --- a/x-pack/plugins/enterprise_search/server/integrations.ts +++ b/x-pack/plugins/enterprise_search/server/integrations.ts @@ -30,7 +30,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your files and folders stored on Box with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], }, { id: 'confluence_cloud', @@ -47,7 +47,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your organizational content on Confluence Cloud with Workplace Search.', } ), - categories: ['knowledge_platform'], + categories: ['productivity'], }, { id: 'confluence_server', @@ -64,7 +64,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your organizational content on Confluence Server with Workplace Search.', } ), - categories: ['knowledge_platform'], + categories: ['productivity'], }, { id: 'dropbox', @@ -78,7 +78,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your files and folders stored on Dropbox with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], }, { id: 'github', @@ -91,7 +91,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your projects and repos on GitHub with Workplace Search.', } ), - categories: ['software_development'], + categories: ['productivity'], }, { id: 'github_enterprise_server', @@ -108,7 +108,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your projects and repos on GitHub Enterprise Server with Workplace Search.', } ), - categories: ['software_development'], + categories: ['productivity'], }, { id: 'gmail', @@ -121,7 +121,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your emails managed by Gmail with Workplace Search.', } ), - categories: ['communication'], + categories: ['communications'], }, { id: 'google_drive', @@ -134,7 +134,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your documents on Google Drive with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], }, { id: 'jira_cloud', @@ -147,7 +147,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your project workflow on Jira Cloud with Workplace Search.', } ), - categories: ['project_management'], + categories: ['productivity'], }, { id: 'jira_server', @@ -160,7 +160,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your project workflow on Jira Server with Workplace Search.', } ), - categories: ['project_management'], + categories: ['productivity'], }, { id: 'onedrive', @@ -173,7 +173,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your files stored on OneDrive with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], uiInternalPath: '/app/enterprise_search/workplace_search/sources/add/one_drive', }, { @@ -187,7 +187,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your content on Salesforce with Workplace Search.', } ), - categories: ['crm'], + categories: ['productivity'], }, { id: 'salesforce_sandbox', @@ -203,7 +203,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your content on Salesforce Sandbox with Workplace Search.', } ), - categories: ['crm'], + categories: ['productivity'], }, { id: 'servicenow', @@ -216,7 +216,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your content on ServiceNow with Workplace Search.', } ), - categories: ['enterprise_management'], + categories: ['productivity'], }, { id: 'sharepoint_online', @@ -232,7 +232,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your files stored on SharePoint Online with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], uiInternalPath: '/app/enterprise_search/workplace_search/sources/add/share_point', }, { @@ -246,7 +246,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your messages on Slack with Workplace Search.', } ), - categories: ['communication'], + categories: ['communications'], }, { id: 'zendesk', @@ -259,7 +259,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your tickets on Zendesk with Workplace Search.', } ), - categories: ['customer_support'], + categories: ['communications'], }, { id: 'custom_api_source', diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_http_agent.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_http_agent.ts index 89210def248b31..b1179183111204 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_http_agent.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_http_agent.ts @@ -59,7 +59,7 @@ class EnterpriseSearchHttpAgent { /* * Convert verificationMode to rejectUnauthorized for more consistent config settings * with the rest of Kibana - * @see https://github.com/elastic/kibana/blob/master/x-pack/plugins/actions/server/builtin_action_types/lib/get_node_tls_options.ts + * @see https://github.com/elastic/kibana/blob/main/x-pack/plugins/actions/server/builtin_action_types/lib/get_node_tls_options.ts */ getAgentOptions(verificationMode: 'full' | 'certificate' | 'none') { const agentOptions: AgentOptions = {}; diff --git a/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts index 87defff89bad4a..554fe127b4353c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts +++ b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts @@ -33,8 +33,8 @@ interface ConfigWithoutBodyOptions * The will pass a String Buffer to the route handler. The proper way to validate this when validation * is enabled to to use `body: schema.buffer()`. * - * @see https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.routeconfigoptionsbody.md - * @see https://github.com/elastic/kibana/blob/master/packages/kbn-config-schema/README.md#schemabuffer + * @see https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.routeconfigoptionsbody.md + * @see https://github.com/elastic/kibana/blob/main/packages/kbn-config-schema/README.md#schemabuffer * * Example: * router.put({ diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index f40046c22b419a..29a744e487f404 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -172,7 +172,7 @@ export class EnterpriseSearchPlugin implements Plugin { /* * Register logs source configuration, used by LogStream components - * @see https://github.com/elastic/kibana/blob/master/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx#with-a-source-configuration + * @see https://github.com/elastic/kibana/blob/main/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx#with-a-source-configuration */ infra.defineInternalSourceConfiguration(LOGS_SOURCE_ID, { name: 'Enterprise Search Logs', diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.test.ts similarity index 67% rename from x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.test.ts rename to x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.test.ts index daab7c35596bf6..cec2262c95a2e5 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.test.ts @@ -7,17 +7,17 @@ import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; -import { registerSearchRelevanceSuggestionsRoutes } from './search_relevance_suggestions'; +import { registerSearchRelevanceSuggestionsRoutes } from './adaptive_relevance'; describe('search relevance insights routes', () => { beforeEach(() => { jest.clearAllMocks(); }); - describe('POST /internal/app_search/engines/{name}/search_relevance_suggestions', () => { + describe('POST /internal/app_search/engines/{name}/adaptive_relevance/suggestions', () => { const mockRouter = new MockRouter({ method: 'post', - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/suggestions', }); beforeEach(() => { @@ -33,15 +33,15 @@ describe('search relevance insights routes', () => { }); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/suggestions', }); }); }); - describe('PUT /internal/app_search/engines/{name}/search_relevance_suggestions', () => { + describe('PUT /internal/app_search/engines/{name}/adaptive_relevance/suggestions', () => { const mockRouter = new MockRouter({ method: 'put', - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/suggestions', }); beforeEach(() => { @@ -62,15 +62,15 @@ describe('search relevance insights routes', () => { }); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/suggestions', }); }); }); - describe('GET /internal/app_search/engines/{name}/search_relevance_suggestions/settings', () => { + describe('GET /internal/app_search/engines/{name}/adaptive_relevance/settings', () => { const mockRouter = new MockRouter({ method: 'get', - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/settings', }); beforeEach(() => { @@ -86,15 +86,15 @@ describe('search relevance insights routes', () => { }); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/settings', }); }); }); - describe('PUT /internal/app_search/engines/{name}/search_relevance_suggestions/settings', () => { + describe('PUT /internal/app_search/engines/{name}/adaptive_relevance/settings', () => { const mockRouter = new MockRouter({ method: 'put', - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/settings', }); beforeEach(() => { @@ -111,15 +111,15 @@ describe('search relevance insights routes', () => { }); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/settings', }); }); }); - describe('GET /internal/app_search/engines/{engineName}/search_relevance_suggestions/{query}', () => { + describe('GET /internal/app_search/engines/{engineName}/adaptive_relevance/suggestions/{query}', () => { const mockRouter = new MockRouter({ method: 'get', - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/{query}', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/suggestions/{query}', }); beforeEach(() => { @@ -136,7 +136,7 @@ describe('search relevance insights routes', () => { }); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/:engineName/search_relevance_suggestions/:query', + path: '/as/engines/:engineName/adaptive_relevance/suggestions/:query', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.ts similarity index 70% rename from x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.ts rename to x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.ts index 95b50a9c4971ef..02260d19186daa 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.ts @@ -17,7 +17,7 @@ export function registerSearchRelevanceSuggestionsRoutes({ }: RouteDependencies) { router.post( { - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/suggestions', validate: { params: schema.object({ engineName: schema.string(), @@ -35,13 +35,13 @@ export function registerSearchRelevanceSuggestionsRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/suggestions', }) ); router.put( skipBodyValidation({ - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/suggestions', validate: { params: schema.object({ engineName: schema.string(), @@ -49,13 +49,13 @@ export function registerSearchRelevanceSuggestionsRoutes({ }, }), enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/suggestions', }) ); router.get( { - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/settings', validate: { params: schema.object({ engineName: schema.string(), @@ -63,13 +63,13 @@ export function registerSearchRelevanceSuggestionsRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/settings', }) ); router.put( skipBodyValidation({ - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/settings', validate: { params: schema.object({ engineName: schema.string(), @@ -77,13 +77,13 @@ export function registerSearchRelevanceSuggestionsRoutes({ }, }), enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings', + path: '/api/as/v0/engines/:engineName/adaptive_relevance/settings', }) ); router.get( { - path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/{query}', + path: '/internal/app_search/engines/{engineName}/adaptive_relevance/suggestions/{query}', validate: { params: schema.object({ engineName: schema.string(), @@ -95,7 +95,7 @@ export function registerSearchRelevanceSuggestionsRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/as/engines/:engineName/search_relevance_suggestions/:query', + path: '/as/engines/:engineName/adaptive_relevance/suggestions/:query', }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts index b6ef8c8acafa5d..a7282e5dc6cc46 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts @@ -85,6 +85,9 @@ export function registerCurationsRoutes({ { path: '/internal/app_search/engines/{engineName}/curations/{curationId}', validate: { + query: schema.object({ + skip_record_analytics: schema.string(), + }), params: schema.object({ engineName: schema.string(), curationId: schema.string(), diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 737b21e6f5a929..602d8c48d520ef 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -7,6 +7,7 @@ import { RouteDependencies } from '../../plugin'; +import { registerSearchRelevanceSuggestionsRoutes } from './adaptive_relevance'; import { registerAnalyticsRoutes } from './analytics'; import { registerApiLogsRoutes } from './api_logs'; import { registerCrawlerRoutes } from './crawler'; @@ -22,7 +23,6 @@ import { registerResultSettingsRoutes } from './result_settings'; import { registerRoleMappingsRoutes } from './role_mappings'; import { registerSchemaRoutes } from './schema'; import { registerSearchRoutes } from './search'; -import { registerSearchRelevanceSuggestionsRoutes } from './search_relevance_suggestions'; import { registerSearchSettingsRoutes } from './search_settings'; import { registerSearchUIRoutes } from './search_ui'; import { registerSettingsRoutes } from './settings'; diff --git a/x-pack/plugins/event_log/README.md b/x-pack/plugins/event_log/README.md index 61f79c662fad6b..0aac72d734f049 100644 --- a/x-pack/plugins/event_log/README.md +++ b/x-pack/plugins/event_log/README.md @@ -427,7 +427,7 @@ yarn test:jest x-pack/plugins/event_log --watch ### API Integration tests -See: [`x-pack/test/plugin_api_integration/test_suites/event_log`](https://github.com/elastic/kibana/tree/master/x-pack/test/plugin_api_integration/test_suites/event_log). +See: [`x-pack/test/plugin_api_integration/test_suites/event_log`](https://github.com/elastic/kibana/tree/main/x-pack/test/plugin_api_integration/test_suites/event_log). To develop integration tests, first start the test server from the root of the repo: diff --git a/x-pack/plugins/event_log/server/es/index.ts b/x-pack/plugins/event_log/server/es/index.ts index 2631df882cc070..a5bc5dce7f2500 100644 --- a/x-pack/plugins/event_log/server/es/index.ts +++ b/x-pack/plugins/event_log/server/es/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { EsContext, createEsContext } from './context'; +export type { EsContext } from './context'; +export { createEsContext } from './context'; diff --git a/x-pack/plugins/event_log/server/event_log_service.mock.ts b/x-pack/plugins/event_log/server/event_log_service.mock.ts index a3ad81eb0e5a63..f43f3e025a7cf6 100644 --- a/x-pack/plugins/event_log/server/event_log_service.mock.ts +++ b/x-pack/plugins/event_log/server/event_log_service.mock.ts @@ -17,6 +17,7 @@ const createEventLogServiceMock = () => { getProviderActions: jest.fn(), registerSavedObjectProvider: jest.fn(), getLogger: jest.fn().mockReturnValue(eventLoggerMock.create()), + getIndexPattern: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/event_log/server/event_log_service.ts b/x-pack/plugins/event_log/server/event_log_service.ts index 993631ed3ca8ac..2cf22b0f207555 100644 --- a/x-pack/plugins/event_log/server/event_log_service.ts +++ b/x-pack/plugins/event_log/server/event_log_service.ts @@ -92,6 +92,10 @@ export class EventLogService implements IEventLogService { return this.savedObjectProviderRegistry.registerProvider(type, provider); } + getIndexPattern() { + return this.esContext.esNames.indexPattern; + } + getLogger(initialProperties: IEvent): IEventLogger { return new EventLogger({ esContext: this.esContext, diff --git a/x-pack/plugins/event_log/server/index.ts b/x-pack/plugins/event_log/server/index.ts index 14c121664d4a8f..877c39a02edc50 100644 --- a/x-pack/plugins/event_log/server/index.ts +++ b/x-pack/plugins/event_log/server/index.ts @@ -9,7 +9,7 @@ import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/serve import { ConfigSchema, IEventLogConfig } from './types'; import { Plugin } from './plugin'; -export { +export type { IEventLogService, IEventLogger, IEventLogClientService, @@ -17,8 +17,8 @@ export { IValidatedEvent, IEventLogClient, QueryEventsBySavedObjectResult, - SAVED_OBJECT_REL_PRIMARY, } from './types'; +export { SAVED_OBJECT_REL_PRIMARY } from './types'; export { ClusterClientAdapter } from './es/cluster_client_adapter'; diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts index c50bed7e01dd59..0cc2e7a1451478 100644 --- a/x-pack/plugins/event_log/server/types.ts +++ b/x-pack/plugins/event_log/server/types.ts @@ -8,11 +8,12 @@ import { schema, TypeOf } from '@kbn/config-schema'; import type { IRouter, KibanaRequest, RequestHandlerContext } from 'src/core/server'; -export { IEvent, IValidatedEvent, EventSchema, ECS_VERSION } from '../generated/schemas'; +export type { IEvent, IValidatedEvent } from '../generated/schemas'; +export { EventSchema, ECS_VERSION } from '../generated/schemas'; import { IEvent } from '../generated/schemas'; import { FindOptionsType } from './event_log_client'; import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; -export { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; +export type { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; import { SavedObjectProvider } from './saved_object_provider_registry'; export const SAVED_OBJECT_REL_PRIMARY = 'primary'; @@ -33,6 +34,7 @@ export interface IEventLogService { getProviderActions(): Map>; registerSavedObjectProvider(type: string, provider: SavedObjectProvider): void; getLogger(properties: IEvent): IEventLogger; + getIndexPattern(): string; } export interface IEventLogClientService { diff --git a/x-pack/plugins/features/common/index.ts b/x-pack/plugins/features/common/index.ts index 450b474744fe9a..9db2a1073cd07e 100644 --- a/x-pack/plugins/features/common/index.ts +++ b/x-pack/plugins/features/common/index.ts @@ -5,14 +5,16 @@ * 2.0. */ -export { FeatureElasticsearchPrivileges } from './feature_elasticsearch_privileges'; -export { FeatureKibanaPrivileges } from './feature_kibana_privileges'; -export { ElasticsearchFeature, ElasticsearchFeatureConfig } from './elasticsearch_feature'; -export { KibanaFeature, KibanaFeatureConfig } from './kibana_feature'; -export { - SubFeature, +export type { FeatureElasticsearchPrivileges } from './feature_elasticsearch_privileges'; +export type { FeatureKibanaPrivileges } from './feature_kibana_privileges'; +export type { ElasticsearchFeatureConfig } from './elasticsearch_feature'; +export { ElasticsearchFeature } from './elasticsearch_feature'; +export type { KibanaFeatureConfig } from './kibana_feature'; +export { KibanaFeature } from './kibana_feature'; +export type { SubFeatureConfig, SubFeaturePrivilegeConfig, SubFeaturePrivilegeGroupConfig, SubFeaturePrivilegeGroupType, } from './sub_feature'; +export { SubFeature } from './sub_feature'; diff --git a/x-pack/plugins/features/public/index.ts b/x-pack/plugins/features/public/index.ts index d1a828f51029ae..2895d83ed114e7 100644 --- a/x-pack/plugins/features/public/index.ts +++ b/x-pack/plugins/features/public/index.ts @@ -8,15 +8,15 @@ import { PluginInitializer } from 'src/core/public'; import { FeaturesPlugin, FeaturesPluginSetup, FeaturesPluginStart } from './plugin'; -export { - KibanaFeature, +export type { KibanaFeatureConfig, FeatureKibanaPrivileges, SubFeatureConfig, SubFeaturePrivilegeConfig, } from '../common'; +export { KibanaFeature } from '../common'; -export { FeaturesPluginSetup, FeaturesPluginStart } from './plugin'; +export type { FeaturesPluginSetup, FeaturesPluginStart } from './plugin'; export const plugin: PluginInitializer = () => new FeaturesPlugin(); diff --git a/x-pack/plugins/features/server/index.ts b/x-pack/plugins/features/server/index.ts index 0890274fed950c..934fcd9d556f62 100644 --- a/x-pack/plugins/features/server/index.ts +++ b/x-pack/plugins/features/server/index.ts @@ -14,15 +14,14 @@ import { FeaturesPlugin } from './plugin'; // run-time contracts. export { uiCapabilitiesRegex } from './feature_schema'; -export { - KibanaFeature, +export type { KibanaFeatureConfig, FeatureKibanaPrivileges, - ElasticsearchFeature, ElasticsearchFeatureConfig, FeatureElasticsearchPrivileges, } from '../common'; -export { PluginSetupContract, PluginStartContract } from './plugin'; +export { KibanaFeature, ElasticsearchFeature } from '../common'; +export type { PluginSetupContract, PluginStartContract } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => new FeaturesPlugin(initializerContext); diff --git a/x-pack/plugins/file_upload/public/components/geojson_upload_form/index.ts b/x-pack/plugins/file_upload/public/components/geojson_upload_form/index.ts index 6168835a3a1459..837c8a0f026949 100644 --- a/x-pack/plugins/file_upload/public/components/geojson_upload_form/index.ts +++ b/x-pack/plugins/file_upload/public/components/geojson_upload_form/index.ts @@ -6,4 +6,4 @@ */ export { GeoJsonUploadForm } from './geojson_upload_form'; -export { OnFileSelectParameters } from './geojson_file_picker'; +export type { OnFileSelectParameters } from './geojson_file_picker'; diff --git a/x-pack/plugins/file_upload/public/importer/geojson_importer/index.ts b/x-pack/plugins/file_upload/public/importer/geojson_importer/index.ts index b5f6845e283247..7d9d8e144fd258 100644 --- a/x-pack/plugins/file_upload/public/importer/geojson_importer/index.ts +++ b/x-pack/plugins/file_upload/public/importer/geojson_importer/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { GeoJsonImporter, GeoJsonPreview, GEOJSON_FILE_TYPES } from './geojson_importer'; +export type { GeoJsonPreview } from './geojson_importer'; +export { GeoJsonImporter, GEOJSON_FILE_TYPES } from './geojson_importer'; diff --git a/x-pack/plugins/file_upload/public/index.ts b/x-pack/plugins/file_upload/public/index.ts index b9e289ef00eebc..00b39dca1180d1 100644 --- a/x-pack/plugins/file_upload/public/index.ts +++ b/x-pack/plugins/file_upload/public/index.ts @@ -16,7 +16,7 @@ export function plugin() { export * from './importer/types'; -export { Props as IndexNameFormProps } from './components/geojson_upload_form/index_name_form'; +export type { Props as IndexNameFormProps } from './components/geojson_upload_form/index_name_form'; -export { FileUploadPluginStart } from './plugin'; -export { FileUploadComponentProps, FileUploadGeoResults } from './lazy_load_bundle'; +export type { FileUploadPluginStart } from './plugin'; +export type { FileUploadComponentProps, FileUploadGeoResults } from './lazy_load_bundle'; diff --git a/x-pack/plugins/fleet/.gitignore b/x-pack/plugins/fleet/.gitignore new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/x-pack/plugins/fleet/README.md b/x-pack/plugins/fleet/README.md index ce8e95c83d6bdc..80d13b28c82655 100644 --- a/x-pack/plugins/fleet/README.md +++ b/x-pack/plugins/fleet/README.md @@ -2,7 +2,7 @@ ## Plugin -- The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/fleet/common/types/index.ts#L9-L27) +- The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/main/x-pack/plugins/fleet/common/types/index.ts#L9-L27) - Adding `xpack.fleet.enabled=false` will disable the plugin including the EPM and Fleet features. It will also remove the `PACKAGE_POLICY_API_ROUTES` and `AGENT_POLICY_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) - Adding `--xpack.fleet.agents.enabled=false` will disable the Fleet API & UI - [code for adding the routes](https://github.com/elastic/kibana/blob/1f27d349533b1c2865c10c45b2cf705d7416fb36/x-pack/plugins/ingest_manager/server/plugin.ts#L115-L133) @@ -26,7 +26,7 @@ Also you need to configure the hosts your agent is going to use to comunication ### Getting started -See the Kibana docs for [how to set up your dev environment](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#setting-up-your-development-environment), [run Elasticsearch](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-elasticsearch), and [start Kibana](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-kibana) +See the Kibana docs for [how to set up your dev environment](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#setting-up-your-development-environment), [run Elasticsearch](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#running-elasticsearch), and [start Kibana](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#running-kibana) One common development workflow is: @@ -44,7 +44,7 @@ One common development workflow is: ``` This plugin follows the `common`, `server`, `public` structure from the [Architecture Style Guide -](https://github.com/elastic/kibana/blob/master/style_guides/architecture_style_guide.md#file-and-folder-structure). We also follow the pattern of developing feature branches under your personal fork of Kibana. +](https://github.com/elastic/kibana/blob/main/style_guides/architecture_style_guide.md#file-and-folder-structure). We also follow the pattern of developing feature branches under your personal fork of Kibana. Note: The plugin was previously named Ingest Manager it's possible that some variables are still named with that old plugin name. diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 60795799bb32d6..aa5e0dbcd5ed17 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -89,7 +89,6 @@ export const AGENT_API_ROUTES = { CHECKIN_PATTERN: `${API_ROOT}/agents/{agentId}/checkin`, ACKS_PATTERN: `${API_ROOT}/agents/{agentId}/acks`, ACTIONS_PATTERN: `${API_ROOT}/agents/{agentId}/actions`, - ENROLL_PATTERN: `${API_ROOT}/agents/enroll`, UNENROLL_PATTERN: `${API_ROOT}/agents/{agentId}/unenroll`, BULK_UNENROLL_PATTERN: `${API_ROOT}/agents/bulk_unenroll`, REASSIGN_PATTERN: `${API_ROOT}/agents/{agentId}/reassign`, diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts index a6f4cd319b9701..ba3fb447536433 100644 --- a/x-pack/plugins/fleet/common/services/index.ts +++ b/x-pack/plugins/fleet/common/services/index.ts @@ -21,10 +21,12 @@ export { isDiffPathProtocol } from './is_diff_path_protocol'; export { LicenseService } from './license'; export { isAgentUpgradeable } from './is_agent_upgradeable'; export { doesPackageHaveIntegrations } from './packages_with_integrations'; -export { +export type { PackagePolicyValidationResults, PackagePolicyConfigValidationResults, PackagePolicyInputValidationResults, +} from './validate_package_policy'; +export { validatePackagePolicy, validatePackagePolicyConfig, validationHasErrors, diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index bd970fc2cd83e6..6eb31fd79aa773 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -32,6 +32,9 @@ export interface FleetConfigType { packages?: PreconfiguredPackage[]; outputs?: PreconfiguredOutput[]; agentIdVerificationEnabled?: boolean; + developer?: { + disableRegistryVersionCheck?: boolean; + }; } // Calling Object.entries(PackagesGroupedByStatus) gave `status: string` diff --git a/x-pack/plugins/fleet/cypress/README.md b/x-pack/plugins/fleet/cypress/README.md new file mode 100644 index 00000000000000..085ed7533e036e --- /dev/null +++ b/x-pack/plugins/fleet/cypress/README.md @@ -0,0 +1,148 @@ +# Cypress Tests + +The `fleet/cypress` directory contains functional UI tests that execute using [Cypress](https://www.cypress.io/). + +## Running the tests + +There are currently three ways to run the tests, comprised of two execution modes and two target environments, which will be detailed below. + +### Execution modes + +#### Interactive mode + +When you run Cypress in interactive mode, an interactive runner is displayed that allows you to see commands as they execute while also viewing the application under test. For more information, please see [cypress documentation](https://docs.cypress.io/guides/core-concepts/test-runner.html#Overview). + +#### Headless mode + +A headless browser is a browser simulation program that does not have a user interface. These programs operate like any other browser, but do not display any UI. This is why meanwhile you are executing the tests on this mode you are not going to see the application under test. Just the output of the test is displayed on the terminal once the execution is finished. + +### Target environments + +#### FTR (CI) + +This is the configuration used by CI. It uses the FTR to spawn both a Kibana instance (http://localhost:5620) and an Elasticsearch instance (http://localhost:9220) with a preloaded minimum set of data (see preceding "Test data" section), and then executes cypress against this stack. You can find this configuration in `x-pack/test/fleet_cypress` + +### Test Execution: Examples + +#### FTR + Headless (Chrome) + +Since this is how tests are run on CI, this will likely be the configuration you want to reproduce failures locally, etc. + +```shell +# bootstrap kibana from the project root +yarn kbn bootstrap + +# build the plugins/assets that cypress will execute against +node scripts/build_kibana_platform_plugins + +# launch the cypress test runner +cd x-pack/plugins/fleet +yarn cypress:run-as-ci +``` +#### FTR + Interactive + +This is the preferred mode for developing new tests. + +```shell +# bootstrap kibana from the project root +yarn kbn bootstrap + +# build the plugins/assets that cypress will execute against +node scripts/build_kibana_platform_plugins + +# launch the cypress test runner +cd x-pack/plugins/fleet +yarn cypress:open-as-ci +``` + +Alternatively, kibana test server can be started separately, to pick up changes in UI (e.g. change in data-test-subj selector) + +``` +# launch kibana test server +node scripts/functional_tests_server --config x-pack/test/fleet_cypress/config.ts + +# launch cypress runner +node scripts/functional_test_runner --config x-pack/test/fleet_cypress/visual_config.ts +``` + +Note that you can select the browser you want to use on the top right side of the interactive runner. + +## Folder Structure + +### integration/ + +Cypress convention. Contains the specs that are going to be executed. + +### fixtures/ + +Cypress convention. Fixtures are used as external pieces of static data when we stub responses. + +### plugins/ + +Cypress convention. As a convenience, by default Cypress will automatically include the plugins file cypress/plugins/index.js before every single spec file it runs. + +### screens/ + +Contains the elements we want to interact with in our tests. + +Each file inside the screens folder represents a screen in our application. + +### tasks/ + +_Tasks_ are functions that may be reused across tests. + +Each file inside the tasks folder represents a screen of our application. + +## Test data + +The data the tests need: + +- Is generated on the fly using our application APIs (preferred way) +- Is ingested on the ELS instance using the `es_archive` utility + +### How to generate a new archive + +**Note:** As mentioned above, archives are only meant to contain external data, e.g. beats data. Due to the tendency for archived domain objects (rules, signals) to quickly become out of date, it is strongly suggested that you generate this data within the test, through interaction with either the UI or the API. + +We use es_archiver to manage the data that our Cypress tests need. + +1. Set up a clean instance of kibana and elasticsearch (if this is not possible, try to clean/minimize the data that you are going to archive). +2. With the kibana and elasticsearch instance up and running, create the data that you need for your test. +3. When you are sure that you have all the data you need run the following command from: `x-pack/plugins/fleet` + +```sh +node ../../../scripts/es_archiver save --dir ../../test/fleet_cypress/es_archives --config ../../../test/functional/config.js --es-url http://:@: +``` + +Example: + +```sh +node ../../../scripts/es_archiver save custom_rules ".kibana",".siem-signal*" --dir ../../test/fleet_cypress/es_archives --config ../../../test/functional/config.js --es-url http://elastic:changeme@localhost:9220 +``` + +Note that the command will create the folder if it does not exist. + +## Development Best Practices + +### Clean up the state + +Remember to clean up the state of the test after its execution, typically with the `cleanKibana` function. Be mindful of failure scenarios, as well: if your test fails, will it leave the environment in a recoverable state? + +### Minimize the use of es_archive + +When possible, create all the data that you need for executing the tests using the application APIS or the UI. + +### Speed up test execution time + +Loading the web page takes a big amount of time, in order to minimize that impact, the following points should be +taken into consideration until another solution is implemented: + +- Group the tests that are similar in different contexts. +- For every context login only once, clean the state between tests if needed without re-loading the page. +- All tests in a spec file must be order-independent. + +Remember that minimizing the number of times the web page is loaded, we minimize as well the execution time. + +## Linting + +Optional linting rules for Cypress and linting setup can be found [here](https://github.com/cypress-io/eslint-plugin-cypress#usage) diff --git a/x-pack/plugins/fleet/cypress/cypress.json b/x-pack/plugins/fleet/cypress/cypress.json new file mode 100644 index 00000000000000..158001b045561a --- /dev/null +++ b/x-pack/plugins/fleet/cypress/cypress.json @@ -0,0 +1,19 @@ +{ + "baseUrl": "http://localhost:5620", + "defaultCommandTimeout": 60000, + "requestTimeout": 60000, + "responseTimetout": 60000, + "execTimeout": 120000, + "pageLoadTimeout": 120000, + "nodeVersion": "system", + "retries": { + "runMode": 2 + }, + "screenshotsFolder": "../../../target/kibana-fleet/cypress/screenshots", + "trashAssetsBeforeRuns": false, + "video": false, + "videosFolder": "../../../target/kibana-fleet/cypress/videos", + "viewportHeight": 900, + "viewportWidth": 1440, + "screenshotOnRunFailure": true +} diff --git a/x-pack/plugins/fleet/cypress/fixtures/integrations/agent_policies.json b/x-pack/plugins/fleet/cypress/fixtures/integrations/agent_policies.json new file mode 100644 index 00000000000000..ba1360e11a21d4 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/fixtures/integrations/agent_policies.json @@ -0,0 +1,978 @@ +{ + "items": [ + { + "id": "30e16140-2106-11ec-a289-25321523992d", + "namespace": "default", + "monitoring_enabled": [ + "logs", + "metrics" + ], + "name": "Default policy", + "description": "Default agent policy created by Kibana", + "is_default": true, + "is_preconfigured": true, + "status": "active", + "is_managed": false, + "revision": 4, + "updated_at": "2021-09-29T09:52:13.879Z", + "updated_by": "elastic", + "package_policies": [ + { + "id": "15785537-fdf2-4e38-bd49-ae0537bbe162", + "version": "WzU5NSwxXQ==", + "name": "system-1", + "namespace": "default", + "package": { + "name": "system", + "title": "System", + "version": "1.4.0" + }, + "enabled": true, + "policy_id": "30e16140-2106-11ec-a289-25321523992d", + "output_id": "1ffdf460-2106-11ec-a289-25321523992d", + "inputs": [ + { + "type": "logfile", + "policy_template": "system", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.auth" + }, + "vars": { + "paths": { + "value": [ + "/var/log/auth.log*", + "/var/log/secure*" + ], + "type": "text" + } + }, + "id": "logfile-system.auth-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "paths": [ + "/var/log/auth.log*", + "/var/log/secure*" + ], + "exclude_files": [ + ".gz$" + ], + "multiline": { + "pattern": "^\\s", + "match": "after" + }, + "processors": [ + { + "add_locale": null + } + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.syslog" + }, + "vars": { + "paths": { + "value": [ + "/var/log/messages*", + "/var/log/syslog*" + ], + "type": "text" + } + }, + "id": "logfile-system.syslog-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "paths": [ + "/var/log/messages*", + "/var/log/syslog*" + ], + "exclude_files": [ + ".gz$" + ], + "multiline": { + "pattern": "^\\s", + "match": "after" + }, + "processors": [ + { + "add_locale": null + } + ] + } + } + ] + }, + { + "type": "winlog", + "policy_template": "system", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.application" + }, + "vars": { + "event_id": { + "type": "text" + }, + "processors": { + "type": "yaml" + }, + "tags": { + "value": [], + "type": "text" + } + }, + "id": "winlog-system.application-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "name": "Application", + "condition": "${host.platform} == 'windows'", + "ignore_older": "72h", + "tags": null + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.security" + }, + "vars": { + "event_id": { + "type": "text" + }, + "processors": { + "type": "yaml" + }, + "tags": { + "value": [], + "type": "text" + } + }, + "id": "winlog-system.security-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "name": "Security", + "condition": "${host.platform} == 'windows'", + "tags": null + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.system" + }, + "vars": { + "event_id": { + "type": "text" + }, + "processors": { + "type": "yaml" + }, + "tags": { + "value": [], + "type": "text" + } + }, + "id": "winlog-system.system-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "name": "System", + "condition": "${host.platform} == 'windows'", + "tags": null + } + } + ], + "vars": { + "preserve_original_event": { + "value": false, + "type": "bool" + } + } + }, + { + "type": "system/metrics", + "policy_template": "system", + "enabled": true, + "streams": [ + { + "enabled": false, + "data_stream": { + "type": "metrics", + "dataset": "system.core" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "core.metrics": { + "value": [ + "percentages" + ], + "type": "text" + } + }, + "id": "system/metrics-system.core-15785537-fdf2-4e38-bd49-ae0537bbe162" + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.cpu" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "cpu.metrics": { + "value": [ + "percentages", + "normalized_percentages" + ], + "type": "text" + } + }, + "id": "system/metrics-system.cpu-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "cpu" + ], + "cpu.metrics": [ + "percentages", + "normalized_percentages" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.diskio" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "diskio.include_devices": { + "value": [], + "type": "text" + } + }, + "id": "system/metrics-system.diskio-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "diskio" + ], + "diskio.include_devices": null, + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.filesystem" + }, + "vars": { + "period": { + "value": "1m", + "type": "text" + }, + "processors": { + "value": "- drop_event.when.regexp:\n system.filesystem.mount_point: ^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)\n", + "type": "yaml" + } + }, + "id": "system/metrics-system.filesystem-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "filesystem" + ], + "period": "1m", + "processors": [ + { + "drop_event.when.regexp": { + "system.filesystem.mount_point": "^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)" + } + } + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.fsstat" + }, + "vars": { + "period": { + "value": "1m", + "type": "text" + }, + "processors": { + "value": "- drop_event.when.regexp:\n system.fsstat.mount_point: ^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)\n", + "type": "yaml" + } + }, + "id": "system/metrics-system.fsstat-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "fsstat" + ], + "period": "1m", + "processors": [ + { + "drop_event.when.regexp": { + "system.fsstat.mount_point": "^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)" + } + } + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.load" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.load-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "load" + ], + "condition": "${host.platform} != 'windows'", + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.memory" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.memory-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "memory" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.network" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "network.interfaces": { + "value": [], + "type": "text" + } + }, + "id": "system/metrics-system.network-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "network" + ], + "period": "10s", + "network.interfaces": null + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.process" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "process.include_top_n.by_cpu": { + "value": 5, + "type": "integer" + }, + "process.include_top_n.by_memory": { + "value": 5, + "type": "integer" + }, + "process.cmdline.cache.enabled": { + "value": true, + "type": "bool" + }, + "process.cgroups.enabled": { + "value": false, + "type": "bool" + }, + "process.env.whitelist": { + "value": [], + "type": "text" + }, + "process.include_cpu_ticks": { + "value": false, + "type": "bool" + }, + "processes": { + "value": [ + ".*" + ], + "type": "text" + } + }, + "id": "system/metrics-system.process-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "process" + ], + "period": "10s", + "process.include_top_n.by_cpu": 5, + "process.include_top_n.by_memory": 5, + "process.cmdline.cache.enabled": true, + "process.cgroups.enabled": false, + "process.include_cpu_ticks": false, + "processes": [ + ".*" + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.process.summary" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.process.summary-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "process_summary" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.socket_summary" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.socket_summary-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "socket_summary" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.uptime" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.uptime-15785537-fdf2-4e38-bd49-ae0537bbe162", + "compiled_stream": { + "metricsets": [ + "uptime" + ], + "period": "10s" + } + } + ], + "vars": { + "system.hostfs": { + "type": "text" + } + } + }, + { + "type": "httpjson", + "policy_template": "system", + "enabled": false, + "streams": [ + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "system.application" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"XmlWinEventLog:Application\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded" + ], + "type": "text" + } + }, + "id": "httpjson-system.application-15785537-fdf2-4e38-bd49-ae0537bbe162" + }, + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "system.security" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"XmlWinEventLog:Security\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded" + ], + "type": "text" + } + }, + "id": "httpjson-system.security-15785537-fdf2-4e38-bd49-ae0537bbe162" + }, + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "system.system" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"XmlWinEventLog:System\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded" + ], + "type": "text" + } + }, + "id": "httpjson-system.system-15785537-fdf2-4e38-bd49-ae0537bbe162" + } + ], + "vars": { + "url": { + "value": "https://server.example.com:8089", + "type": "text" + }, + "username": { + "type": "text" + }, + "password": { + "type": "password" + }, + "token": { + "type": "password" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "ssl": { + "value": "#certificate_authorities:\n# - |\n# -----BEGIN CERTIFICATE-----\n# MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF\n# ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2\n# MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB\n# BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n\n# fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl\n# 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t\n# /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP\n# PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41\n# CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O\n# BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux\n# 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D\n# 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw\n# 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA\n# H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu\n# 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0\n# yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk\n# sxSmbIUfc2SGJGCJD4I=\n# -----END CERTIFICATE-----\n", + "type": "yaml" + } + } + } + ], + "revision": 1, + "created_at": "2021-09-29T09:18:23.207Z", + "created_by": "system", + "updated_at": "2021-09-29T09:18:23.207Z", + "updated_by": "system" + }, + { + "id": "63172a6b-4f00-4376-b5e6-fe9b3f00fc79", + "version": "WzczOSwxXQ==", + "name": "apache-1", + "description": "", + "namespace": "default", + "policy_id": "30e16140-2106-11ec-a289-25321523992d", + "enabled": true, + "output_id": "", + "inputs": [ + { + "type": "logfile", + "policy_template": "apache", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "apache.access" + }, + "vars": { + "paths": { + "value": [ + "/var/log/apache2/access.log*", + "/var/log/apache2/other_vhosts_access.log*", + "/var/log/httpd/access_log*" + ], + "type": "text" + }, + "tags": { + "value": [ + "apache-access" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "logfile-apache.access-63172a6b-4f00-4376-b5e6-fe9b3f00fc79", + "compiled_stream": { + "paths": [ + "/var/log/apache2/access.log*", + "/var/log/apache2/other_vhosts_access.log*", + "/var/log/httpd/access_log*" + ], + "tags": [ + "apache-access" + ], + "exclude_files": [ + ".gz$" + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "apache.error" + }, + "vars": { + "paths": { + "value": [ + "/var/log/apache2/error.log*", + "/var/log/httpd/error_log*" + ], + "type": "text" + }, + "tags": { + "value": [ + "apache-error" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "logfile-apache.error-63172a6b-4f00-4376-b5e6-fe9b3f00fc79", + "compiled_stream": { + "paths": [ + "/var/log/apache2/error.log*", + "/var/log/httpd/error_log*" + ], + "exclude_files": [ + ".gz$" + ], + "tags": [ + "apache-error" + ], + "processors": [ + { + "add_locale": null + } + ] + } + } + ] + }, + { + "type": "httpjson", + "policy_template": "apache", + "enabled": false, + "streams": [ + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "apache.access" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"access*\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded", + "apache-access" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "httpjson-apache.access-63172a6b-4f00-4376-b5e6-fe9b3f00fc79" + }, + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "apache.error" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=apache:error OR sourcetype=apache_error", + "type": "text" + }, + "tags": { + "value": [ + "forwarded", + "apache-error" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "httpjson-apache.error-63172a6b-4f00-4376-b5e6-fe9b3f00fc79" + } + ], + "vars": { + "url": { + "value": "https://server.example.com:8089", + "type": "text" + }, + "username": { + "type": "text" + }, + "password": { + "type": "password" + }, + "token": { + "type": "password" + }, + "ssl": { + "value": "#certificate_authorities:\n# - |\n# -----BEGIN CERTIFICATE-----\n# MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF\n# ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2\n# MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB\n# BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n\n# fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl\n# 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t\n# /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP\n# PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41\n# CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O\n# BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux\n# 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D\n# 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw\n# 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA\n# H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu\n# 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0\n# yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk\n# sxSmbIUfc2SGJGCJD4I=\n# -----END CERTIFICATE-----\n", + "type": "yaml" + } + } + }, + { + "type": "apache/metrics", + "policy_template": "apache", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "apache.status" + }, + "vars": { + "period": { + "value": "30s", + "type": "text" + }, + "server_status_path": { + "value": "/server-status", + "type": "text" + } + }, + "id": "apache/metrics-apache.status-63172a6b-4f00-4376-b5e6-fe9b3f00fc79", + "compiled_stream": { + "metricsets": [ + "status" + ], + "hosts": [ + "http://127.0.0.1" + ], + "period": "30s", + "server_status_path": "/server-status" + } + } + ], + "vars": { + "hosts": { + "value": [ + "http://127.0.0.1" + ], + "type": "text" + } + } + } + ], + "package": { + "name": "apache", + "title": "Apache", + "version": "1.1.0" + }, + "revision": 1, + "created_at": "2021-09-29T09:52:12.865Z", + "created_by": "elastic", + "updated_at": "2021-09-29T09:52:12.865Z", + "updated_by": "elastic" + } + ], + "agents": 1 + }, + { + "id": "30e16141-2106-11ec-a289-25321523992d", + "namespace": "default", + "monitoring_enabled": [ + "logs", + "metrics" + ], + "name": "Default Fleet Server policy", + "description": "Default Fleet Server agent policy created by Kibana", + "is_default": false, + "is_default_fleet_server": true, + "is_preconfigured": true, + "status": "active", + "is_managed": false, + "revision": 1, + "updated_at": "2021-09-29T09:18:25.581Z", + "updated_by": "system", + "package_policies": [ + { + "id": "3f79c8a2-ed32-45d9-a7e7-b58852f4cb7d", + "version": "WzU5NywxXQ==", + "name": "fleet_server-1", + "namespace": "default", + "package": { + "name": "fleet_server", + "title": "Fleet Server", + "version": "1.0.1" + }, + "enabled": true, + "policy_id": "30e16141-2106-11ec-a289-25321523992d", + "output_id": "1ffdf460-2106-11ec-a289-25321523992d", + "inputs": [ + { + "type": "fleet-server", + "policy_template": "fleet_server", + "enabled": true, + "streams": [], + "vars": { + "host": { + "value": [ + "0.0.0.0" + ], + "type": "text" + }, + "port": { + "value": [ + 8220 + ], + "type": "integer" + }, + "max_connections": { + "type": "integer" + }, + "custom": { + "value": "", + "type": "yaml" + } + }, + "compiled_input": { + "server": { + "port": 8220, + "host": "0.0.0.0" + } + } + } + ], + "revision": 1, + "created_at": "2021-09-29T09:18:25.204Z", + "created_by": "system", + "updated_at": "2021-09-29T09:18:25.204Z", + "updated_by": "system" + } + ], + "agents": 0 + } + ], + "total": 2, + "page": 1, + "perPage": 20 +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/cypress/fixtures/integrations/agent_policy.json b/x-pack/plugins/fleet/cypress/fixtures/integrations/agent_policy.json new file mode 100644 index 00000000000000..aa6520f513acdc --- /dev/null +++ b/x-pack/plugins/fleet/cypress/fixtures/integrations/agent_policy.json @@ -0,0 +1,644 @@ +{ + "item": { + "id": "30e16140-2106-11ec-a289-25321523992d", + "namespace": "default", + "monitoring_enabled": [ + "logs", + "metrics" + ], + "name": "Default policy", + "description": "Default agent policy created by Kibana", + "is_default": true, + "is_preconfigured": true, + "status": "active", + "is_managed": false, + "revision": 1, + "updated_at": "2021-09-30T10:02:50.389Z", + "updated_by": "system", + "package_policies": [ + { + "id": "4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "version": "WzEyNjQsMV0=", + "name": "system-1", + "namespace": "default", + "package": { + "name": "system", + "title": "System", + "version": "1.4.0" + }, + "enabled": true, + "policy_id": "8f108d20-21d5-11ec-9dad-073c0cd6096b", + "output_id": "4f979e90-21d5-11ec-9dad-073c0cd6096b", + "inputs": [ + { + "type": "logfile", + "policy_template": "system", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.auth" + }, + "vars": { + "paths": { + "value": [ + "/var/log/auth.log*", + "/var/log/secure*" + ], + "type": "text" + } + }, + "id": "logfile-system.auth-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "paths": [ + "/var/log/auth.log*", + "/var/log/secure*" + ], + "exclude_files": [ + ".gz$" + ], + "multiline": { + "pattern": "^\\s", + "match": "after" + }, + "processors": [ + { + "add_locale": null + } + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.syslog" + }, + "vars": { + "paths": { + "value": [ + "/var/log/messages*", + "/var/log/syslog*" + ], + "type": "text" + } + }, + "id": "logfile-system.syslog-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "paths": [ + "/var/log/messages*", + "/var/log/syslog*" + ], + "exclude_files": [ + ".gz$" + ], + "multiline": { + "pattern": "^\\s", + "match": "after" + }, + "processors": [ + { + "add_locale": null + } + ] + } + } + ] + }, + { + "type": "winlog", + "policy_template": "system", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.application" + }, + "vars": { + "event_id": { + "type": "text" + }, + "processors": { + "type": "yaml" + }, + "tags": { + "value": [], + "type": "text" + } + }, + "id": "winlog-system.application-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "name": "Application", + "condition": "${host.platform} == 'windows'", + "ignore_older": "72h", + "tags": null + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.security" + }, + "vars": { + "event_id": { + "type": "text" + }, + "processors": { + "type": "yaml" + }, + "tags": { + "value": [], + "type": "text" + } + }, + "id": "winlog-system.security-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "name": "Security", + "condition": "${host.platform} == 'windows'", + "tags": null + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "system.system" + }, + "vars": { + "event_id": { + "type": "text" + }, + "processors": { + "type": "yaml" + }, + "tags": { + "value": [], + "type": "text" + } + }, + "id": "winlog-system.system-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "name": "System", + "condition": "${host.platform} == 'windows'", + "tags": null + } + } + ], + "vars": { + "preserve_original_event": { + "value": false, + "type": "bool" + } + } + }, + { + "type": "system/metrics", + "policy_template": "system", + "enabled": true, + "streams": [ + { + "enabled": false, + "data_stream": { + "type": "metrics", + "dataset": "system.core" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "core.metrics": { + "value": [ + "percentages" + ], + "type": "text" + } + }, + "id": "system/metrics-system.core-4243f6b9-6ce2-48ec-859a-b5df4baa7c11" + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.cpu" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "cpu.metrics": { + "value": [ + "percentages", + "normalized_percentages" + ], + "type": "text" + } + }, + "id": "system/metrics-system.cpu-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "cpu" + ], + "cpu.metrics": [ + "percentages", + "normalized_percentages" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.diskio" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "diskio.include_devices": { + "value": [], + "type": "text" + } + }, + "id": "system/metrics-system.diskio-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "diskio" + ], + "diskio.include_devices": null, + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.filesystem" + }, + "vars": { + "period": { + "value": "1m", + "type": "text" + }, + "processors": { + "value": "- drop_event.when.regexp:\n system.filesystem.mount_point: ^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)\n", + "type": "yaml" + } + }, + "id": "system/metrics-system.filesystem-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "filesystem" + ], + "period": "1m", + "processors": [ + { + "drop_event.when.regexp": { + "system.filesystem.mount_point": "^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)" + } + } + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.fsstat" + }, + "vars": { + "period": { + "value": "1m", + "type": "text" + }, + "processors": { + "value": "- drop_event.when.regexp:\n system.fsstat.mount_point: ^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)\n", + "type": "yaml" + } + }, + "id": "system/metrics-system.fsstat-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "fsstat" + ], + "period": "1m", + "processors": [ + { + "drop_event.when.regexp": { + "system.fsstat.mount_point": "^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)" + } + } + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.load" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.load-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "load" + ], + "condition": "${host.platform} != 'windows'", + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.memory" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.memory-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "memory" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.network" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "network.interfaces": { + "value": [], + "type": "text" + } + }, + "id": "system/metrics-system.network-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "network" + ], + "period": "10s", + "network.interfaces": null + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.process" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + }, + "process.include_top_n.by_cpu": { + "value": 5, + "type": "integer" + }, + "process.include_top_n.by_memory": { + "value": 5, + "type": "integer" + }, + "process.cmdline.cache.enabled": { + "value": true, + "type": "bool" + }, + "process.cgroups.enabled": { + "value": false, + "type": "bool" + }, + "process.env.whitelist": { + "value": [], + "type": "text" + }, + "process.include_cpu_ticks": { + "value": false, + "type": "bool" + }, + "processes": { + "value": [ + ".*" + ], + "type": "text" + } + }, + "id": "system/metrics-system.process-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "process" + ], + "period": "10s", + "process.include_top_n.by_cpu": 5, + "process.include_top_n.by_memory": 5, + "process.cmdline.cache.enabled": true, + "process.cgroups.enabled": false, + "process.include_cpu_ticks": false, + "processes": [ + ".*" + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.process.summary" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.process.summary-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "process_summary" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.socket_summary" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.socket_summary-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "socket_summary" + ], + "period": "10s" + } + }, + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "system.uptime" + }, + "vars": { + "period": { + "value": "10s", + "type": "text" + } + }, + "id": "system/metrics-system.uptime-4243f6b9-6ce2-48ec-859a-b5df4baa7c11", + "compiled_stream": { + "metricsets": [ + "uptime" + ], + "period": "10s" + } + } + ], + "vars": { + "system.hostfs": { + "type": "text" + } + } + }, + { + "type": "httpjson", + "policy_template": "system", + "enabled": false, + "streams": [ + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "system.application" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"XmlWinEventLog:Application\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded" + ], + "type": "text" + } + }, + "id": "httpjson-system.application-4243f6b9-6ce2-48ec-859a-b5df4baa7c11" + }, + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "system.security" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"XmlWinEventLog:Security\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded" + ], + "type": "text" + } + }, + "id": "httpjson-system.security-4243f6b9-6ce2-48ec-859a-b5df4baa7c11" + }, + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "system.system" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"XmlWinEventLog:System\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded" + ], + "type": "text" + } + }, + "id": "httpjson-system.system-4243f6b9-6ce2-48ec-859a-b5df4baa7c11" + } + ], + "vars": { + "url": { + "value": "https://server.example.com:8089", + "type": "text" + }, + "username": { + "type": "text" + }, + "password": { + "type": "password" + }, + "token": { + "type": "password" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "ssl": { + "value": "#certificate_authorities:\n# - |\n# -----BEGIN CERTIFICATE-----\n# MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF\n# ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2\n# MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB\n# BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n\n# fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl\n# 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t\n# /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP\n# PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41\n# CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O\n# BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux\n# 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D\n# 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw\n# 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA\n# H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu\n# 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0\n# yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk\n# sxSmbIUfc2SGJGCJD4I=\n# -----END CERTIFICATE-----\n", + "type": "yaml" + } + } + } + ], + "revision": 1, + "created_at": "2021-09-30T10:02:48.904Z", + "created_by": "system", + "updated_at": "2021-09-30T10:02:48.904Z", + "updated_by": "system" + } + ] + } +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/cypress/fixtures/integrations/apache.json b/x-pack/plugins/fleet/cypress/fixtures/integrations/apache.json new file mode 100644 index 00000000000000..3b78048fdd83fc --- /dev/null +++ b/x-pack/plugins/fleet/cypress/fixtures/integrations/apache.json @@ -0,0 +1,1059 @@ +{ + "response": { + "name": "apache", + "title": "Apache", + "version": "1.1.0", + "release": "ga", + "description": "This Elastic integration collects logs and metrics from Apache servers", + "type": "integration", + "download": "/epr/apache/apache-1.1.0.zip", + "path": "/package/apache/1.1.0", + "icons": [ + { + "src": "/img/logo_apache.svg", + "path": "/package/apache/1.1.0/img/logo_apache.svg", + "title": "Apache Logo", + "size": "32x32", + "type": "image/svg+xml" + } + ], + "format_version": "1.0.0", + "readme": "/package/apache/1.1.0/docs/README.md", + "license": "basic", + "categories": [ + "web" + ], + "conditions": { + "kibana.version": "^7.14.0" + }, + "screenshots": [ + { + "src": "/img/apache-metrics-overview.png", + "path": "/package/apache/1.1.0/img/apache-metrics-overview.png", + "title": "Apache metrics overview", + "size": "3360x3064", + "type": "image/png" + }, + { + "src": "/img/apache-logs-overview.png", + "path": "/package/apache/1.1.0/img/apache-logs-overview.png", + "title": "Apache logs overview", + "size": "3342x1384", + "type": "image/png" + } + ], + "assets": { + "kibana": { + "dashboard": [ + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "dashboard", + "file": "apache-Logs-Apache-Dashboard.json", + "path": "apache-1.1.0/kibana/dashboard/apache-Logs-Apache-Dashboard.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "dashboard", + "file": "apache-Metrics-Apache-HTTPD-server-status.json", + "path": "apache-1.1.0/kibana/dashboard/apache-Metrics-Apache-HTTPD-server-status.json" + } + ], + "ml_module": [ + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "ml_module", + "file": "apache-Logs-ml.json", + "path": "apache-1.1.0/kibana/ml_module/apache-Logs-ml.json" + } + ], + "search": [ + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "search", + "file": "apache-HTTPD.json", + "path": "apache-1.1.0/kibana/search/apache-HTTPD.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "search", + "file": "apache-access-logs.json", + "path": "apache-1.1.0/kibana/search/apache-access-logs.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "search", + "file": "apache-errors-log.json", + "path": "apache-1.1.0/kibana/search/apache-errors-log.json" + } + ], + "visualization": [ + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-22057f20-3a12-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-22057f20-3a12-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-320cd980-3a36-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-320cd980-3a36-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-47820ce0-3a1d-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-47820ce0-3a1d-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-7724cf20-3a39-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-7724cf20-3a39-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-7d68f730-3a39-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-7d68f730-3a39-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-805d7bb0-3a10-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-805d7bb0-3a10-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-99666080-3a20-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-99666080-3a20-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-HTTPD-CPU.json", + "path": "apache-1.1.0/kibana/visualization/apache-HTTPD-CPU.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-HTTPD-Load1-slash-5-slash-15.json", + "path": "apache-1.1.0/kibana/visualization/apache-HTTPD-Load1-slash-5-slash-15.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-HTTPD-Scoreboard.json", + "path": "apache-1.1.0/kibana/visualization/apache-HTTPD-Scoreboard.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-a45311f0-3a34-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-a45311f0-3a34-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-access-unique-IPs-map.json", + "path": "apache-1.1.0/kibana/visualization/apache-access-unique-IPs-map.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-browsers.json", + "path": "apache-1.1.0/kibana/visualization/apache-browsers.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-ed44f820-3a10-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-ed44f820-3a10-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-error-logs-over-time.json", + "path": "apache-1.1.0/kibana/visualization/apache-error-logs-over-time.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-f4ffec70-3a36-11eb-8946-296aab7b13db.json", + "path": "apache-1.1.0/kibana/visualization/apache-f4ffec70-3a36-11eb-8946-296aab7b13db.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-operating-systems.json", + "path": "apache-1.1.0/kibana/visualization/apache-operating-systems.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-response-codes-of-top-URLs.json", + "path": "apache-1.1.0/kibana/visualization/apache-response-codes-of-top-URLs.json" + }, + { + "pkgkey": "apache-1.1.0", + "service": "kibana", + "type": "visualization", + "file": "apache-response-codes-over-time.json", + "path": "apache-1.1.0/kibana/visualization/apache-response-codes-over-time.json" + } + ] + }, + "elasticsearch": { + "ingest_pipeline": [ + { + "pkgkey": "apache-1.1.0", + "service": "elasticsearch", + "type": "ingest_pipeline", + "file": "default.yml", + "dataset": "access", + "path": "apache-1.1.0/data_stream/access/elasticsearch/ingest_pipeline/default.yml" + }, + { + "pkgkey": "apache-1.1.0", + "service": "elasticsearch", + "type": "ingest_pipeline", + "file": "third-party.yml", + "dataset": "access", + "path": "apache-1.1.0/data_stream/access/elasticsearch/ingest_pipeline/third-party.yml" + }, + { + "pkgkey": "apache-1.1.0", + "service": "elasticsearch", + "type": "ingest_pipeline", + "file": "default.yml", + "dataset": "error", + "path": "apache-1.1.0/data_stream/error/elasticsearch/ingest_pipeline/default.yml" + }, + { + "pkgkey": "apache-1.1.0", + "service": "elasticsearch", + "type": "ingest_pipeline", + "file": "third-party.yml", + "dataset": "error", + "path": "apache-1.1.0/data_stream/error/elasticsearch/ingest_pipeline/third-party.yml" + } + ] + } + }, + "policy_templates": [ + { + "name": "apache", + "title": "Apache logs and metrics", + "description": "Collect logs and metrics from Apache instances", + "inputs": [ + { + "type": "logfile", + "title": "Collect logs from Apache instances", + "description": "Collecting Apache access and error logs" + }, + { + "type": "httpjson", + "vars": [ + { + "name": "url", + "type": "text", + "title": "URL of Splunk Enterprise Server", + "description": "i.e. scheme://host:port, path is automatic", + "multi": false, + "required": true, + "show_user": true, + "default": "https://server.example.com:8089" + }, + { + "name": "username", + "type": "text", + "title": "Splunk REST API Username", + "multi": false, + "required": false, + "show_user": true + }, + { + "name": "password", + "type": "password", + "title": "Splunk REST API Password", + "multi": false, + "required": false, + "show_user": true + }, + { + "name": "token", + "type": "password", + "title": "Splunk Authorization Token", + "description": "Bearer Token or Session Key, e.g. \"Bearer eyJFd3e46...\"\nor \"Splunk 192fd3e...\". Cannot be used with username\nand password.\n", + "multi": false, + "required": false, + "show_user": true + }, + { + "name": "ssl", + "type": "yaml", + "title": "SSL Configuration", + "description": "i.e. certificate_authorities, supported_protocols, verification_mode etc.", + "multi": false, + "required": false, + "show_user": false, + "default": "#certificate_authorities:\n# - |\n# -----BEGIN CERTIFICATE-----\n# MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF\n# ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2\n# MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB\n# BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n\n# fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl\n# 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t\n# /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP\n# PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41\n# CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O\n# BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux\n# 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D\n# 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw\n# 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA\n# H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu\n# 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0\n# yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk\n# sxSmbIUfc2SGJGCJD4I=\n# -----END CERTIFICATE-----\n" + } + ], + "title": "Collect logs from third-party REST API (experimental)", + "description": "Collect logs from third-party REST API (experimental)" + }, + { + "type": "apache/metrics", + "vars": [ + { + "name": "hosts", + "type": "text", + "title": "Hosts", + "multi": true, + "required": true, + "show_user": true, + "default": [ + "http://127.0.0.1" + ] + } + ], + "title": "Collect metrics from Apache instances", + "description": "Collecting Apache status metrics" + } + ], + "multiple": true + } + ], + "data_streams": [ + { + "type": "logs", + "dataset": "apache.access", + "title": "Apache access logs", + "release": "experimental", + "ingest_pipeline": "default", + "streams": [ + { + "input": "logfile", + "vars": [ + { + "name": "paths", + "type": "text", + "title": "Paths", + "multi": true, + "required": true, + "show_user": true, + "default": [ + "/var/log/apache2/access.log*", + "/var/log/apache2/other_vhosts_access.log*", + "/var/log/httpd/access_log*" + ] + }, + { + "name": "tags", + "type": "text", + "title": "Tags", + "multi": true, + "required": true, + "show_user": false, + "default": [ + "apache-access" + ] + }, + { + "name": "preserve_original_event", + "type": "bool", + "title": "Preserve original event", + "description": "Preserves a raw copy of the original event, added to the field `event.original`", + "multi": false, + "required": true, + "show_user": true, + "default": false + }, + { + "name": "processors", + "type": "yaml", + "title": "Processors", + "description": "Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.\n", + "multi": false, + "required": false, + "show_user": false + } + ], + "template_path": "log.yml.hbs", + "title": "Apache access logs", + "description": "Collect Apache access logs", + "enabled": true + }, + { + "input": "httpjson", + "vars": [ + { + "name": "interval", + "type": "text", + "title": "Interval to query Splunk Enterprise REST API", + "description": "Go Duration syntax (eg. 10s)", + "multi": false, + "required": true, + "show_user": true, + "default": "10s" + }, + { + "name": "search", + "type": "text", + "title": "Splunk search string", + "multi": false, + "required": true, + "show_user": true, + "default": "search sourcetype=\"access*\"" + }, + { + "name": "tags", + "type": "text", + "title": "Tags", + "multi": true, + "required": false, + "show_user": false, + "default": [ + "forwarded", + "apache-access" + ] + }, + { + "name": "preserve_original_event", + "type": "bool", + "title": "Preserve original event", + "description": "Preserves a raw copy of the original event, added to the field `event.original`", + "multi": false, + "required": true, + "show_user": true, + "default": false + }, + { + "name": "processors", + "type": "yaml", + "title": "Processors", + "description": "Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.\n", + "multi": false, + "required": false, + "show_user": false + } + ], + "template_path": "httpjson.yml.hbs", + "title": "Apache access logs via Splunk Enterprise REST API", + "description": "Collect apache access logs via Splunk Enterprise REST API", + "enabled": false + } + ], + "package": "apache", + "path": "access" + }, + { + "type": "logs", + "dataset": "apache.error", + "title": "Apache error logs", + "release": "experimental", + "ingest_pipeline": "default", + "streams": [ + { + "input": "logfile", + "vars": [ + { + "name": "paths", + "type": "text", + "title": "Paths", + "multi": true, + "required": true, + "show_user": true, + "default": [ + "/var/log/apache2/error.log*", + "/var/log/httpd/error_log*" + ] + }, + { + "name": "tags", + "type": "text", + "title": "Tags", + "multi": true, + "required": true, + "show_user": false, + "default": [ + "apache-error" + ] + }, + { + "name": "preserve_original_event", + "type": "bool", + "title": "Preserve original event", + "description": "Preserves a raw copy of the original event, added to the field `event.original`", + "multi": false, + "required": true, + "show_user": true, + "default": false + }, + { + "name": "processors", + "type": "yaml", + "title": "Processors", + "description": "Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.\n", + "multi": false, + "required": false, + "show_user": false + } + ], + "template_path": "log.yml.hbs", + "title": "Apache error logs", + "description": "Collect Apache error logs", + "enabled": true + }, + { + "input": "httpjson", + "vars": [ + { + "name": "interval", + "type": "text", + "title": "Interval to query Splunk Enterprise REST API", + "description": "Go Duration syntax (eg. 10s)", + "multi": false, + "required": true, + "show_user": true, + "default": "10s" + }, + { + "name": "search", + "type": "text", + "title": "Splunk search string", + "multi": false, + "required": true, + "show_user": true, + "default": "search sourcetype=apache:error OR sourcetype=apache_error" + }, + { + "name": "tags", + "type": "text", + "title": "Tags", + "multi": true, + "required": false, + "show_user": false, + "default": [ + "forwarded", + "apache-error" + ] + }, + { + "name": "preserve_original_event", + "type": "bool", + "title": "Preserve original event", + "description": "Preserves a raw copy of the original event, added to the field `event.original`", + "multi": false, + "required": true, + "show_user": true, + "default": false + }, + { + "name": "processors", + "type": "yaml", + "title": "Processors", + "description": "Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.\n", + "multi": false, + "required": false, + "show_user": false + } + ], + "template_path": "httpjson.yml.hbs", + "title": "Apache error logs via Splunk Enterprise REST API", + "description": "Collect apache error logs via Splunk Enterprise REST API", + "enabled": false + } + ], + "package": "apache", + "path": "error" + }, + { + "type": "metrics", + "dataset": "apache.status", + "title": "Apache status metrics", + "release": "experimental", + "streams": [ + { + "input": "apache/metrics", + "vars": [ + { + "name": "period", + "type": "text", + "title": "Period", + "multi": false, + "required": true, + "show_user": true, + "default": "30s" + }, + { + "name": "server_status_path", + "type": "text", + "title": "Server Status Path", + "multi": false, + "required": true, + "show_user": false, + "default": "/server-status" + } + ], + "template_path": "stream.yml.hbs", + "title": "Apache status metrics", + "description": "Collect Apache status metrics", + "enabled": true + } + ], + "package": "apache", + "path": "status" + } + ], + "owner": { + "github": "elastic/integrations" + }, + "latestVersion": "1.1.0", + "removable": true, + "status": "installed", + "savedObject": { + "id": "apache", + "type": "epm-packages", + "namespaces": [], + "updated_at": "2021-09-30T10:47:12.961Z", + "version": "WzI1NjgsMV0=", + "attributes": { + "installed_kibana": [ + { + "id": "apache-Logs-Apache-Dashboard", + "type": "dashboard" + }, + { + "id": "apache-Metrics-Apache-HTTPD-server-status", + "type": "dashboard" + }, + { + "id": "apache-22057f20-3a12-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-320cd980-3a36-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-47820ce0-3a1d-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-7724cf20-3a39-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-7d68f730-3a39-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-805d7bb0-3a10-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-99666080-3a20-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-HTTPD-CPU", + "type": "visualization" + }, + { + "id": "apache-HTTPD-Load1-slash-5-slash-15", + "type": "visualization" + }, + { + "id": "apache-HTTPD-Scoreboard", + "type": "visualization" + }, + { + "id": "apache-a45311f0-3a34-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-access-unique-IPs-map", + "type": "visualization" + }, + { + "id": "apache-browsers", + "type": "visualization" + }, + { + "id": "apache-ed44f820-3a10-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-error-logs-over-time", + "type": "visualization" + }, + { + "id": "apache-f4ffec70-3a36-11eb-8946-296aab7b13db", + "type": "visualization" + }, + { + "id": "apache-operating-systems", + "type": "visualization" + }, + { + "id": "apache-response-codes-of-top-URLs", + "type": "visualization" + }, + { + "id": "apache-response-codes-over-time", + "type": "visualization" + }, + { + "id": "apache-HTTPD", + "type": "search" + }, + { + "id": "apache-access-logs", + "type": "search" + }, + { + "id": "apache-errors-log", + "type": "search" + }, + { + "id": "apache-Logs-ml", + "type": "ml-module" + } + ], + "installed_es": [ + { + "id": "logs-apache.access-1.1.0", + "type": "ingest_pipeline" + }, + { + "id": "logs-apache.access-1.1.0-third-party", + "type": "ingest_pipeline" + }, + { + "id": "logs-apache.error-1.1.0", + "type": "ingest_pipeline" + }, + { + "id": "logs-apache.error-1.1.0-third-party", + "type": "ingest_pipeline" + }, + { + "id": "logs-apache.access", + "type": "index_template" + }, + { + "id": "logs-apache.access@settings", + "type": "component_template" + }, + { + "id": "logs-apache.access@custom", + "type": "component_template" + }, + { + "id": "logs-apache.error", + "type": "index_template" + }, + { + "id": "logs-apache.error@settings", + "type": "component_template" + }, + { + "id": "logs-apache.error@custom", + "type": "component_template" + }, + { + "id": "metrics-apache.status", + "type": "index_template" + }, + { + "id": "metrics-apache.status@settings", + "type": "component_template" + }, + { + "id": "metrics-apache.status@custom", + "type": "component_template" + } + ], + "package_assets": [ + { + "id": "c99057a8-c51a-5795-9e00-b4b09237f780", + "type": "epm-packages-assets" + }, + { + "id": "1388d2c7-254a-5cd4-882d-89b3e8b681cd", + "type": "epm-packages-assets" + }, + { + "id": "c3068bcb-5a74-5044-91f6-c8e99eefb003", + "type": "epm-packages-assets" + }, + { + "id": "4cea5f13-0ec6-5ecc-9012-f2dba2c86fab", + "type": "epm-packages-assets" + }, + { + "id": "6f27b654-fc39-502b-bdda-83ed13e775c1", + "type": "epm-packages-assets" + }, + { + "id": "baa6d518-fa85-530f-9cdc-b0f2207599f8", + "type": "epm-packages-assets" + }, + { + "id": "ea0cfbd9-8173-5429-a83b-6168b2cd4f27", + "type": "epm-packages-assets" + }, + { + "id": "3745632e-1306-5ac6-84ee-0fceae577988", + "type": "epm-packages-assets" + }, + { + "id": "079a3007-eec5-504e-a993-8c489ccc992c", + "type": "epm-packages-assets" + }, + { + "id": "625ba117-a66d-5eba-9172-201e4f03fbf0", + "type": "epm-packages-assets" + }, + { + "id": "f0dd03dd-3dee-51da-881b-425e76966139", + "type": "epm-packages-assets" + }, + { + "id": "c356fb2c-395b-595e-bdf4-51c5750d6efe", + "type": "epm-packages-assets" + }, + { + "id": "861a6d88-8e80-5282-8cc4-b74b13da22f8", + "type": "epm-packages-assets" + }, + { + "id": "49186533-1536-5d2d-a45a-b51a4db1eeca", + "type": "epm-packages-assets" + }, + { + "id": "533a5c29-648c-593c-9444-df3d03c4aae0", + "type": "epm-packages-assets" + }, + { + "id": "9d34d784-f5a7-5213-a711-37bf2af21da5", + "type": "epm-packages-assets" + }, + { + "id": "4d5fa019-7503-5a89-95af-a03227622ecd", + "type": "epm-packages-assets" + }, + { + "id": "edc0c10d-f7f4-5523-8dac-ce9c64aff44d", + "type": "epm-packages-assets" + }, + { + "id": "5792421c-b31c-59a3-891c-1566bc85447b", + "type": "epm-packages-assets" + }, + { + "id": "7a72f59a-27a6-5514-9489-1258de496199", + "type": "epm-packages-assets" + }, + { + "id": "69dffce3-96d1-5c71-b4ae-41b6d61fdd4a", + "type": "epm-packages-assets" + }, + { + "id": "0b971e05-221e-5430-87e6-fbebbc8d4a23", + "type": "epm-packages-assets" + }, + { + "id": "5d7fb7e1-e775-5832-95a7-074d692fb176", + "type": "epm-packages-assets" + }, + { + "id": "4a50c74b-e4ce-511c-badd-54997537b6b8", + "type": "epm-packages-assets" + }, + { + "id": "54e21b74-9ea5-537f-8cce-673b10b8ac39", + "type": "epm-packages-assets" + }, + { + "id": "c9fd9a64-722c-59f7-a686-4d92d4395be0", + "type": "epm-packages-assets" + }, + { + "id": "5a53ca55-23ec-59bc-8d04-be12f1776358", + "type": "epm-packages-assets" + }, + { + "id": "b2652216-a523-5183-8eaa-c26f9ba4bbee", + "type": "epm-packages-assets" + }, + { + "id": "97f717d7-78d6-5b8c-acde-edf80aa27201", + "type": "epm-packages-assets" + }, + { + "id": "6b27939a-1f2a-536d-8d84-560ed372d21a", + "type": "epm-packages-assets" + }, + { + "id": "7d68617a-88b0-5d34-8a98-8f51d3c49568", + "type": "epm-packages-assets" + }, + { + "id": "8e212777-acac-5068-acbb-143e0cbfb3eb", + "type": "epm-packages-assets" + }, + { + "id": "436ed6b2-aa68-55d4-912a-346e14903d7b", + "type": "epm-packages-assets" + }, + { + "id": "5169ccd9-75f9-5d84-8116-2f2bac0dd93f", + "type": "epm-packages-assets" + }, + { + "id": "a36f82fe-4aa0-508f-92e4-e33d779c1ed2", + "type": "epm-packages-assets" + }, + { + "id": "96d9ae25-0ee7-59aa-b8a0-4fbb929cce4a", + "type": "epm-packages-assets" + }, + { + "id": "05e1449f-3723-5d3c-a76f-5e307d88c35b", + "type": "epm-packages-assets" + }, + { + "id": "a0e8abee-4777-5a7f-bb9a-c2c60d49d060", + "type": "epm-packages-assets" + }, + { + "id": "4c77c830-b4e2-5c77-a3dd-941249799ce7", + "type": "epm-packages-assets" + }, + { + "id": "e082c4c2-3215-5fb0-a485-b261a774314e", + "type": "epm-packages-assets" + }, + { + "id": "1f4467ca-6aa9-5fcb-a346-f334e018db3f", + "type": "epm-packages-assets" + }, + { + "id": "fc831e85-d43f-5402-8780-c9fb3b040b34", + "type": "epm-packages-assets" + }, + { + "id": "208cc640-7cb1-5dd0-902e-47d82fe273af", + "type": "epm-packages-assets" + }, + { + "id": "65e211ff-9497-5882-88cc-ebfd79578cff", + "type": "epm-packages-assets" + }, + { + "id": "a6ea40cc-bb98-5039-8d52-151ac69cbfb5", + "type": "epm-packages-assets" + }, + { + "id": "d9e1d1e6-1c31-5164-8805-b8b2249bd8b5", + "type": "epm-packages-assets" + }, + { + "id": "aa843dec-f345-5c94-99e3-8bd2bffb9b4e", + "type": "epm-packages-assets" + }, + { + "id": "2b019917-8d4c-5da9-80b2-5005524a1290", + "type": "epm-packages-assets" + }, + { + "id": "617effde-ae31-5f48-928a-acdf7b6bc0bb", + "type": "epm-packages-assets" + }, + { + "id": "10245259-aff6-5cc9-b60b-9d88a230894e", + "type": "epm-packages-assets" + }, + { + "id": "753a2e77-13fe-5aa8-94a7-08e9357e64f0", + "type": "epm-packages-assets" + }, + { + "id": "4132f76c-78bc-5d70-a7cd-421910242f96", + "type": "epm-packages-assets" + }, + { + "id": "74230ee0-f671-57fc-bf3a-1c1be03acf22", + "type": "epm-packages-assets" + }, + { + "id": "a2465b23-c15e-56f9-acad-e2d5387cae48", + "type": "epm-packages-assets" + }, + { + "id": "94586e3f-78a0-5cf8-b4c2-923f4516153a", + "type": "epm-packages-assets" + }, + { + "id": "7b356571-eb79-541c-ba99-e6fdebf74e98", + "type": "epm-packages-assets" + }, + { + "id": "babd82eb-7317-58c0-a5fc-4d14ca1f2d17", + "type": "epm-packages-assets" + }, + { + "id": "aa68dd98-4844-5162-b96f-e6b5eae5f987", + "type": "epm-packages-assets" + } + ], + "es_index_patterns": { + "access": "logs-apache.access-*", + "error": "logs-apache.error-*", + "status": "metrics-apache.status-*" + }, + "name": "apache", + "version": "1.1.0", + "internal": false, + "removable": true, + "install_version": "1.1.0", + "install_status": "installed", + "install_started_at": "2021-09-30T10:46:58.713Z", + "install_source": "registry" + }, + "references": [], + "migrationVersion": { + "epm-packages": "7.14.1" + }, + "coreMigrationVersion": "8.0.0" + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/cypress/fixtures/integrations/create_integration_response.json b/x-pack/plugins/fleet/cypress/fixtures/integrations/create_integration_response.json new file mode 100644 index 00000000000000..6820aadd01fb12 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/fixtures/integrations/create_integration_response.json @@ -0,0 +1,255 @@ +{ + "item": { + "id": "1", + "version": "WzI4NDAsMV0=", + "name": "apache-1", + "description": "", + "namespace": "default", + "policy_id": "9ced27e0-20ff-11ec-b353-dd9d66c6f483", + "enabled": true, + "output_id": "", + "inputs": [ + { + "type": "logfile", + "policy_template": "apache", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "apache.access" + }, + "vars": { + "paths": { + "value": [ + "/var/log/apache2/access.log*", + "/var/log/apache2/other_vhosts_access.log*", + "/var/log/httpd/access_log*" + ], + "type": "text" + }, + "tags": { + "value": [ + "apache-access" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "logfile-apache.access-1c588150-010b-448a-b2b8-820d1b33811e", + "compiled_stream": { + "paths": [ + "/var/log/apache2/access.log*", + "/var/log/apache2/other_vhosts_access.log*", + "/var/log/httpd/access_log*" + ], + "tags": [ + "apache-access" + ], + "exclude_files": [ + ".gz$" + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "apache.error" + }, + "vars": { + "paths": { + "value": [ + "/var/log/apache2/error.log*", + "/var/log/httpd/error_log*" + ], + "type": "text" + }, + "tags": { + "value": [ + "apache-error" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "logfile-apache.error-1c588150-010b-448a-b2b8-820d1b33811e", + "compiled_stream": { + "paths": [ + "/var/log/apache2/error.log*", + "/var/log/httpd/error_log*" + ], + "exclude_files": [ + ".gz$" + ], + "tags": [ + "apache-error" + ], + "processors": [ + { + "add_locale": null + } + ] + } + } + ] + }, + { + "type": "httpjson", + "policy_template": "apache", + "enabled": false, + "streams": [ + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "apache.access" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"access*\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded", + "apache-access" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "httpjson-apache.access-1c588150-010b-448a-b2b8-820d1b33811e" + }, + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "apache.error" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=apache:error OR sourcetype=apache_error", + "type": "text" + }, + "tags": { + "value": [ + "forwarded", + "apache-error" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "httpjson-apache.error-1c588150-010b-448a-b2b8-820d1b33811e" + } + ], + "vars": { + "url": { + "value": "https://server.example.com:8089", + "type": "text" + }, + "username": { + "type": "text" + }, + "password": { + "type": "password" + }, + "token": { + "type": "password" + }, + "ssl": { + "value": "#certificate_authorities:\n# - |\n# -----BEGIN CERTIFICATE-----\n# MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF\n# ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2\n# MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB\n# BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n\n# fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl\n# 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t\n# /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP\n# PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41\n# CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O\n# BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux\n# 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D\n# 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw\n# 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA\n# H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu\n# 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0\n# yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk\n# sxSmbIUfc2SGJGCJD4I=\n# -----END CERTIFICATE-----\n", + "type": "yaml" + } + } + }, + { + "type": "apache/metrics", + "policy_template": "apache", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "apache.status" + }, + "vars": { + "period": { + "value": "30s", + "type": "text" + }, + "server_status_path": { + "value": "/server-status", + "type": "text" + } + }, + "id": "apache/metrics-apache.status-1c588150-010b-448a-b2b8-820d1b33811e", + "compiled_stream": { + "metricsets": [ + "status" + ], + "hosts": [ + "http://127.0.0.1" + ], + "period": "30s", + "server_status_path": "/server-status" + } + } + ], + "vars": { + "hosts": { + "value": [ + "http://127.0.0.1" + ], + "type": "text" + } + } + } + ], + "package": { + "name": "apache", + "title": "Apache", + "version": "1.1.0" + }, + "revision": 1, + "created_at": "2021-09-29T09:12:55.869Z", + "created_by": "elastic", + "updated_at": "2021-09-29T09:12:55.869Z", + "updated_by": "elastic" + } +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/cypress/fixtures/integrations/list.json b/x-pack/plugins/fleet/cypress/fixtures/integrations/list.json new file mode 100644 index 00000000000000..73c3ff54c5d958 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/fixtures/integrations/list.json @@ -0,0 +1,260 @@ +{ + "items": [ + { + "id": "1", + "version": "WzczOSwxXQ==", + "name": "apache-1", + "description": "", + "namespace": "default", + "policy_id": "30e16140-2106-11ec-a289-25321523992d", + "enabled": true, + "output_id": "", + "inputs": [ + { + "type": "logfile", + "policy_template": "apache", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "apache.access" + }, + "vars": { + "paths": { + "value": [ + "/var/log/apache2/access.log*", + "/var/log/apache2/other_vhosts_access.log*", + "/var/log/httpd/access_log*" + ], + "type": "text" + }, + "tags": { + "value": [ + "apache-access" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "logfile-apache.access-63172a6b-4f00-4376-b5e6-fe9b3f00fc79", + "compiled_stream": { + "paths": [ + "/var/log/apache2/access.log*", + "/var/log/apache2/other_vhosts_access.log*", + "/var/log/httpd/access_log*" + ], + "tags": [ + "apache-access" + ], + "exclude_files": [ + ".gz$" + ] + } + }, + { + "enabled": true, + "data_stream": { + "type": "logs", + "dataset": "apache.error" + }, + "vars": { + "paths": { + "value": [ + "/var/log/apache2/error.log*", + "/var/log/httpd/error_log*" + ], + "type": "text" + }, + "tags": { + "value": [ + "apache-error" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "logfile-apache.error-63172a6b-4f00-4376-b5e6-fe9b3f00fc79", + "compiled_stream": { + "paths": [ + "/var/log/apache2/error.log*", + "/var/log/httpd/error_log*" + ], + "exclude_files": [ + ".gz$" + ], + "tags": [ + "apache-error" + ], + "processors": [ + { + "add_locale": null + } + ] + } + } + ] + }, + { + "type": "httpjson", + "policy_template": "apache", + "enabled": false, + "streams": [ + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "apache.access" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=\"access*\"", + "type": "text" + }, + "tags": { + "value": [ + "forwarded", + "apache-access" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "httpjson-apache.access-63172a6b-4f00-4376-b5e6-fe9b3f00fc79" + }, + { + "enabled": false, + "data_stream": { + "type": "logs", + "dataset": "apache.error" + }, + "vars": { + "interval": { + "value": "10s", + "type": "text" + }, + "search": { + "value": "search sourcetype=apache:error OR sourcetype=apache_error", + "type": "text" + }, + "tags": { + "value": [ + "forwarded", + "apache-error" + ], + "type": "text" + }, + "preserve_original_event": { + "value": false, + "type": "bool" + }, + "processors": { + "type": "yaml" + } + }, + "id": "httpjson-apache.error-63172a6b-4f00-4376-b5e6-fe9b3f00fc79" + } + ], + "vars": { + "url": { + "value": "https://server.example.com:8089", + "type": "text" + }, + "username": { + "type": "text" + }, + "password": { + "type": "password" + }, + "token": { + "type": "password" + }, + "ssl": { + "value": "#certificate_authorities:\n# - |\n# -----BEGIN CERTIFICATE-----\n# MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF\n# ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2\n# MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB\n# BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n\n# fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl\n# 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t\n# /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP\n# PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41\n# CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O\n# BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux\n# 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D\n# 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw\n# 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA\n# H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu\n# 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0\n# yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk\n# sxSmbIUfc2SGJGCJD4I=\n# -----END CERTIFICATE-----\n", + "type": "yaml" + } + } + }, + { + "type": "apache/metrics", + "policy_template": "apache", + "enabled": true, + "streams": [ + { + "enabled": true, + "data_stream": { + "type": "metrics", + "dataset": "apache.status" + }, + "vars": { + "period": { + "value": "30s", + "type": "text" + }, + "server_status_path": { + "value": "/server-status", + "type": "text" + } + }, + "id": "apache/metrics-apache.status-63172a6b-4f00-4376-b5e6-fe9b3f00fc79", + "compiled_stream": { + "metricsets": [ + "status" + ], + "hosts": [ + "http://127.0.0.1" + ], + "period": "30s", + "server_status_path": "/server-status" + } + } + ], + "vars": { + "hosts": { + "value": [ + "http://127.0.0.1" + ], + "type": "text" + } + } + } + ], + "package": { + "name": "apache", + "title": "Apache", + "version": "1.1.0" + }, + "revision": 1, + "created_at": "2021-09-29T09:52:12.865Z", + "created_by": "elastic", + "updated_at": "2021-09-29T09:52:12.865Z", + "updated_by": "elastic" + } + ], + "total": 1, + "page": 1, + "perPage": 20 +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts new file mode 100644 index 00000000000000..804fe56510c1db --- /dev/null +++ b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ADD_AGENT_BUTTON, AGENT_POLICIES_TAB, ENROLLMENT_TOKENS_TAB } from '../screens/fleet'; +import { FLEET, navigateTo } from '../tasks/navigation'; + +describe('Fleet startup', () => { + before(() => { + navigateTo(FLEET); + }); + + it('should display Add agent button and Healthy agent once Fleet Agent page loaded', () => { + cy.getBySel(ADD_AGENT_BUTTON).contains('Add agent'); + cy.get('.euiBadge').contains('Healthy'); + }); + + it('should display default agent policies on agent policies tab', () => { + cy.getBySel(AGENT_POLICIES_TAB).click(); + cy.get('.euiLink').contains('Default policy'); + cy.get('.euiLink').contains('Default Fleet Server policy'); + }); + + it('should display default tokens on enrollment tokens tab', () => { + cy.getBySel(ENROLLMENT_TOKENS_TAB).click(); + cy.get('.euiTableRow').should('have.length', 2); + cy.get('.euiTableRowCell').contains('Default policy'); + cy.get('.euiTableRowCell').contains('Default Fleet Server policy'); + }); +}); diff --git a/x-pack/plugins/fleet/cypress/integration/integrations.spec.ts b/x-pack/plugins/fleet/cypress/integration/integrations.spec.ts new file mode 100644 index 00000000000000..88769ece39f2f7 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/integration/integrations.spec.ts @@ -0,0 +1,96 @@ +/* + * 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 { INTEGRATIONS, navigateTo } from '../tasks/navigation'; +import { + addIntegration, + installPackageWithVersion, + deleteIntegrations, + clickIfVisible, +} from '../tasks/integrations'; +import { + CONFIRM_MODAL_BTN, + FLYOUT_CLOSE_BTN_SEL, + INTEGRATIONS_CARD, + INTEGRATION_NAME_LINK, + LATEST_VERSION, + PACKAGE_VERSION, + POLICIES_TAB, + SETTINGS_TAB, + UPDATE_PACKAGE_BTN, +} from '../screens/integrations'; + +describe('Add Integration', () => { + const integration = 'Apache'; + + describe('Real API', () => { + afterEach(() => { + deleteIntegrations(integration); + }); + it('should display Apache integration in the Policies list once installed ', () => { + addAndVerifyIntegration(); + }); + + it('should upgrade policies with integration update', () => { + const oldVersion = '0.3.3'; + installPackageWithVersion('apache', oldVersion); + navigateTo(`app/integrations/detail/apache-${oldVersion}/policies`); + + addIntegration(); + + cy.getBySel(INTEGRATION_NAME_LINK).contains('apache-'); + cy.getBySel(PACKAGE_VERSION).contains(oldVersion); + + clickIfVisible(FLYOUT_CLOSE_BTN_SEL); + + cy.getBySel(SETTINGS_TAB).click(); + cy.getBySel(UPDATE_PACKAGE_BTN).click(); + cy.getBySel(CONFIRM_MODAL_BTN).click(); + + cy.getBySel(LATEST_VERSION).then(($title) => { + const newVersion = $title.text(); + cy.get('#upgradePoliciesCheckbox').should('not.exist'); + cy.getBySel(POLICIES_TAB).click(); + cy.getBySel(PACKAGE_VERSION).contains(oldVersion).should('not.exist'); + cy.getBySel(PACKAGE_VERSION).contains(newVersion); + }); + }); + }); + + function addAndVerifyIntegration() { + cy.intercept('GET', '/api/fleet/epm/packages?*').as('packages'); + navigateTo(INTEGRATIONS); + cy.wait('@packages'); + cy.get('.euiLoadingSpinner').should('not.exist'); + cy.get('input[placeholder="Search for integrations"]').type('Apache'); + cy.get(INTEGRATIONS_CARD).contains(integration).click(); + addIntegration(); + cy.getBySel(INTEGRATION_NAME_LINK).contains('apache-'); + } + + it.skip('[Mocked requests] should display Apache integration in the Policies list once installed ', () => { + cy.intercept('POST', '/api/fleet/package_policies', { + fixture: 'integrations/create_integration_response.json', + }); + cy.intercept( + 'GET', + '/api/fleet/package_policies?page=1&perPage=20&kuery=ingest-package-policies.package.name%3A%20apache', + { fixture: 'integrations/list.json' } + ); + cy.intercept('GET', '/api/fleet/agent_policies?*', { + fixture: 'integrations/agent_policies.json', + }); + cy.intercept('GET', '/api/fleet/agent_policies/30e16140-2106-11ec-a289-25321523992d', { + fixture: 'integrations/agent_policy.json', + }); + // TODO fixture includes 1 package policy, should be empty initially + cy.intercept('GET', '/api/fleet/epm/packages/apache-1.1.0', { + fixture: 'integrations/apache.json', + }); + addAndVerifyIntegration(); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.js b/x-pack/plugins/fleet/cypress/plugins/index.ts similarity index 91% rename from x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.js rename to x-pack/plugins/fleet/cypress/plugins/index.ts index aeeb88c6d12798..a30fd07912cf87 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.js +++ b/x-pack/plugins/fleet/cypress/plugins/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -/// +// / // *********************************************************** // This example plugins/index.js can be used to load plugins // @@ -22,7 +22,7 @@ /** * @type {Cypress.PluginConfig} */ -module.exports = () => { +module.exports = (_on: any, _config: any) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config }; diff --git a/x-pack/plugins/fleet/cypress/screens/fleet.ts b/x-pack/plugins/fleet/cypress/screens/fleet.ts new file mode 100644 index 00000000000000..6be51e5ed24bce --- /dev/null +++ b/x-pack/plugins/fleet/cypress/screens/fleet.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. + */ + +export const ADD_AGENT_BUTTON = 'addAgentButton'; + +export const AGENT_POLICIES_TAB = 'fleet-agent-policies-tab'; +export const ENROLLMENT_TOKENS_TAB = 'fleet-enrollment-tokens-tab'; diff --git a/x-pack/plugins/fleet/cypress/screens/integrations.ts b/x-pack/plugins/fleet/cypress/screens/integrations.ts new file mode 100644 index 00000000000000..d42fb904b3224c --- /dev/null +++ b/x-pack/plugins/fleet/cypress/screens/integrations.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. + */ + +export const ADD_POLICY_BTN = 'addIntegrationPolicyButton'; +export const CREATE_PACKAGE_POLICY_SAVE_BTN = 'createPackagePolicySaveButton'; +export const INTEGRATIONS_CARD = '.euiCard__titleAnchor'; + +export const INTEGRATION_NAME_LINK = 'integrationNameLink'; + +export const CONFIRM_MODAL_BTN = 'confirmModalConfirmButton'; +export const CONFIRM_MODAL_BTN_SEL = `[data-test-subj=${CONFIRM_MODAL_BTN}]`; + +export const FLYOUT_CLOSE_BTN_SEL = '[data-test-subj="euiFlyoutCloseButton"]'; + +export const SETTINGS_TAB = 'tab-settings'; +export const POLICIES_TAB = 'tab-policies'; + +export const UPDATE_PACKAGE_BTN = 'updatePackageBtn'; +export const LATEST_VERSION = 'latestVersion'; + +export const PACKAGE_VERSION = 'packageVersionText'; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/archives_metadata.ts b/x-pack/plugins/fleet/cypress/screens/navigation.ts similarity index 62% rename from x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/archives_metadata.ts rename to x-pack/plugins/fleet/cypress/screens/navigation.ts index 3382f0f8ee4601..fee38161b6b2b7 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/es_archiver/archives_metadata.ts +++ b/x-pack/plugins/fleet/cypress/screens/navigation.ts @@ -5,10 +5,4 @@ * 2.0. */ -/* eslint-disable-next-line*/ - export default { - 'apm_8.0.0': { - start: '2021-08-03T06:50:15.910Z', - end: '2021-08-03T07:20:15.910Z', - }, -}; +export const TOGGLE_NAVIGATION_BTN = '[data-test-subj="toggleNavButton"]'; diff --git a/x-pack/plugins/fleet/cypress/support/commands.ts b/x-pack/plugins/fleet/cypress/support/commands.ts new file mode 100644 index 00000000000000..54cc44f0057f3b --- /dev/null +++ b/x-pack/plugins/fleet/cypress/support/commands.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// diff --git a/x-pack/plugins/fleet/cypress/support/index.ts b/x-pack/plugins/fleet/cypress/support/index.ts new file mode 100644 index 00000000000000..f074e424d93c34 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/support/index.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +// / + +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + getBySel(value: string): Chainable; + } + } +} + +function getBySel(selector: string, ...args: any[]) { + return cy.get(`[data-test-subj=${selector}]`, ...args); +} + +Cypress.Commands.add('getBySel', getBySel); + +// Alternatively you can use CommonJS syntax: +// require('./commands') +Cypress.on('uncaught:exception', () => { + return false; +}); diff --git a/x-pack/plugins/fleet/cypress/tasks/integrations.ts b/x-pack/plugins/fleet/cypress/tasks/integrations.ts new file mode 100644 index 00000000000000..f1c891fa1186c0 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/tasks/integrations.ts @@ -0,0 +1,58 @@ +/* + * 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 { + ADD_POLICY_BTN, + CONFIRM_MODAL_BTN, + CREATE_PACKAGE_POLICY_SAVE_BTN, + FLYOUT_CLOSE_BTN_SEL, + INTEGRATION_NAME_LINK, +} from '../screens/integrations'; + +export const addIntegration = () => { + cy.getBySel(ADD_POLICY_BTN).click(); + cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).click(); + // sometimes agent is assigned to default policy, sometimes not + cy.getBySel(CONFIRM_MODAL_BTN).click(); + + cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).should('not.exist'); + clickIfVisible(FLYOUT_CLOSE_BTN_SEL); +}; + +export function clickIfVisible(selector: string) { + cy.get('body').then(($body) => { + if ($body.find(selector).length) { + cy.get(selector).click(); + } + }); +} + +export const deleteIntegrations = async (integration: string) => { + const ids: string[] = []; + cy.getBySel(INTEGRATION_NAME_LINK) + .each(($a) => { + const href = $a.attr('href') as string; + ids.push(href.substr(href.lastIndexOf('/') + 1)); + }) + .then(() => { + cy.request({ + url: `/api/fleet/package_policies/delete`, + headers: { 'kbn-xsrf': 'cypress' }, + body: `{ "packagePolicyIds": ${JSON.stringify(ids)} }`, + method: 'POST', + }); + }); +}; + +export const installPackageWithVersion = (integration: string, version: string) => { + cy.request({ + url: `/api/fleet/epm/packages/${integration}-${version}`, + headers: { 'kbn-xsrf': 'cypress' }, + body: '{ "force": true }', + method: 'POST', + }); +}; diff --git a/x-pack/plugins/fleet/cypress/tasks/navigation.ts b/x-pack/plugins/fleet/cypress/tasks/navigation.ts new file mode 100644 index 00000000000000..a2dd131b647a6a --- /dev/null +++ b/x-pack/plugins/fleet/cypress/tasks/navigation.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. + */ + +import { TOGGLE_NAVIGATION_BTN } from '../screens/navigation'; + +export const INTEGRATIONS = 'app/integrations#/'; +export const FLEET = 'app/fleet/'; + +export const navigateTo = (page: string) => { + cy.visit(page); +}; + +export const openNavigationFlyout = () => { + cy.get(TOGGLE_NAVIGATION_BTN).click(); +}; diff --git a/x-pack/plugins/fleet/cypress/tsconfig.json b/x-pack/plugins/fleet/cypress/tsconfig.json new file mode 100644 index 00000000000000..1adb067fe682e6 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.base.json", + "include": [ + "**/*" + ], + "exclude": [ + "target/**/*" + ], + "compilerOptions": { + "outDir": "target/types", + "types": [ + "cypress", + "node" + ], + "resolveJsonModule": true, + }, + } diff --git a/x-pack/plugins/fleet/dev_docs/api_integration_tests.md b/x-pack/plugins/fleet/dev_docs/api_integration_tests.md index bf1a84ae4c5634..7372c06269ce44 100644 --- a/x-pack/plugins/fleet/dev_docs/api_integration_tests.md +++ b/x-pack/plugins/fleet/dev_docs/api_integration_tests.md @@ -35,7 +35,7 @@ Port `12345` is used as an example here, it can be anything, but the environment ## DockerServers service setup We use the `DockerServers` service provided by `kbn-test`. The documentation for this functionality can be found here: -https://github.com/elastic/kibana/blob/master/packages/kbn-test/src/functional_test_runner/lib/docker_servers/README.md +https://github.com/elastic/kibana/blob/main/packages/kbn-test/src/functional_test_runner/lib/docker_servers/README.md The main configuration for the `DockerServers` service for our tests can be found in `x-pack/test/fleet_api_integration/config.ts`: diff --git a/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.excalidraw b/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.excalidraw index bfd73693d156db..5be5f2758f00c4 100644 --- a/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.excalidraw +++ b/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.excalidraw @@ -358,7 +358,7 @@ ], "fontSize": 20, "fontFamily": 2, - "text": "Fleet and Integrations are both Kibana applications. Kibana's architecture\nallows for many plugins/application to exist and register themselves as distinct\nexperiences. The Fleet team mainly works on these two Kibana applications \nwithin the `xpack/plugins/fleet` directory in the Kibana codebase.\n\nhttps://github.com/elastic/kibana/tree/master/x-pack/plugins/fleet\nhttps://www.elastic.co/guide/en/fleet/current/fleet-overview.html\n", + "text": "Fleet and Integrations are both Kibana applications. Kibana's architecture\nallows for many plugins/application to exist and register themselves as distinct\nexperiences. The Fleet team mainly works on these two Kibana applications \nwithin the `xpack/plugins/fleet` directory in the Kibana codebase.\n\nhttps://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet\nhttps://www.elastic.co/guide/en/fleet/current/fleet-overview.html\n", "baseline": 179, "textAlign": "left", "verticalAlign": "top" diff --git a/x-pack/plugins/fleet/dev_docs/tracing.md b/x-pack/plugins/fleet/dev_docs/tracing.md index 1f2d51cc214ec4..e5607667450432 100644 --- a/x-pack/plugins/fleet/dev_docs/tracing.md +++ b/x-pack/plugins/fleet/dev_docs/tracing.md @@ -10,7 +10,7 @@ To use it in Fleet, https://github.com/elastic/kibana/blob/a537f9af500bc3d3a6e2ceea8817ee89c474cbb0/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts#L30-L31 -
an example for startTransaction Transaction docs

Screen Shot 2020-11-02 at 9 06 50 AM

-
an example for startSpanSpan docs

Screen Shot 2020-06-02 at 9 15 42 PM

- 1. start Kibana with APM enabled (as described in [Instrumenting with Elastic APM](https://github.com/elastic/kibana/blob/master/docs/developer/getting-started/debugging.asciidoc#instrumenting-with-elastic-apm)) + 1. start Kibana with APM enabled (as described in [Instrumenting with Elastic APM](https://github.com/elastic/kibana/blob/main/docs/developer/getting-started/debugging.asciidoc#instrumenting-with-elastic-apm)) -
via env variable or config/apm.dev.js ELASTIC_APM_ACTIVE=true yarn start

or module.exports = { diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 1ca88cac1cc11b..d516827ebf9a74 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -9,7 +9,7 @@ "ui": true, "configPath": ["xpack", "fleet"], "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share"], - "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch"], + "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch", "telemetry"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils", "usageCollection"] } diff --git a/x-pack/plugins/fleet/package.json b/x-pack/plugins/fleet/package.json index e374dabb82458c..ef15c2fc6bb667 100644 --- a/x-pack/plugins/fleet/package.json +++ b/x-pack/plugins/fleet/package.json @@ -3,5 +3,11 @@ "name": "fleet", "version": "8.0.0", "private": true, - "license": "Elastic-License" + "license": "Elastic-License", + "scripts": { + "cypress:open": "../../../node_modules/.bin/cypress open --config-file ./cypress/cypress.json", + "cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/fleet_cypress/visual_config.ts", + "cypress:run": "../../../node_modules/.bin/cypress run --config-file ./cypress/cypress.json", + "cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/fleet_cypress/cli_config.ts" + } } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx index 8229aced234fa3..28b04e2fb69ac7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx @@ -237,6 +237,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({ } > -

- -

- - ) : ( + if (isEdit) { + return ( + +

+ +

+
+ ); + } + + if (isUpgrade) { + return ( + +

+ +

+
+ ); + } + + return (

{ const { docLinks } = useStartServices(); + + // Fetch all packagePolicies having the package name + const { data: packagePolicyData, isLoading: isLoadingPackagePolicies } = useGetPackagePolicies({ + perPage: SO_SEARCH_LIMIT, + page: 1, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageInfo.name}`, + }); + // Form show/hide states const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); @@ -84,33 +88,37 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ // Update package policy's package and agent policy info useEffect(() => { + if (isLoadingPackagePolicies) { + return; + } const pkg = packagePolicy.package; const currentPkgKey = pkg ? pkgKeyFromPackageInfo(pkg) : ''; const pkgKey = pkgKeyFromPackageInfo(packageInfo); // If package has changed, create shell package policy with input&stream values based on package info if (currentPkgKey !== pkgKey) { - // Existing package policies on the agent policy using the package name, retrieve highest number appended to package policy name + // Retrieve highest number appended to package policy name and increment it by one const pkgPoliciesNamePattern = new RegExp(`${packageInfo.name}-(\\d+)`); - const pkgPoliciesWithMatchingNames = agentPolicy - ? (agentPolicy.package_policies as PackagePolicy[]) + const pkgPoliciesWithMatchingNames = packagePolicyData?.items + ? packagePolicyData.items .filter((ds) => Boolean(ds.name.match(pkgPoliciesNamePattern))) .map((ds) => parseInt(ds.name.match(pkgPoliciesNamePattern)![1], 10)) .sort((a, b) => a - b) : []; + const incrementedName = `${packageInfo.name}-${ + pkgPoliciesWithMatchingNames.length + ? pkgPoliciesWithMatchingNames[pkgPoliciesWithMatchingNames.length - 1] + 1 + : 1 + }`; + updatePackagePolicy( packageToPackagePolicy( packageInfo, agentPolicy?.id || '', packagePolicy.output_id, packagePolicy.namespace, - packagePolicy.name || - `${packageInfo.name}-${ - pkgPoliciesWithMatchingNames.length - ? pkgPoliciesWithMatchingNames[pkgPoliciesWithMatchingNames.length - 1] + 1 - : 1 - }`, + packagePolicy.name || incrementedName, packagePolicy.description, integrationToEnable ) @@ -124,7 +132,15 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ namespace: agentPolicy.namespace, }); } - }, [packagePolicy, agentPolicy, packageInfo, updatePackagePolicy, integrationToEnable]); + }, [ + packagePolicy, + agentPolicy, + packageInfo, + updatePackagePolicy, + integrationToEnable, + packagePolicyData, + isLoadingPackagePolicies, + ]); return validationResults ? ( ) => { + setIsEdited(true); const newPackagePolicy = { ...packagePolicy, ...updatedFields, @@ -343,6 +344,7 @@ export const EditPackagePolicyForm = memo<{ }, [from, getHref, packageInfo, policyId]); // Save package policy + const [isEdited, setIsEdited] = useState(false); const [formState, setFormState] = useState('INVALID'); const savePackagePolicy = async () => { setFormState('LOADING'); @@ -582,7 +584,8 @@ export const EditPackagePolicyForm = memo<{ (false); useEffect(() => { - const stateStorage = createKbnUrlStateStorage(); + const stateStorage = createKbnUrlStateStorage({ useHashQuery: false, useHash: false }); const { start, stop } = syncState({ storageKey: STATE_STORAGE_KEY, stateContainer: stateContainer as INullableBaseStateContainer, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index d6a6210bc86730..5fa60eb72b2e54 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -224,6 +224,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{ fill iconType="plusInCircle" onClick={() => setIsEnrollmentFlyoutOpen(true)} + data-test-subj="addAgentButton" > 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 f135f214f4d7fd..655488e3ae6a75 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 @@ -100,7 +100,9 @@ export const DataStreamListPage: React.FunctionComponent<{}> = () => { }), render: (date: DataStream['last_activity_ms']) => { try { - const formatter = fieldFormats.getInstance('date'); + const formatter = fieldFormats.getInstance('date', { + pattern: 'MMM D, YYYY @ HH:mm:ss', + }); return formatter.convert(date); } catch (e) { return ; diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index c2f6f53627e382..3a091c30bb792d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -5,21 +5,14 @@ * 2.0. */ -import React, { memo, useEffect, useState } from 'react'; +import React, { memo } from 'react'; import type { AppMountParameters } from 'kibana/public'; import { EuiErrorBoundary, EuiPortal } from '@elastic/eui'; import type { History } from 'history'; import { Router, Redirect, Route, Switch } from 'react-router-dom'; -import { FormattedMessage } from '@kbn/i18n/react'; import useObservable from 'react-use/lib/useObservable'; -import { - ConfigContext, - FleetStatusProvider, - KibanaVersionContext, - sendGetPermissionsCheck, - sendSetup, -} from '../../hooks'; +import { ConfigContext, FleetStatusProvider, KibanaVersionContext } from '../../hooks'; import type { FleetConfigType, FleetStartServices } from '../../plugin'; @@ -32,91 +25,14 @@ import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/com import { AgentPolicyContextProvider, useUrlModal } from './hooks'; import { INTEGRATIONS_ROUTING_PATHS, pagePathGetters } from './constants'; -import { Error, Loading, SettingFlyout } from './components'; +import { SettingFlyout } from './components'; import type { UIExtensionsStorage } from './types'; import { EPMApp } from './sections/epm'; -import { DefaultLayout } from './layouts'; -import { PackageInstallProvider } from './hooks'; -import { useBreadcrumbs, UIExtensionsContext } from './hooks'; +import { PackageInstallProvider, UIExtensionsContext } from './hooks'; import { IntegrationsHeader } from './components/header'; -const ErrorLayout = ({ children }: { children: JSX.Element }) => ( - - {children} - -); - -export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { - useBreadcrumbs('integrations'); - - const [isPermissionsLoading, setIsPermissionsLoading] = useState(false); - const [isInitialized, setIsInitialized] = useState(false); - const [initializationError, setInitializationError] = useState(null); - - useEffect(() => { - (async () => { - setIsInitialized(false); - setInitializationError(null); - try { - // Attempt Fleet Setup if user has permissions, otherwise skip - setIsPermissionsLoading(true); - const permissionsResponse = await sendGetPermissionsCheck(); - setIsPermissionsLoading(false); - - if (permissionsResponse.data?.success) { - try { - const setupResponse = await sendSetup(); - if (setupResponse.error) { - setInitializationError(setupResponse.error); - } - } catch (err) { - setInitializationError(err); - } - setIsInitialized(true); - } else { - setIsInitialized(true); - } - } catch { - // If there's an error checking permissions, default to proceeding without running setup - // User will only have access to EPM endpoints if they actually have permission - setIsInitialized(true); - } - })(); - }, []); - - if (isPermissionsLoading) { - return ( - - - - ); - } - - if (!isInitialized || initializationError) { - return ( - - {initializationError ? ( - - } - error={initializationError} - /> - ) : ( - - )} - - ); - } - - return <>{children}; -}); - /** * Fleet Application context all the way down to the Router, but with no permissions or setup checks * and no routes defined diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts index d5d8aa093e300f..f69132d9a64524 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts @@ -120,6 +120,17 @@ describe('useMergeEprWithReplacements', () => { ]); }); + test('should filter out apm from package list', () => { + const eprPackages: PackageListItem[] = mockEprPackages([ + { + name: 'apm', + release: 'beta', + }, + ]); + + expect(useMergeEprPackagesWithReplacements(eprPackages, [])).toEqual([]); + }); + test('should consists of all 3 types (ga eprs, replacements for non-ga eprs, replacements without epr equivalent', () => { const eprPackages: PackageListItem[] = mockEprPackages([ { @@ -136,6 +147,10 @@ describe('useMergeEprWithReplacements', () => { name: 'activemq', release: 'beta', }, + { + name: 'apm', + release: 'ga', + }, ]); const replacements: CustomIntegration[] = mockIntegrations([ { diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts index 4c59f0ef451235..ff1b51ef19a813 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts @@ -8,6 +8,7 @@ import type { PackageListItem } from '../../../../common/types/models'; import type { CustomIntegration } from '../../../../../../../src/plugins/custom_integrations/common'; import { filterCustomIntegrations } from '../../../../../../../src/plugins/custom_integrations/public'; +import { FLEET_APM_PACKAGE } from '../../../../common/constants'; // Export this as a utility to find replacements for a package (e.g. in the overview-page for an EPR package) function findReplacementsForEprPackage( @@ -22,12 +23,17 @@ function findReplacementsForEprPackage( } export function useMergeEprPackagesWithReplacements( - eprPackages: PackageListItem[], + rawEprPackages: PackageListItem[], replacements: CustomIntegration[] ): Array { const merged: Array = []; const filteredReplacements = replacements; + // APM EPR-packages should _never_ show. They have special handling. + const eprPackages = rawEprPackages.filter((p) => { + return p.name !== FLEET_APM_PACKAGE; + }); + // Either select replacement or select beat eprPackages.forEach((eprPackage: PackageListItem) => { const hits = findReplacementsForEprPackage( diff --git a/x-pack/plugins/fleet/public/applications/integrations/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/index.tsx index 4099879538afa7..620cf83fd762de 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/index.tsx @@ -15,7 +15,7 @@ import type { FleetConfigType, FleetStartServices } from '../../plugin'; import { licenseService } from '../../hooks'; import type { UIExtensionsStorage } from '../../types'; -import { AppRoutes, IntegrationsAppContext, WithPermissionsAndSetup } from './app'; +import { AppRoutes, IntegrationsAppContext } from './app'; export interface ProtectedRouteProps extends RouteProps { isAllowed?: boolean; @@ -58,9 +58,7 @@ const IntegrationsApp = ({ extensions={extensions} setHeaderActionMenu={setHeaderActionMenu} > - - - + ); }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx index 9c9027fb94ac52..fa9b9a7ed190e0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx @@ -22,6 +22,8 @@ import { EuiFlexItem, } from '@elastic/eui'; +import { useStartServices } from '../../../hooks'; + export type IntegrationPreferenceType = 'recommended' | 'beats' | 'agent'; interface Option { @@ -34,23 +36,6 @@ export interface Props { onChange: (type: IntegrationPreferenceType) => void; } -const link = ( - - - -); - -const title = ( - -); - const recommendedTooltip = ( { const [idSelected, setIdSelected] = React.useState(initialType); + + const { docLinks } = useStartServices(); + + const link = ( + + + + ); + + const title = ( + + ); + const radios = options.map((option) => ({ id: option.type, value: option.type, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/package_policy_agents_cell.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/package_policy_agents_cell.tsx index 0ecab3290051e4..fc3007b174ced5 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/package_policy_agents_cell.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/package_policy_agents_cell.tsx @@ -22,8 +22,12 @@ const AddAgentButton = ({ onAddAgent }: { onAddAgent: () => void }) => ( ); const AddAgentButtonWithPopover = ({ onAddAgent }: { onAddAgent: () => void }) => { - const button = ; const [isHelpOpen, setIsHelpOpen] = useState(true); + const onAddAgentCloseHelp = () => { + setIsHelpOpen(false); + onAddAgent(); + }; + const button = ; return ( - + = memo(({ packageInfo }: Props) => { /> - + {installedVersion} @@ -262,7 +262,7 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { /> - + {latestVersion} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx index b5a8394fa2cb22..48d4ef5d846d49 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx @@ -289,6 +289,7 @@ export const UpdateButton: React.FunctionComponent = ({ onClick={ upgradePackagePolicies ? () => setIsUpdateModalVisible(true) : handleClickUpdate } + data-test-subj="updatePackageBtn" > { if (selectedCategory === '') { return true; } + return c.categories.includes(selectedCategory); }); @@ -255,7 +256,7 @@ export const AvailablePackages: React.FC = memo(() => { defaultMessage: 'Monitor, detect and diagnose complex performance issues from your application.', })} - href={addBasePath('/app/integrations/detail/apm')} + href={addBasePath('/app/home#/tutorial/apm')} icon={} /> diff --git a/x-pack/plugins/fleet/public/components/add_agent_help_popover.tsx b/x-pack/plugins/fleet/public/components/add_agent_help_popover.tsx index e47070fdc7b996..32d974324f13fd 100644 --- a/x-pack/plugins/fleet/public/components/add_agent_help_popover.tsx +++ b/x-pack/plugins/fleet/public/components/add_agent_help_popover.tsx @@ -11,6 +11,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import type { NoArgCallback } from '@elastic/eui'; import { EuiTourStep, EuiLink, EuiText } from '@elastic/eui'; +import { useTheme } from 'styled-components'; + +import type { EuiTheme } from '../../../../../src/plugins/kibana_react/common'; import { useStartServices } from '../hooks'; @@ -26,7 +29,7 @@ export const AddAgentHelpPopover = ({ closePopover: NoArgCallback; }) => { const { docLinks } = useStartServices(); - + const theme = useTheme() as EuiTheme; const optionalProps: { offset?: number } = {}; if (offset !== undefined) { @@ -55,6 +58,7 @@ export const AddAgentHelpPopover = ({ /> } + zIndex={theme.eui.euiZLevel1 - 1} // put popover behind any modals that happen to be open isStepOpen={isOpen} minWidth={300} onFinish={() => {}} diff --git a/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx b/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx index 24d9dc8e2c1004..1b0d90098fa48b 100644 --- a/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx +++ b/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx @@ -13,6 +13,7 @@ import type { TutorialModuleNoticeComponent } from 'src/plugins/home/public'; import { useGetPackages, useLink, useCapabilities } from '../../hooks'; import { pkgKeyFromPackageInfo } from '../../services'; +import { FLEET_APM_PACKAGE } from '../../../common/constants'; const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }) => { const { getHref } = useLink(); @@ -22,7 +23,7 @@ const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName } const pkgInfo = !isLoading && packagesData?.response && - packagesData.response.find((pkg) => pkg.name === moduleName); + packagesData.response.find((pkg) => pkg.name === moduleName && pkg.name !== FLEET_APM_PACKAGE); // APM needs special handling if (hasIngestManager && pkgInfo) { return ( diff --git a/x-pack/plugins/fleet/public/components/index.ts b/x-pack/plugins/fleet/public/components/index.ts index 3252315312d029..9015071450bf04 100644 --- a/x-pack/plugins/fleet/public/components/index.ts +++ b/x-pack/plugins/fleet/public/components/index.ts @@ -13,7 +13,8 @@ export { LinkedAgentCount } from './linked_agent_count'; export { ExtensionWrapper } from './extension_wrapper'; export { AlphaMessaging } from './alpha_messaging'; export { AlphaFlyout } from './alpha_flyout'; -export { HeaderProps, Header } from './header'; +export type { HeaderProps } from './header'; +export { Header } from './header'; export { NewEnrollmentTokenModal } from './new_enrollment_key_modal'; export { AgentPolicyPackageBadges } from './agent_policy_package_badges'; export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts index c41dd1ad42a726..16454a266c3c48 100644 --- a/x-pack/plugins/fleet/public/hooks/index.ts +++ b/x-pack/plugins/fleet/public/hooks/index.ts @@ -12,8 +12,10 @@ export { useKibanaVersion, KibanaVersionContext } from './use_kibana_version'; export { licenseService, useLicense } from './use_license'; export { useLink } from './use_link'; export { useKibanaLink, getHrefToObjectInKibanaApp } from './use_kibana_link'; -export { usePackageIconType, UsePackageIconType } from './use_package_icon_type'; -export { usePagination, Pagination, PAGE_SIZE_OPTIONS } from './use_pagination'; +export type { UsePackageIconType } from './use_package_icon_type'; +export { usePackageIconType } from './use_package_icon_type'; +export type { Pagination } from './use_pagination'; +export { usePagination, PAGE_SIZE_OPTIONS } from './use_pagination'; export { useUrlPagination } from './use_url_pagination'; export { useSorting } from './use_sorting'; export { useDebounce } from './use_debounce'; diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index b54d00eafdaabe..ee4fe526fb0e41 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -12,7 +12,7 @@ import type { PluginInitializerContext } from 'src/core/public'; import { FleetPlugin } from './plugin'; -export { FleetSetup, FleetStart } from './plugin'; +export type { FleetSetup, FleetStart } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => { return new FleetPlugin(initializerContext); @@ -24,7 +24,5 @@ export * from './types/ui_extensions'; export { pagePathGetters } from './constants'; export { pkgKeyFromPackageInfo } from './services'; -export { - CustomAssetsAccordion, - CustomAssetsAccordionProps, -} from './components/custom_assets_accordion'; +export type { CustomAssetsAccordionProps } from './components/custom_assets_accordion'; +export { CustomAssetsAccordion } from './components/custom_assets_accordion'; diff --git a/x-pack/plugins/fleet/public/layouts/index.ts b/x-pack/plugins/fleet/public/layouts/index.ts index e6e366654a866b..62e3c5b889ca8e 100644 --- a/x-pack/plugins/fleet/public/layouts/index.ts +++ b/x-pack/plugins/fleet/public/layouts/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { WithHeaderLayout, WithHeaderLayoutProps } from './with_header'; +export type { WithHeaderLayoutProps } from './with_header'; +export { WithHeaderLayout } from './with_header'; export { WithoutHeaderLayout } from './without_header'; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index b0e4e56aa344a0..039c1da9b934cc 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -52,7 +52,7 @@ import { createExtensionRegistrationCallback } from './services/ui_extensions'; import type { UIExtensionRegistrationCallback, UIExtensionsStorage } from './types'; import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension'; -export { FleetConfigType } from '../common/types'; +export type { FleetConfigType } from '../common/types'; import { setCustomIntegrations } from './services/custom_integrations'; diff --git a/x-pack/plugins/fleet/public/services/index.ts b/x-pack/plugins/fleet/public/services/index.ts index fbd3bddde744b8..10dfe6b59d6ba3 100644 --- a/x-pack/plugins/fleet/public/services/index.ts +++ b/x-pack/plugins/fleet/public/services/index.ts @@ -7,6 +7,11 @@ export { getFlattenedObject } from '@kbn/std'; +export type { + PackagePolicyValidationResults, + PackagePolicyConfigValidationResults, + PackagePolicyInputValidationResults, +} from '../../common'; export { AgentStatusKueryHelper, agentPolicyRouteService, @@ -30,9 +35,6 @@ export { LicenseService, isAgentUpgradeable, doesPackageHaveIntegrations, - PackagePolicyValidationResults, - PackagePolicyConfigValidationResults, - PackagePolicyInputValidationResults, validatePackagePolicy, validatePackagePolicyConfig, validationHasErrors, diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 3ff0a760b58828..d61223f0cebc78 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -5,10 +5,7 @@ * 2.0. */ -export { - // utility function - entries, - // Object types +export type { Agent, AgentMetadata, AgentPolicy, @@ -28,9 +25,7 @@ export { Output, DataStream, Settings, - // API schema - misc setup, status GetFleetStatusResponse, - // API schemas - Agent policy GetAgentPoliciesRequest, GetAgentPoliciesResponse, GetAgentPoliciesResponseItem, @@ -44,7 +39,6 @@ export { CopyAgentPolicyResponse, DeleteAgentPolicyRequest, DeleteAgentPolicyResponse, - // API schemas - Package policy CreatePackagePolicyRequest, CreatePackagePolicyResponse, UpdatePackagePolicyRequest, @@ -53,9 +47,7 @@ export { DryRunPackagePolicy, UpgradePackagePolicyResponse, UpgradePackagePolicyDryRunResponse, - // API schemas - Data streams GetDataStreamsResponse, - // API schemas - Agents GetAgentsResponse, GetAgentsRequest, GetOneAgentResponse, @@ -75,24 +67,19 @@ export { PostBulkAgentReassignResponse, PostNewAgentActionResponse, PostNewAgentActionRequest, - // API schemas - Enrollment API Keys GetEnrollmentAPIKeysResponse, GetEnrollmentAPIKeysRequest, GetOneEnrollmentAPIKeyResponse, PostEnrollmentAPIKeyRequest, PostEnrollmentAPIKeyResponse, - // API schemas - Outputs GetOutputsResponse, PutOutputRequest, PutOutputResponse, - // API schemas - Settings GetSettingsResponse, PutSettingsRequest, PutSettingsResponse, - // API schemas - app CheckPermissionsResponse, GenerateServiceTokenResponse, - // EPM types AssetReference, AssetsGroupedByServiceByType, AssetType, @@ -100,8 +87,6 @@ export { CategoryId, CategorySummaryItem, CategorySummaryList, - ElasticsearchAssetType, - KibanaAssetType, PackageInfo, RegistryVarsEntry, RegistryInput, @@ -123,7 +108,6 @@ export { InstallPackageResponse, DeletePackageResponse, DetailViewPanelName, - InstallStatus, InstallationStatus, Installable, RegistryRelease, @@ -131,6 +115,7 @@ export { UpdatePackageRequest, UpdatePackageResponse, } from '../../common'; +export { entries, ElasticsearchAssetType, KibanaAssetType, InstallStatus } from '../../common'; export * from './intra_app_route_state'; export * from './ui_extensions'; diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 9ce361503ddf3d..e1ee2652594cc6 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -18,18 +18,18 @@ import { import { FleetPlugin } from './plugin'; export { default as apm } from 'elastic-apm-node'; -export { +export type { AgentService, ESIndexPatternService, - getRegistryUrl, PackageService, AgentPolicyServiceInterface, ArtifactsClientInterface, Artifact, ListArtifactsProps, } from './services'; +export { getRegistryUrl } from './services'; -export { FleetSetupContract, FleetSetupDeps, FleetStartContract } from './plugin'; +export type { FleetSetupContract, FleetSetupDeps, FleetStartContract } from './plugin'; export type { ExternalCallback, PutPackagePolicyUpdateCallback, @@ -45,42 +45,59 @@ export const config: PluginConfigDescriptor = { }, deprecations: ({ renameFromRoot, unused, unusedFromRoot }) => [ // Fleet plugin was named ingestManager before - renameFromRoot('xpack.ingestManager.enabled', 'xpack.fleet.enabled'), - renameFromRoot('xpack.ingestManager.registryUrl', 'xpack.fleet.registryUrl'), - renameFromRoot('xpack.ingestManager.registryProxyUrl', 'xpack.fleet.registryProxyUrl'), - renameFromRoot('xpack.ingestManager.fleet', 'xpack.ingestManager.agents'), - renameFromRoot('xpack.ingestManager.agents.enabled', 'xpack.fleet.agents.enabled'), - renameFromRoot('xpack.ingestManager.agents.elasticsearch', 'xpack.fleet.agents.elasticsearch'), + renameFromRoot('xpack.ingestManager.enabled', 'xpack.fleet.enabled', { level: 'critical' }), + renameFromRoot('xpack.ingestManager.registryUrl', 'xpack.fleet.registryUrl', { + level: 'critical', + }), + renameFromRoot('xpack.ingestManager.registryProxyUrl', 'xpack.fleet.registryProxyUrl', { + level: 'critical', + }), + renameFromRoot('xpack.ingestManager.fleet', 'xpack.ingestManager.agents', { + level: 'critical', + }), + renameFromRoot('xpack.ingestManager.agents.enabled', 'xpack.fleet.agents.enabled', { + level: 'critical', + }), + renameFromRoot('xpack.ingestManager.agents.elasticsearch', 'xpack.fleet.agents.elasticsearch', { + level: 'critical', + }), renameFromRoot( 'xpack.ingestManager.agents.tlsCheckDisabled', - 'xpack.fleet.agents.tlsCheckDisabled' + 'xpack.fleet.agents.tlsCheckDisabled', + { level: 'critical' } ), renameFromRoot( 'xpack.ingestManager.agents.pollingRequestTimeout', - 'xpack.fleet.agents.pollingRequestTimeout' + 'xpack.fleet.agents.pollingRequestTimeout', + { level: 'critical' } ), renameFromRoot( 'xpack.ingestManager.agents.maxConcurrentConnections', - 'xpack.fleet.agents.maxConcurrentConnections' + 'xpack.fleet.agents.maxConcurrentConnections', + { level: 'critical' } ), - renameFromRoot('xpack.ingestManager.agents.kibana', 'xpack.fleet.agents.kibana'), + renameFromRoot('xpack.ingestManager.agents.kibana', 'xpack.fleet.agents.kibana', { + level: 'critical', + }), renameFromRoot( 'xpack.ingestManager.agents.agentPolicyRolloutRateLimitIntervalMs', - 'xpack.fleet.agents.agentPolicyRolloutRateLimitIntervalMs' + 'xpack.fleet.agents.agentPolicyRolloutRateLimitIntervalMs', + { level: 'critical' } ), renameFromRoot( 'xpack.ingestManager.agents.agentPolicyRolloutRateLimitRequestPerInterval', - 'xpack.fleet.agents.agentPolicyRolloutRateLimitRequestPerInterval' + 'xpack.fleet.agents.agentPolicyRolloutRateLimitRequestPerInterval', + { level: 'critical' } ), - unusedFromRoot('xpack.ingestManager'), + unusedFromRoot('xpack.ingestManager', { level: 'critical' }), // Unused settings before Fleet server exists - unused('agents.kibana'), - unused('agents.maxConcurrentConnections'), - unused('agents.agentPolicyRolloutRateLimitIntervalMs'), - unused('agents.agentPolicyRolloutRateLimitRequestPerInterval'), - unused('agents.pollingRequestTimeout'), - unused('agents.tlsCheckDisabled'), - unused('agents.fleetServerEnabled'), + unused('agents.kibana', { level: 'critical' }), + unused('agents.maxConcurrentConnections', { level: 'critical' }), + unused('agents.agentPolicyRolloutRateLimitIntervalMs', { level: 'critical' }), + unused('agents.agentPolicyRolloutRateLimitRequestPerInterval', { level: 'critical' }), + unused('agents.pollingRequestTimeout', { level: 'critical' }), + unused('agents.tlsCheckDisabled', { level: 'critical' }), + unused('agents.fleetServerEnabled', { level: 'critical' }), // Renaming elasticsearch.host => elasticsearch.hosts (fullConfig, fromPath, addDeprecation) => { const oldValue = fullConfig?.xpack?.fleet?.agents?.elasticsearch?.host; @@ -95,6 +112,7 @@ export const config: PluginConfigDescriptor = { `Use [xpack.fleet.agents.elasticsearch.hosts] with an array of host instead.`, ], }, + level: 'critical', }); } @@ -120,12 +138,16 @@ export const config: PluginConfigDescriptor = { agentPolicies: PreconfiguredAgentPoliciesSchema, outputs: PreconfiguredOutputsSchema, agentIdVerificationEnabled: schema.boolean({ defaultValue: true }), + developer: schema.object({ + // TODO: change default to false as soon as EPR issue fixed. Blocker for 8.0. + disableRegistryVersionCheck: schema.boolean({ defaultValue: true }), + }), }), }; export type FleetConfigType = TypeOf; -export { PackagePolicyServiceInterface } from './services/package_policy'; +export type { PackagePolicyServiceInterface } from './services/package_policy'; export { relativeDownloadUrlFromArtifact } from './services/artifacts/mappings'; diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 9300e0bb6c3e18..943f100c94f729 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -19,6 +19,7 @@ import { securityMock } from '../../../security/server/mocks'; import type { PackagePolicyServiceInterface } from '../services/package_policy'; import type { AgentPolicyServiceInterface, AgentService } from '../services'; import type { FleetAppContext } from '../plugin'; +import { createMockTelemetryEventsSender } from '../telemetry/__mocks__'; // Export all mocks from artifacts export * from '../services/artifacts/mocks'; @@ -57,8 +58,9 @@ export const createAppContextStartContractMock = (): MockedFleetAppContext => { agentIdVerificationEnabled: true, }, config$, - kibanaVersion: '8.0.0', - kibanaBranch: 'master', + kibanaVersion: '8.99.0', // Fake version :) + kibanaBranch: 'main', + telemetryEventsSender: createMockTelemetryEventsSender(), }; }; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 410682a13733cb..7cc1b8b1cfcc9a 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -18,6 +18,8 @@ import type { } from 'kibana/server'; import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import type { TelemetryPluginSetup, TelemetryPluginStart } from 'src/plugins/telemetry/server'; + import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import type { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; import type { LicensingPluginSetup, ILicense } from '../../licensing/server'; @@ -83,6 +85,7 @@ import { RouterWrappers } from './routes/security'; import { startFleetServerSetup } from './services/fleet_server'; import { FleetArtifactsClient } from './services/artifacts'; import type { FleetRouter } from './types/request_context'; +import { TelemetryEventsSender } from './telemetry/sender'; export interface FleetSetupDeps { licensing: LicensingPluginSetup; @@ -91,12 +94,14 @@ export interface FleetSetupDeps { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; + telemetry?: TelemetryPluginSetup; } export interface FleetStartDeps { data: DataPluginStart; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; security?: SecurityPluginStart; + telemetry?: TelemetryPluginStart; } export interface FleetAppContext { @@ -115,6 +120,7 @@ export interface FleetAppContext { cloud?: CloudSetup; logger?: Logger; httpSetup?: HttpServiceSetup; + telemetryEventsSender: TelemetryEventsSender; } export type FleetSetupContract = void; @@ -176,6 +182,7 @@ export class FleetPlugin private httpSetup?: HttpServiceSetup; private securitySetup?: SecurityPluginSetup; private encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; + private readonly telemetryEventsSender: TelemetryEventsSender; constructor(private readonly initializerContext: PluginInitializerContext) { this.config$ = this.initializerContext.config.create(); @@ -184,6 +191,7 @@ export class FleetPlugin this.kibanaBranch = this.initializerContext.env.packageInfo.branch; this.logger = this.initializerContext.logger.get(); this.configInitialValue = this.initializerContext.config.get(); + this.telemetryEventsSender = new TelemetryEventsSender(this.logger.get('telemetry_events')); } public setup(core: CoreSetup, deps: FleetSetupDeps) { @@ -302,6 +310,8 @@ export class FleetPlugin }); } } + + this.telemetryEventsSender.setup(deps.telemetry); } public start(core: CoreStart, plugins: FleetStartDeps): FleetStartContract { @@ -321,11 +331,14 @@ export class FleetPlugin httpSetup: this.httpSetup, cloud: this.cloud, logger: this.logger, + telemetryEventsSender: this.telemetryEventsSender, }); licenseService.start(this.licensing$); const fleetServerSetup = startFleetServerSetup(); + this.telemetryEventsSender.start(plugins.telemetry, core); + return { fleetSetupCompleted: () => new Promise((resolve) => { @@ -362,5 +375,6 @@ export class FleetPlugin public async stop() { appContextService.stop(); licenseService.stop(); + this.telemetryEventsSender.stop(); } } diff --git a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts index 232df94d7610b0..85a983d21771d3 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts @@ -127,7 +127,7 @@ export const getListHandler: RequestHandler = async (context, request, response) type: '', package: dataStream._meta?.package?.name || '', package_version: '', - last_activity_ms: dataStream.maximum_timestamp, + last_activity_ms: dataStream.maximum_timestamp, // overridden below if maxIngestedTimestamp agg returns a result size_in_bytes: dataStream.store_size_bytes, dashboards: [], }; @@ -156,6 +156,11 @@ export const getListHandler: RequestHandler = async (context, request, response) }, }, aggs: { + maxIngestedTimestamp: { + max: { + field: 'event.ingested', + }, + }, dataset: { terms: { field: 'data_stream.dataset', @@ -178,12 +183,20 @@ export const getListHandler: RequestHandler = async (context, request, response) }, }); + const { maxIngestedTimestamp } = dataStreamAggs as Record< + string, + estypes.AggregationsValueAggregate + >; const { dataset, namespace, type } = dataStreamAggs as Record< string, - estypes.AggregationsMultiBucketAggregate<{ key?: string }> + estypes.AggregationsMultiBucketAggregate<{ key?: string; value?: number }> >; - // Set values from backing indices query + // some integrations e.g custom logs don't have event.ingested + if (maxIngestedTimestamp?.value) { + dataStreamResponse.last_activity_ms = maxIngestedTimestamp?.value; + } + dataStreamResponse.dataset = dataset.buckets[0]?.key || ''; dataStreamResponse.namespace = namespace.buckets[0]?.key || ''; dataStreamResponse.type = type.buckets[0]?.key || ''; diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 77e7a2c4ede1aa..58463bfa5569d2 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -146,7 +146,8 @@ export const updatePackagePolicyHandler: RequestHandler< esClient, request.params.packagePolicyId, { ...newData, package: pkg, inputs }, - { user } + { user }, + packagePolicy.package?.version ); return response.ok({ body: { item: updatedPackagePolicy }, diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_16_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_16_0.ts index b69523434408b7..c435e504a2bfe3 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_16_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_16_0.ts @@ -8,7 +8,7 @@ import type { SavedObjectMigrationFn } from 'kibana/server'; import type { Installation, PackagePolicy } from '../../../common'; -import { AUTO_UPDATE_PACKAGES, DEFAULT_PACKAGES } from '../../../common'; +import { DEFAULT_PACKAGES } from '../../../common'; import { migratePackagePolicyToV7160 as SecSolMigratePackagePolicyToV7160 } from './security_solution'; @@ -18,11 +18,7 @@ export const migrateInstallationToV7160: SavedObjectMigrationFn { const updatedInstallationDoc = installationDoc; - if ( - [...AUTO_UPDATE_PACKAGES, ...DEFAULT_PACKAGES].some( - (pkg) => pkg.name === updatedInstallationDoc.attributes.name - ) - ) { + if (DEFAULT_PACKAGES.some((pkg) => pkg.name === updatedInstallationDoc.attributes.name)) { updatedInstallationDoc.attributes.keep_policies_up_to_date = true; } diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 321bc7f289594d..7de907b9a15fad 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -410,12 +410,16 @@ class AgentPolicyService { options ); - // Copy all package policies + // Copy all package policies and append (copy) to their names if (baseAgentPolicy.package_policies.length) { const newPackagePolicies = (baseAgentPolicy.package_policies as PackagePolicy[]).map( (packagePolicy: PackagePolicy) => { const { id: packagePolicyId, version, ...newPackagePolicy } = packagePolicy; - return newPackagePolicy; + const updatedPackagePolicy = { + ...newPackagePolicy, + name: `${packagePolicy.name} (copy)`, + }; + return updatedPackagePolicy; } ); await packagePolicyService.bulkCreate( diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index a1e6ef4545aefe..7ec1607598b8aa 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -33,6 +33,7 @@ import type { } from '../types'; import type { FleetAppContext } from '../plugin'; import type { CloudSetup } from '../../../cloud/server'; +import type { TelemetryEventsSender } from '../telemetry/sender'; class AppContextService { private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined; @@ -51,6 +52,7 @@ class AppContextService { private logger: Logger | undefined; private httpSetup?: HttpServiceSetup; private externalCallbacks: ExternalCallbacksStorage = new Map(); + private telemetryEventsSender: TelemetryEventsSender | undefined; public start(appContext: FleetAppContext) { this.data = appContext.data; @@ -66,6 +68,7 @@ class AppContextService { this.kibanaVersion = appContext.kibanaVersion; this.kibanaBranch = appContext.kibanaBranch; this.httpSetup = appContext.httpSetup; + this.telemetryEventsSender = appContext.telemetryEventsSender; if (appContext.config$) { this.config$ = appContext.config$; @@ -203,6 +206,10 @@ class AppContextService { >; } } + + public getTelemetryEventsSender() { + return this.telemetryEventsSender; + } } export const appContextService = new AppContextService(); diff --git a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts index 29594b0a247a12..bc26388c990f32 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts @@ -264,7 +264,7 @@ export const getEsPackage = async ( dataStreams.push({ dataset: dataset || `${pkgName}.${dataStreamPath}`, package: pkgName, - ingest_pipeline: ingestPipeline || 'default', + ingest_pipeline: ingestPipeline, path: dataStreamPath, streams, ...dataStreamManifestProps, diff --git a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts index c530c61d0a8f77..a46a26738bdab9 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts @@ -217,7 +217,6 @@ export function parseAndVerifyDataStreams( } const streams = parseAndVerifyStreams(manifestStreams, dataStreamPath); - // default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26 dataStreams.push( Object.entries(restOfProps).reduce( (validatedDataStream, [key, value]) => { @@ -233,7 +232,7 @@ export function parseAndVerifyDataStreams( type, package: pkgName, dataset: dataset || `${pkgName}.${dataStreamPath}`, - ingest_pipeline: ingestPipeline || 'default', + ingest_pipeline: ingestPipeline, path: dataStreamPath, streams, } 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 8d3c1fbe0daa4a..b6a1850fed5b8e 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -30,7 +30,8 @@ import { normalizeKuery } from '../../saved_object'; import { createInstallableFrom, isUnremovablePackage } from './index'; -export { getFile, SearchParams } from '../registry'; +export type { SearchParams } from '../registry'; +export { getFile } from '../registry'; function nameAsTitle(name: string) { return name.charAt(0).toUpperCase() + name.substr(1).toLowerCase(); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/index.ts b/x-pack/plugins/fleet/server/services/epm/packages/index.ts index 58e7c9e8928d88..a6970a8d19db4e 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/index.ts @@ -12,6 +12,7 @@ import { KibanaAssetType } from '../../../types'; import type { AssetType, Installable, Installation } from '../../../types'; export { bulkInstallPackages, isBulkInstallError } from './bulk_install_packages'; +export type { SearchParams } from './get'; export { getCategories, getFile, @@ -21,16 +22,10 @@ export { getPackageInfo, getPackages, getLimitedPackages, - SearchParams, } from './get'; -export { - BulkInstallResponse, - IBulkInstallPackageError, - handleInstallPackageFailure, - installPackage, - ensureInstalledPackage, -} from './install'; +export type { BulkInstallResponse, IBulkInstallPackageError } from './install'; +export { handleInstallPackageFailure, installPackage, ensureInstalledPackage } from './install'; export { removeInstallation } from './remove'; export function isUnremovablePackage(value: string): boolean { 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 966187e7127e27..f57965614adc6c 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -18,6 +18,7 @@ import type { InstallablePackage, InstallSource, } from '../../../../common'; +import { DEFAULT_PACKAGES } from '../../../../common'; import { IngestManagerError, PackageOperationNotSupportedError, @@ -469,6 +470,12 @@ export async function createInstallation(options: { const removable = !isUnremovablePackage(pkgName); const toSaveESIndexPatterns = generateESIndexPatterns(packageInfo.data_streams); + // For default packages, default the `keep_policies_up_to_date` setting to true. For all other + // package, default it to false. + const defaultKeepPoliciesUpToDate = DEFAULT_PACKAGES.some( + ({ name }) => name === packageInfo.name + ); + const created = await savedObjectsClient.create( PACKAGES_SAVED_OBJECT_TYPE, { @@ -484,7 +491,7 @@ export async function createInstallation(options: { install_status: 'installing', install_started_at: new Date().toISOString(), install_source: installSource, - keep_policies_up_to_date: false, + keep_policies_up_to_date: defaultKeepPoliciesUpToDate, }, { id: pkgName, overwrite: true } ); 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 aa2c3f1d4da3cf..85a52763b7fb7a 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -140,11 +140,18 @@ export async function fetchFile(filePath: string): Promise { } function setKibanaVersion(url: URL) { + // TODO: change default to false as soon as EPR issue fixed. Blocker for 8.0. + const disableVersionCheck = + appContextService.getConfig()?.developer?.disableRegistryVersionCheck ?? true; + if (disableVersionCheck) { + return; + } + const kibanaVersion = appContextService.getKibanaVersion().split('-')[0]; // may be x.y.z-SNAPSHOT const kibanaBranch = appContextService.getKibanaBranch(); - // on master, request all packages regardless of version - if (kibanaVersion && kibanaBranch !== 'master') { + // on main, request all packages regardless of version + if (kibanaVersion && kibanaBranch !== 'main') { url.searchParams.set('kibana.version', kibanaVersion); } } diff --git a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts index dfca8511fd84cc..bcaf5ac1f0634e 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts @@ -21,7 +21,7 @@ const SNAPSHOT_REGISTRY_URL_CDN = 'https://epr-snapshot.elastic.co'; const getDefaultRegistryUrl = (): string => { const branch = appContextService.getKibanaBranch(); - if (branch === 'master') { + if (branch === 'main') { return SNAPSHOT_REGISTRY_URL_CDN; } else if (appContextService.getKibanaVersion().includes('-SNAPSHOT')) { return STAGING_REGISTRY_URL_CDN; diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 46747762213f19..dcc00251e70f4d 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -134,6 +134,12 @@ jest.mock('./epm/packages/cleanup', () => { }; }); +jest.mock('./upgrade_usage', () => { + return { + sendTelemetryEvents: jest.fn(), + }; +}); + const mockedFetchInfo = fetchInfo as jest.Mock>; type CombinedExternalCallback = PutPackagePolicyUpdateCallback | PostPackagePolicyCreateCallback; @@ -1843,6 +1849,100 @@ describe('Package policy service', () => { expect(logfileStream?.enabled).toBe(false); }); }); + + describe('when a datastream is deleted from an input', () => { + it('it remove the non existing datastream', () => { + const basePackagePolicy: NewPackagePolicy = { + name: 'base-package-policy', + description: 'Base Package Policy', + namespace: 'default', + enabled: true, + policy_id: 'xxxx', + output_id: 'xxxx', + package: { + name: 'test-package', + title: 'Test Package', + version: '0.0.1', + }, + inputs: [ + { + type: 'logs', + policy_template: 'template_1', + enabled: true, + vars: { + path: { + type: 'text', + value: ['/var/log/logfile.log'], + }, + }, + streams: [ + { + enabled: true, + data_stream: { dataset: 'dataset.test123', type: 'log' }, + }, + ], + }, + ], + }; + + const packageInfo: PackageInfo = { + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [ + { + name: 'template_1', + title: 'Template 1', + description: 'Template 1', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [ + { + name: 'path', + type: 'text', + }, + ], + }, + ], + }, + ], + // @ts-ignore + assets: {}, + }; + + const inputsOverride: NewPackagePolicyInput[] = [ + { + type: 'logs', + enabled: true, + streams: [], + vars: { + path: { + type: 'text', + value: '/var/log/new-logfile.log', + }, + }, + }, + ]; + + const result = overridePackageInputs( + basePackagePolicy, + packageInfo, + // TODO: Update this type assertion when the `InputsOverride` type is updated such + // that it no longer causes unresolvable type errors when used directly + inputsOverride as InputsOverride[], + false + ); + expect(result.inputs[0]?.vars?.path.value).toEqual(['/var/log/logfile.log']); + }); + }); }); }); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index c03ccfc43ebd89..985351c3e981b5 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -9,7 +9,7 @@ import { omit, partition } from 'lodash'; import { i18n } from '@kbn/i18n'; import semverLte from 'semver/functions/lte'; import { getFlattenedObject } from '@kbn/std'; -import type { KibanaRequest, LogMeta } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient, RequestHandlerContext, @@ -42,7 +42,6 @@ import type { } from '../../common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; import { - HostedAgentPolicyRestrictionRelatedError, IngestManagerError, ingestErrorToResponseOptions, PackagePolicyIneligibleForUpgradeError, @@ -68,6 +67,8 @@ import { compileTemplate } from './epm/agent/agent'; import { normalizeKuery } from './saved_object'; import { appContextService } from '.'; import { removeOldAssets } from './epm/packages/cleanup'; +import type { PackagePolicyUpgradeUsage } from './upgrade_usage'; +import { sendTelemetryEvents } from './upgrade_usage'; export type InputsOverride = Partial & { vars?: Array; @@ -84,17 +85,6 @@ export const DATA_STREAM_ALLOWED_INDEX_PRIVILEGES = new Set([ 'read_cross_cluster', ]); -interface PackagePolicyUpgradeLogMeta extends LogMeta { - package_policy_upgrade: { - package_name: string; - current_version: string; - new_version: string; - status: 'success' | 'failure'; - error?: any[]; - dryRun?: boolean; - }; -} - class PackagePolicyService { public async create( soClient: SavedObjectsClientContract, @@ -108,24 +98,14 @@ class PackagePolicyService { skipEnsureInstalled?: boolean; } ): Promise { - // Check that its agent policy does not have a package policy with the same name - const parentAgentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id); - if (!parentAgentPolicy) { - throw new Error('Agent policy not found'); - } - if (parentAgentPolicy.is_managed && !options?.force) { - throw new HostedAgentPolicyRestrictionRelatedError( - `Cannot add integrations to hosted agent policy ${parentAgentPolicy.id}` - ); - } - if ( - (parentAgentPolicy.package_policies as PackagePolicy[]).find( - (siblingPackagePolicy) => siblingPackagePolicy.name === packagePolicy.name - ) - ) { - throw new IngestManagerError( - 'There is already a package with the same name on this agent policy' - ); + const existingPoliciesWithName = await this.list(soClient, { + perPage: 1, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: "${packagePolicy.name}"`, + }); + + // Check that the name does not exist already + if (existingPoliciesWithName.items.length > 0) { + throw new IngestManagerError('There is already a package with the same name'); } let elasticsearch: PackagePolicy['elasticsearch']; // Add ids to stream @@ -329,12 +309,12 @@ class PackagePolicyService { }); return { - items: packagePolicies.saved_objects.map((packagePolicySO) => ({ + items: packagePolicies?.saved_objects.map((packagePolicySO) => ({ id: packagePolicySO.id, version: packagePolicySO.version, ...packagePolicySO.attributes, })), - total: packagePolicies.total, + total: packagePolicies?.total, page, perPage, }; @@ -369,7 +349,8 @@ class PackagePolicyService { esClient: ElasticsearchClient, id: string, packagePolicy: UpdatePackagePolicy, - options?: { user?: AuthenticatedUser } + options?: { user?: AuthenticatedUser }, + currentVersion?: string ): Promise { const oldPackagePolicy = await this.get(soClient, id); const { version, ...restOfPackagePolicy } = packagePolicy; @@ -377,19 +358,15 @@ class PackagePolicyService { if (!oldPackagePolicy) { throw new Error('Package policy not found'); } + // Check that the name does not exist already but exclude the current package policy + const existingPoliciesWithName = await this.list(soClient, { + perPage: 1, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: "${packagePolicy.name}"`, + }); + const filtered = (existingPoliciesWithName?.items || []).filter((p) => p.id !== id); - // Check that its agent policy does not have a package policy with the same name - const parentAgentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id); - if (!parentAgentPolicy) { - throw new Error('Agent policy not found'); - } - if ( - (parentAgentPolicy.package_policies as PackagePolicy[]).find( - (siblingPackagePolicy) => - siblingPackagePolicy.id !== id && siblingPackagePolicy.name === packagePolicy.name - ) - ) { - throw new Error('There is already a package with the same name on this agent policy'); + if (filtered.length > 0) { + throw new IngestManagerError('There is already a package with the same name'); } let inputs = restOfPackagePolicy.inputs.map((input) => @@ -404,6 +381,7 @@ class PackagePolicyService { pkgName: packagePolicy.package.name, pkgVersion: packagePolicy.package.version, }); + const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version); inputs = await this._compilePackagePolicyInputs( registryPkgInfo, @@ -444,22 +422,22 @@ class PackagePolicyService { currentVersion: packagePolicy.package.version, }); - const upgradeMeta: PackagePolicyUpgradeLogMeta = { - package_policy_upgrade: { + if (packagePolicy.package.version !== currentVersion) { + const upgradeTelemetry: PackagePolicyUpgradeUsage = { package_name: packagePolicy.package.name, + current_version: currentVersion || 'unknown', new_version: packagePolicy.package.version, - current_version: 'unknown', status: 'success', dryRun: false, - }, - }; - - appContextService - .getLogger() - .info( - `Package policy successfully upgraded ${JSON.stringify(upgradeMeta)}`, - upgradeMeta + }; + sendTelemetryEvents( + appContextService.getLogger(), + appContextService.getTelemetryEventsSender(), + upgradeTelemetry ); + appContextService.getLogger().info(`Package policy upgraded successfully`); + appContextService.getLogger().debug(JSON.stringify(upgradeTelemetry)); + } } return newPolicy; @@ -628,7 +606,14 @@ class PackagePolicyService { ); updatePackagePolicy.elasticsearch = registryPkgInfo.elasticsearch; - await this.update(soClient, esClient, id, updatePackagePolicy, options); + await this.update( + soClient, + esClient, + id, + updatePackagePolicy, + options, + packagePolicy.package.version + ); result.push({ id, name: packagePolicy.name, @@ -690,24 +675,27 @@ class PackagePolicyService { const hasErrors = 'errors' in updatedPackagePolicy; if (packagePolicy.package.version !== packageInfo.version) { - const upgradeMeta: PackagePolicyUpgradeLogMeta = { - package_policy_upgrade: { - package_name: packageInfo.name, - current_version: packagePolicy.package.version, - new_version: packageInfo.version, - status: hasErrors ? 'failure' : 'success', - error: hasErrors ? updatedPackagePolicy.errors : undefined, - dryRun: true, - }, + const upgradeTelemetry: PackagePolicyUpgradeUsage = { + package_name: packageInfo.name, + current_version: packagePolicy.package.version, + new_version: packageInfo.version, + status: hasErrors ? 'failure' : 'success', + error: hasErrors ? updatedPackagePolicy.errors : undefined, + dryRun: true, }; + sendTelemetryEvents( + appContextService.getLogger(), + appContextService.getTelemetryEventsSender(), + upgradeTelemetry + ); appContextService .getLogger() - .info( + .info( `Package policy upgrade dry run ${ hasErrors ? 'resulted in errors' : 'ran successfully' - } ${JSON.stringify(upgradeMeta)}`, - upgradeMeta + }` ); + appContextService.getLogger().debug(JSON.stringify(upgradeTelemetry)); } return { @@ -1111,7 +1099,9 @@ export function overridePackageInputs( } if (override.vars) { - originalInput = deepMergeVars(originalInput, override) as NewPackagePolicyInput; + const indexOfInput = inputs.indexOf(originalInput); + inputs[indexOfInput] = deepMergeVars(originalInput, override) as NewPackagePolicyInput; + originalInput = inputs[indexOfInput]; } if (override.streams) { @@ -1130,10 +1120,24 @@ export function overridePackageInputs( } if (stream.vars) { - originalStream = deepMergeVars(originalStream, stream as InputsOverride); + const indexOfStream = originalInput.streams.indexOf(originalStream); + originalInput.streams[indexOfStream] = deepMergeVars( + originalStream, + stream as InputsOverride + ); + originalStream = originalInput.streams[indexOfStream]; } } } + + // Filter all stream that have been removed from the input + originalInput.streams = originalInput.streams.filter((originalStream) => { + return ( + override.streams?.some( + (s) => s.data_stream.dataset === originalStream.data_stream.dataset + ) ?? false + ); + }); } const resultingPackagePolicy: NewPackagePolicy = { diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 102b0595151514..2899c327e8d2bf 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -9,7 +9,11 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import type { PreconfiguredAgentPolicy, PreconfiguredOutput } from '../../common/types'; +import type { + InstallResult, + PreconfiguredAgentPolicy, + PreconfiguredOutput, +} from '../../common/types'; import type { AgentPolicy, NewPackagePolicy, Output } from '../types'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants'; @@ -30,6 +34,7 @@ jest.mock('./output'); const mockedOutputService = outputService as jest.Mocked; const mockInstalledPackages = new Map(); +const mockInstallPackageErrors = new Map(); const mockConfiguredPolicies = new Map(); const mockDefaultOutput: Output = { @@ -99,8 +104,22 @@ function getPutPreconfiguredPackagesMock() { } jest.mock('./epm/packages/install', () => ({ - installPackage({ pkgkey, force }: { pkgkey: string; force?: boolean }) { + async installPackage({ + pkgkey, + force, + }: { + pkgkey: string; + force?: boolean; + }): Promise { const [pkgName, pkgVersion] = pkgkey.split('-'); + const installError = mockInstallPackageErrors.get(pkgName); + if (installError) { + return { + error: new Error(installError), + installType: 'install', + }; + } + const installedPackage = mockInstalledPackages.get(pkgName); if (installedPackage) { if (installedPackage.version === pkgVersion) return installedPackage; @@ -109,7 +128,10 @@ jest.mock('./epm/packages/install', () => ({ const packageInstallation = { name: pkgName, version: pkgVersion, title: pkgName }; mockInstalledPackages.set(pkgName, packageInstallation); - return packageInstallation; + return { + status: 'installed', + installType: 'install', + }; }, ensurePackagesCompletedInstall() { return []; @@ -133,6 +155,8 @@ jest.mock('./epm/packages/get', () => ({ }, })); +jest.mock('./epm/kibana/index_pattern/install'); + jest.mock('./package_policy', () => ({ ...jest.requireActual('./package_policy'), packagePolicyService: { @@ -177,6 +201,7 @@ const spyAgentPolicyServicBumpAllAgentPoliciesForOutput = jest.spyOn( describe('policy preconfiguration', () => { beforeEach(() => { mockInstalledPackages.clear(); + mockInstallPackageErrors.clear(); mockConfiguredPolicies.clear(); spyAgentPolicyServiceUpdate.mockClear(); spyAgentPolicyServicBumpAllAgentPoliciesForOutput.mockClear(); @@ -266,7 +291,7 @@ describe('policy preconfiguration', () => { ); }); - it('should not create a policy if we are not able to add packages ', async () => { + it('should not create a policy and throw an error if install fails for required package', async () => { const soClient = getPutPreconfiguredPackagesMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const policies: PreconfiguredAgentPolicy[] = [ @@ -282,23 +307,48 @@ describe('policy preconfiguration', () => { ], }, ]; + mockInstallPackageErrors.set('test_package', 'REGISTRY ERROR'); - let error; - try { - await ensurePreconfiguredPackagesAndPolicies( + await expect( + ensurePreconfiguredPackagesAndPolicies( soClient, esClient, policies, - [{ name: 'CANNOT_MATCH', version: 'x.y.z' }], + [{ name: 'test_package', version: '3.0.0' }], mockDefaultOutput - ); - } catch (err) { - error = err; - } + ) + ).rejects.toThrow( + '[Test policy] could not be added. [test_package] could not be installed due to error: [Error: REGISTRY ERROR]' + ); + }); + + it('should not create a policy and throw an error if package is not installed for an unknown reason', async () => { + const soClient = getPutPreconfiguredPackagesMock(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const policies: PreconfiguredAgentPolicy[] = [ + { + name: 'Test policy', + namespace: 'default', + id: 'test-id', + package_policies: [ + { + package: { name: 'test_package' }, + name: 'Test package', + }, + ], + }, + ]; - expect(error).toBeDefined(); - expect(error.message).toEqual( - 'Test policy could not be added. test_package is not installed, add test_package to `xpack.fleet.packages` or remove it from Test package.' + await expect( + ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + policies, + [{ name: 'CANNOT_MATCH', version: 'x.y.z' }], + mockDefaultOutput + ) + ).rejects.toThrow( + '[Test policy] could not be added. [test_package] is not installed, add [test_package] to [xpack.fleet.packages] or remove it from [Test package].' ); }); it('should not attempt to recreate or modify an agent policy if its ID is unchanged', async () => { @@ -497,6 +547,17 @@ describe('comparePreconfiguredPolicyToCurrent', () => { ); expect(hasChanged).toBe(false); }); + + it('should not return hasChanged when only namespace field changes', () => { + const { hasChanged } = comparePreconfiguredPolicyToCurrent( + { + ...baseConfig, + namespace: 'newnamespace', + }, + basePackagePolicy + ); + expect(hasChanged).toBe(false); + }); }); describe('output preconfiguration', () => { diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 3b322e1112d6ae..e5fea73815ea7c 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -20,7 +20,11 @@ import type { PreconfigurationError, PreconfiguredOutput, } from '../../common'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, normalizeHostsForAgents } from '../../common'; +import { + AGENT_POLICY_SAVED_OBJECT_TYPE, + SO_SEARCH_LIMIT, + normalizeHostsForAgents, +} from '../../common'; import { PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, PRECONFIGURATION_LATEST_KEYWORD, @@ -33,7 +37,7 @@ 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 { overridePackageInputs } from './package_policy'; +import { overridePackageInputs, packagePolicyService } from './package_policy'; import { appContextService } from './app_context'; import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; import { upgradeManagedPackagePolicies } from './managed_package_policies'; @@ -147,6 +151,8 @@ export async function ensurePreconfiguredPackagesAndPolicies( packages: PreconfiguredPackage[] = [], defaultOutput: Output ): Promise { + const logger = appContextService.getLogger(); + // Validate configured packages to ensure there are no version conflicts const packageNames = groupBy(packages, (pkg) => pkg.name); const duplicatePackages = Object.entries(packageNames).filter( @@ -181,15 +187,20 @@ export async function ensurePreconfiguredPackagesAndPolicies( }); const fulfilledPackages = []; - const rejectedPackages = []; + const rejectedPackages: PreconfigurationError[] = []; for (let i = 0; i < preconfiguredPackages.length; i++) { const packageResult = preconfiguredPackages[i]; - if ('error' in packageResult) + if ('error' in packageResult) { + logger.warn( + `Failed installing package [${packages[i].name}] due to error: [${packageResult.error}]` + ); rejectedPackages.push({ package: { name: packages[i].name, version: packages[i].version }, error: packageResult.error, - } as PreconfigurationError); - else fulfilledPackages.push(packageResult); + }); + } else { + fulfilledPackages.push(packageResult); + } } // Keeping this outside of the Promise.all because it introduces a race condition. @@ -264,14 +275,14 @@ export async function ensurePreconfiguredPackagesAndPolicies( ); const fulfilledPolicies = []; - const rejectedPolicies = []; + const rejectedPolicies: PreconfigurationError[] = []; for (let i = 0; i < preconfiguredPolicies.length; i++) { const policyResult = preconfiguredPolicies[i]; if (policyResult.status === 'rejected') { rejectedPolicies.push({ error: policyResult.reason as Error, agentPolicy: { name: policies[i].name }, - } as PreconfigurationError); + }); continue; } fulfilledPolicies.push(policyResult.value); @@ -288,10 +299,25 @@ export async function ensurePreconfiguredPackagesAndPolicies( pkgName: pkg.name, }); if (!installedPackage) { + const rejectedPackage = rejectedPackages.find((rp) => rp.package?.name === pkg.name); + + if (rejectedPackage) { + throw new Error( + i18n.translate('xpack.fleet.preconfiguration.packageRejectedError', { + defaultMessage: `[{agentPolicyName}] could not be added. [{pkgName}] could not be installed due to error: [{errorMessage}]`, + values: { + agentPolicyName: preconfiguredAgentPolicy.name, + pkgName: pkg.name, + errorMessage: rejectedPackage.error.toString(), + }, + }) + ); + } + throw new Error( i18n.translate('xpack.fleet.preconfiguration.packageMissingError', { defaultMessage: - '{agentPolicyName} could not be added. {pkgName} is not installed, add {pkgName} to `{packagesConfigValue}` or remove it from {packagePolicyName}.', + '[{agentPolicyName}] could not be added. [{pkgName}] is not installed, add [{pkgName}] to [{packagesConfigValue}] or remove it from [{packagePolicyName}].', values: { agentPolicyName: preconfiguredAgentPolicy.name, packagePolicyName: name, @@ -327,14 +353,16 @@ export async function ensurePreconfiguredPackagesAndPolicies( } } - const fulfilledPolicyPackagePolicyIds = fulfilledPolicies - .filter(({ policy }) => policy?.package_policies) - .flatMap(({ policy }) => policy?.package_policies as string[]); - + // 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, - fulfilledPolicyPackagePolicyIds + allPackagePolicyIds.items ); return { @@ -361,7 +389,9 @@ export function comparePreconfiguredPolicyToCurrent( policyFromConfig: PreconfiguredAgentPolicy, currentPolicy: AgentPolicy ) { - const configTopLevelFields = omit(policyFromConfig, 'package_policies', 'id'); + // Namespace is omitted from being compared because even for managed policies, we still + // want users to be able to pick their own namespace: https://github.com/elastic/kibana/issues/110533 + const configTopLevelFields = omit(policyFromConfig, 'package_policies', 'id', 'namespace'); const currentTopLevelFields = pick(currentPolicy, ...Object.keys(configTopLevelFields)); return { diff --git a/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts b/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts new file mode 100644 index 00000000000000..5445ad233eddc1 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts @@ -0,0 +1,70 @@ +/* + * 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 { Logger } from 'src/core/server'; +import { loggingSystemMock } from 'src/core/server/mocks'; + +import type { TelemetryEventsSender } from '../telemetry/sender'; +import { createMockTelemetryEventsSender } from '../telemetry/__mocks__'; + +import { sendTelemetryEvents, capErrorSize } from './upgrade_usage'; +import type { PackagePolicyUpgradeUsage } from './upgrade_usage'; + +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 upgardeMessage: PackagePolicyUpgradeUsage = { + package_name: 'aws', + current_version: '0.6.1', + new_version: '1.3.0', + status: 'failure', + error: [ + { key: 'queueUrl', message: ['Queue URL is required'] }, + { message: 'Invalid format' }, + ], + dryRun: true, + }; + + sendTelemetryEvents(loggerMock, eventsTelemetryMock, upgardeMessage); + + expect(eventsTelemetryMock.queueTelemetryEvents).toHaveBeenCalledWith('fleet-upgrades', [ + { + current_version: '0.6.1', + error: [ + { + key: 'queueUrl', + message: ['Queue URL is required'], + }, + { + message: 'Invalid format', + }, + ], + error_message: ['Field is required', 'Invalid format'], + new_version: '1.3.0', + package_name: 'aws', + status: 'failure', + dryRun: true, + }, + ]); + }); + + it('should cap error size', () => { + const maxSize = 2; + const errors = [{ message: '1' }, { message: '2' }, { message: '3' }]; + + const result = capErrorSize(errors, maxSize); + + expect(result.length).toEqual(maxSize); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/upgrade_usage.ts b/x-pack/plugins/fleet/server/services/upgrade_usage.ts new file mode 100644 index 00000000000000..68bb126496e01d --- /dev/null +++ b/x-pack/plugins/fleet/server/services/upgrade_usage.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; + +import type { TelemetryEventsSender } from '../telemetry/sender'; + +export interface PackagePolicyUpgradeUsage { + package_name: string; + current_version: string; + new_version: string; + status: 'success' | 'failure'; + error?: UpgradeError[]; + dryRun?: boolean; + error_message?: string[]; +} + +export interface UpgradeError { + key?: string; + message: string | string[]; +} + +export const MAX_ERROR_SIZE = 100; +export const FLEET_UPGRADES_CHANNEL_NAME = 'fleet-upgrades'; + +export function sendTelemetryEvents( + logger: Logger, + eventsTelemetry: TelemetryEventsSender | undefined, + upgradeUsage: PackagePolicyUpgradeUsage +) { + if (eventsTelemetry === undefined) { + return; + } + + try { + const cappedErrors = capErrorSize(upgradeUsage.error || [], MAX_ERROR_SIZE); + eventsTelemetry.queueTelemetryEvents(FLEET_UPGRADES_CHANNEL_NAME, [ + { + ...upgradeUsage, + error: upgradeUsage.error ? cappedErrors : undefined, + error_message: makeErrorGeneric(cappedErrors), + }, + ]); + } catch (exc) { + logger.error(`queing telemetry events failed ${exc}`); + } +} + +export function capErrorSize(errors: UpgradeError[], maxSize: number): UpgradeError[] { + return errors.length > maxSize ? errors?.slice(0, maxSize) : errors; +} + +function makeErrorGeneric(errors: UpgradeError[]): string[] { + return errors.map((error) => { + if (Array.isArray(error.message)) { + const firstMessage = error.message[0]; + return firstMessage?.indexOf('is required') > -1 ? 'Field is required' : firstMessage; + } + return error.message as string; + }); +} diff --git a/x-pack/plugins/fleet/server/telemetry/__mocks__/index.ts b/x-pack/plugins/fleet/server/telemetry/__mocks__/index.ts new file mode 100644 index 00000000000000..2070aeca208617 --- /dev/null +++ b/x-pack/plugins/fleet/server/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/fleet/server/telemetry/queue.test.ts b/x-pack/plugins/fleet/server/telemetry/queue.test.ts new file mode 100644 index 00000000000000..510b8983870362 --- /dev/null +++ b/x-pack/plugins/fleet/server/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/fleet/server/telemetry/queue.ts b/x-pack/plugins/fleet/server/telemetry/queue.ts new file mode 100644 index 00000000000000..3496cfb94915d3 --- /dev/null +++ b/x-pack/plugins/fleet/server/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/fleet/server/telemetry/sender.test.ts b/x-pack/plugins/fleet/server/telemetry/sender.test.ts new file mode 100644 index 00000000000000..8fe4c6e150ff94 --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/sender.test.ts @@ -0,0 +1,143 @@ +/* + * 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 { TelemetryEventsSender } from './sender'; + +jest.mock('axios', () => { + return { + post: jest.fn(), + }; +}); + +describe('TelemetryEventsSender', () => { + let logger: ReturnType; + let sender: TelemetryEventsSender; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + sender = new TelemetryEventsSender(logger); + sender.start(undefined, { + elasticsearch: { client: { asInternalUser: { info: jest.fn(async () => ({})) } } }, + } as any); + }); + + describe('queueTelemetryEvents', () => { + it('queues two events', () => { + sender.queueTelemetryEvents('fleet-upgrades', [ + { package_name: 'system', current_version: '0.3', new_version: '1.0', status: 'success' }, + ]); + expect(sender['queuesPerChannel']['fleet-upgrades']).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('fleet-upgrades', [ + { package_name: 'apache', current_version: '0.3', new_version: '1.0', status: 'success' }, + ]); + sender['sendEvents'] = jest.fn(); + + await sender['sendIfDue'](); + + expect(sender['sendEvents']).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/fleet-upgrades', + undefined, + expect.anything() + ); + }); + + it("shouldn't send when telemetry is disabled", async () => { + const telemetryStart = { + getIsOptedIn: jest.fn(async () => false), + }; + sender['telemetryStart'] = telemetryStart; + + sender.queueTelemetryEvents('fleet-upgrades', [ + { package_name: 'system', current_version: '0.3', new_version: '1.0', status: 'success' }, + ]); + 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') + ), + }; + + sender['fetchClusterInfo'] = jest.fn(async () => { + return { + cluster_uuid: '1', + cluster_name: 'name', + version: { + number: '8.0.0', + }, + } as InfoResponse; + }); + + 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-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/fleet/server/telemetry/sender.ts b/x-pack/plugins/fleet/server/telemetry/sender.ts new file mode 100644 index 00000000000000..3bda17fbd1d79c --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/sender.ts @@ -0,0 +1,192 @@ +/* + * 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 { FleetTelemetryChannel, FleetTelemetryChannelEvents } 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; + + 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.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; + } + + const clusterInfo = await this.fetchClusterInfo(); + + for (const channel of Object.keys(this.queuesPerChannel)) { + await this.sendEvents( + await this.fetchTelemetryUrl(channel), + 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'); + } + + const { body } = await this.esClient.info(); + return body; + } + + 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 + ); + } catch (err) { + this.logger.warn(`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"); + } + telemetryUrl.pathname = `/v3/send/${channel}`; + return telemetryUrl.toString(); + } + + private async send( + events: unknown[], + telemetryUrl: string, + clusterUuid: string | undefined, + clusterVersionNumber: 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-Stack-Version': clusterVersionNumber ? clusterVersionNumber : '7.16.0', + }, + }); + this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); + } catch (err) { + this.logger.warn( + `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/fleet/server/telemetry/types.ts b/x-pack/plugins/fleet/server/telemetry/types.ts new file mode 100644 index 00000000000000..4351546ecdf02f --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/types.ts @@ -0,0 +1,15 @@ +/* + * 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 { PackagePolicyUpgradeUsage } from '../services/upgrade_usage'; + +export interface FleetTelemetryChannelEvents { + // channel name => event type + 'fleet-upgrades': PackagePolicyUpgradeUsage; +} + +export type FleetTelemetryChannel = keyof FleetTelemetryChannelEvents; diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index 5bdd95ef0b874a..174aac03d6a3cf 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -5,8 +5,7 @@ * 2.0. */ -export { - // Object types +export type { Agent, AgentMetadata, AgentSOAttributes, @@ -51,13 +50,10 @@ export { AssetReference, EsAssetReference, KibanaAssetReference, - ElasticsearchAssetType, RegistryPackage, InstallablePackage, AssetType, Installable, - KibanaAssetType, - KibanaSavedObjectType, AssetParts, AssetsGroupedByServiceByType, CategoryId, @@ -74,13 +70,17 @@ export { InstallResult, GetCategoriesRequest, DataType, - dataTypes, - // Fleet Server types FleetServerEnrollmentAPIKey, FleetServerAgent, FleetServerAgentAction, FleetServerPolicy, } from '../../common'; +export { + ElasticsearchAssetType, + KibanaAssetType, + KibanaSavedObjectType, + dataTypes, +} from '../../common'; export type AgentPolicyUpdateHandler = ( action: 'created' | 'updated' | 'deleted', @@ -96,4 +96,4 @@ export interface BulkActionResult { export * from './models'; export * from './rest_spec'; export * from './extensions'; -export { FleetRequestHandler, FleetRequestHandlerContext } from './request_context'; +export type { FleetRequestHandler, FleetRequestHandlerContext } from './request_context'; diff --git a/x-pack/plugins/fleet/storybook/manager.ts b/x-pack/plugins/fleet/storybook/manager.ts index 471a735ed370f9..193364f06a62ee 100644 --- a/x-pack/plugins/fleet/storybook/manager.ts +++ b/x-pack/plugins/fleet/storybook/manager.ts @@ -13,7 +13,7 @@ addons.setConfig({ theme: create({ base: 'light', brandTitle: 'Kibana Fleet Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/fleet', + brandUrl: 'https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet', }), showPanel: true.valueOf, selectedPanel: PANEL_ID, diff --git a/x-pack/plugins/global_search/public/index.ts b/x-pack/plugins/global_search/public/index.ts index adb392cc61a5a7..55a16407eb603b 100644 --- a/x-pack/plugins/global_search/public/index.ts +++ b/x-pack/plugins/global_search/public/index.ts @@ -20,7 +20,7 @@ export const plugin: PluginInitializer< GlobalSearchPluginStartDeps > = (context) => new GlobalSearchPlugin(context); -export { +export type { GlobalSearchBatchedResults, GlobalSearchProviderFindOptions, GlobalSearchProviderResult, @@ -29,9 +29,9 @@ export { GlobalSearchFindParams, GlobalSearchProviderFindParams, } from '../common/types'; -export { +export type { GlobalSearchPluginSetup, GlobalSearchPluginStart, GlobalSearchResultProvider, } from './types'; -export { GlobalSearchFindOptions } from './services/types'; +export type { GlobalSearchFindOptions } from './services/types'; diff --git a/x-pack/plugins/global_search/public/services/index.ts b/x-pack/plugins/global_search/public/services/index.ts index 0405a6d8899c8a..b178d3e76f849e 100644 --- a/x-pack/plugins/global_search/public/services/index.ts +++ b/x-pack/plugins/global_search/public/services/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { SearchService, SearchServiceSetup, SearchServiceStart } from './search_service'; -export { GlobalSearchFindOptions } from './types'; +export type { SearchServiceSetup, SearchServiceStart } from './search_service'; +export { SearchService } from './search_service'; +export type { GlobalSearchFindOptions } from './types'; diff --git a/x-pack/plugins/global_search/server/index.ts b/x-pack/plugins/global_search/server/index.ts index 1da9a10fad84ad..d98e10f4043065 100644 --- a/x-pack/plugins/global_search/server/index.ts +++ b/x-pack/plugins/global_search/server/index.ts @@ -22,14 +22,14 @@ export const plugin: PluginInitializer< export { config } from './config'; -export { +export type { GlobalSearchBatchedResults, GlobalSearchProviderFindOptions, GlobalSearchProviderResult, GlobalSearchProviderResultUrl, GlobalSearchResult, } from '../common/types'; -export { +export type { GlobalSearchFindOptions, GlobalSearchProviderContext, GlobalSearchPluginStart, diff --git a/x-pack/plugins/global_search/server/services/index.ts b/x-pack/plugins/global_search/server/services/index.ts index 32ae3805038db0..ac5b326dd5c058 100644 --- a/x-pack/plugins/global_search/server/services/index.ts +++ b/x-pack/plugins/global_search/server/services/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { SearchService, SearchServiceSetup, SearchServiceStart } from './search_service'; +export type { SearchServiceSetup, SearchServiceStart } from './search_service'; +export { SearchService } from './search_service'; diff --git a/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap b/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap deleted file mode 100644 index 8433d98c232d61..00000000000000 --- a/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap +++ /dev/null @@ -1,44 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SearchBar correctly filters and sorts results 1`] = ` -Array [ - "Canvas • Kibana", - "Discover • Kibana", - "Graph • Kibana", -] -`; - -exports[`SearchBar correctly filters and sorts results 2`] = ` -Array [ - "Discover • Kibana", - "My Dashboard • Test", -] -`; - -exports[`SearchBar only display results from the last search 1`] = ` -Array [ - "Visualize • Kibana", - "Map • Kibana", -] -`; - -exports[`SearchBar only display results from the last search 2`] = ` -Array [ - "Visualize • Kibana", - "Map • Kibana", -] -`; - -exports[`SearchBar supports keyboard shortcuts 1`] = ` - -`; diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx index c8bd54540e6a6c..dd7b1f26669434 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx @@ -6,15 +6,21 @@ */ import React from 'react'; -import { waitFor, act } from '@testing-library/react'; -import { ReactWrapper } from 'enzyme'; +import { act, render, screen, fireEvent } from '@testing-library/react'; import { of, BehaviorSubject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; -import { mountWithIntl } from '@kbn/test/jest'; import { applicationServiceMock } from '../../../../../src/core/public/mocks'; import { globalSearchPluginMock } from '../../../global_search/public/mocks'; import { GlobalSearchBatchedResults, GlobalSearchResult } from '../../../global_search/public'; import { SearchBar } from './search_bar'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; + +jest.mock( + 'react-virtualized-auto-sizer', + () => + ({ children }: any) => + children({ height: 600, width: 600 }) +); type Result = { id: string; type: string } | string; @@ -36,9 +42,7 @@ const createResult = (result: Result): GlobalSearchResult => { const createBatch = (...results: Result[]): GlobalSearchBatchedResults => ({ results: results.map(createResult), }); - -const getSelectableProps: any = (component: any) => component.find('EuiSelectable').props(); -const getSearchProps: any = (component: any) => component.find('EuiFieldSearch').props(); +jest.useFakeTimers(); describe('SearchBar', () => { let searchService: ReturnType; @@ -46,31 +50,37 @@ describe('SearchBar', () => { const basePathUrl = '/plugins/globalSearchBar/assets/'; const darkMode = false; - let component: ReactWrapper; - beforeEach(() => { applications = applicationServiceMock.createStartContract(); searchService = globalSearchPluginMock.createStartContract(); - jest.useFakeTimers(); }); - const triggerFocus = () => { - component.find('input[data-test-subj="nav-search-input"]').simulate('focus'); - }; - const update = () => { act(() => { jest.runAllTimers(); }); - component.update(); }; - const simulateTypeChar = async (text: string) => { - await waitFor(() => getSearchProps(component).onInput({ currentTarget: { value: text } })); + const focusAndUpdate = async () => { + await act(async () => { + (await screen.findByTestId('nav-search-input')).focus(); + jest.runAllTimers(); + }); }; - const getDisplayedOptionsTitle = () => { - return getSelectableProps(component).options.map((option: any) => option.title); + const simulateTypeChar = (text: string) => { + fireEvent.input(screen.getByTestId('nav-search-input'), { target: { value: text } }); + act(() => { + jest.runAllTimers(); + }); + }; + + const assertSearchResults = async (list: string[]) => { + for (let i = 0; i < list.length; i++) { + expect(await screen.findByTitle(list[i])).toBeInTheDocument(); + } + + expect(await screen.findAllByTestId('nav-search-option')).toHaveLength(list.length); }; it('correctly filters and sorts results', async () => { @@ -83,53 +93,52 @@ describe('SearchBar', () => { ) .mockReturnValueOnce(of(createBatch('Discover', { id: 'My Dashboard', type: 'test' }))); - component = mountWithIntl( - + render( + + + ); expect(searchService.find).toHaveBeenCalledTimes(0); - triggerFocus(); - update(); + await focusAndUpdate(); expect(searchService.find).toHaveBeenCalledTimes(1); expect(searchService.find).toHaveBeenCalledWith({}, {}); - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + await assertSearchResults(['Canvas • Kibana', 'Discover • Kibana', 'Graph • Kibana']); - await simulateTypeChar('d'); - update(); + simulateTypeChar('d'); - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + await assertSearchResults(['Discover • Kibana', 'My Dashboard • Test']); expect(searchService.find).toHaveBeenCalledTimes(2); - expect(searchService.find).toHaveBeenCalledWith({ term: 'd' }, {}); + expect(searchService.find).toHaveBeenLastCalledWith({ term: 'd' }, {}); }); - it('supports keyboard shortcuts', () => { - mountWithIntl( - , - { attachTo: document.body } + it('supports keyboard shortcuts', async () => { + render( + + + ); + act(() => { + fireEvent.keyDown(window, { key: '/', ctrlKey: true, metaKey: true }); + }); - const searchEvent = new KeyboardEvent('keydown', { - key: '/', - ctrlKey: true, - metaKey: true, - } as any); - window.dispatchEvent(searchEvent); + const inputElement = await screen.findByTestId('nav-search-input'); - expect(document.activeElement).toMatchSnapshot(); + expect(document.activeElement).toEqual(inputElement); }); it('only display results from the last search', async () => { @@ -144,30 +153,29 @@ describe('SearchBar', () => { searchService.find.mockReturnValueOnce(firstSearch).mockReturnValueOnce(secondSearch); - component = mountWithIntl( - + render( + + + ); - triggerFocus(); - update(); + await focusAndUpdate(); expect(searchService.find).toHaveBeenCalledTimes(1); - - await simulateTypeChar('d'); - update(); - - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + // + simulateTypeChar('d'); + await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']); firstSearchTrigger.next(true); update(); - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']); }); }); diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx index c459b2c0456813..97e19bab3d2d6e 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx @@ -180,6 +180,7 @@ export function SearchBar({ darkMode, }: Props) { const isMounted = useMountedState(); + const [initialLoad, setInitialLoad] = useState(false); const [searchValue, setSearchValue] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [searchRef, setSearchRef] = useState(null); @@ -190,12 +191,14 @@ export function SearchBar({ const UNKNOWN_TAG_ID = '__unknown__'; useEffect(() => { - const fetch = async () => { - const types = await globalSearch.getSearchableTypes(); - setSearchableTypes(types); - }; - fetch(); - }, [globalSearch]); + if (initialLoad) { + const fetch = async () => { + const types = await globalSearch.getSearchableTypes(); + setSearchableTypes(types); + }; + fetch(); + } + }, [globalSearch, initialLoad]); const loadSuggestions = useCallback( (term: string) => { @@ -234,75 +237,80 @@ export function SearchBar({ useDebounce( () => { - // cancel pending search if not completed yet - if (searchSubscription.current) { - searchSubscription.current.unsubscribe(); - searchSubscription.current = null; - } + if (initialLoad) { + // cancel pending search if not completed yet + if (searchSubscription.current) { + searchSubscription.current.unsubscribe(); + searchSubscription.current = null; + } + + const suggestions = loadSuggestions(searchValue); + + let aggregatedResults: GlobalSearchResult[] = []; + if (searchValue.length !== 0) { + trackUiMetric(METRIC_TYPE.COUNT, 'search_request'); + } + + const rawParams = parseSearchParams(searchValue); + const tagIds = + taggingApi && rawParams.filters.tags + ? rawParams.filters.tags.map( + (tagName) => taggingApi.ui.getTagIdFromName(tagName) ?? UNKNOWN_TAG_ID + ) + : undefined; + const searchParams: GlobalSearchFindParams = { + term: rawParams.term, + types: rawParams.filters.types, + tags: tagIds, + }; + // TODO technically a subtle bug here + // this term won't be set until the next time the debounce is fired + // so the SearchOption won't highlight anything if only one call is fired + // in practice, this is hard to spot, unlikely to happen, and is a negligible issue + setSearchTerm(rawParams.term ?? ''); + + searchSubscription.current = globalSearch.find(searchParams, {}).subscribe({ + next: ({ results }) => { + if (searchValue.length > 0) { + aggregatedResults = [...results, ...aggregatedResults].sort(sortByScore); + setOptions(aggregatedResults, suggestions, searchParams.tags); + return; + } + + // if searchbar is empty, filter to only applications and sort alphabetically + results = results.filter(({ type }: GlobalSearchResult) => type === 'application'); + + aggregatedResults = [...results, ...aggregatedResults].sort(sortByTitle); - const suggestions = loadSuggestions(searchValue); - - let aggregatedResults: GlobalSearchResult[] = []; - if (searchValue.length !== 0) { - trackUiMetric(METRIC_TYPE.COUNT, 'search_request'); - } - - const rawParams = parseSearchParams(searchValue); - const tagIds = - taggingApi && rawParams.filters.tags - ? rawParams.filters.tags.map( - (tagName) => taggingApi.ui.getTagIdFromName(tagName) ?? UNKNOWN_TAG_ID - ) - : undefined; - const searchParams: GlobalSearchFindParams = { - term: rawParams.term, - types: rawParams.filters.types, - tags: tagIds, - }; - // TODO technically a subtle bug here - // this term won't be set until the next time the debounce is fired - // so the SearchOption won't highlight anything if only one call is fired - // in practice, this is hard to spot, unlikely to happen, and is a negligible issue - setSearchTerm(rawParams.term ?? ''); - - searchSubscription.current = globalSearch.find(searchParams, {}).subscribe({ - next: ({ results }) => { - if (searchValue.length > 0) { - aggregatedResults = [...results, ...aggregatedResults].sort(sortByScore); setOptions(aggregatedResults, suggestions, searchParams.tags); - return; - } - - // if searchbar is empty, filter to only applications and sort alphabetically - results = results.filter(({ type }: GlobalSearchResult) => type === 'application'); - - aggregatedResults = [...results, ...aggregatedResults].sort(sortByTitle); - - setOptions(aggregatedResults, suggestions, searchParams.tags); - }, - error: () => { - // Not doing anything on error right now because it'll either just show the previous - // results or empty results which is basically what we want anyways - trackUiMetric(METRIC_TYPE.COUNT, 'unhandled_error'); - }, - complete: () => {}, - }); + }, + error: () => { + // Not doing anything on error right now because it'll either just show the previous + // results or empty results which is basically what we want anyways + trackUiMetric(METRIC_TYPE.COUNT, 'unhandled_error'); + }, + complete: () => {}, + }); + } }, 350, - [searchValue, loadSuggestions] + [searchValue, loadSuggestions, searchableTypes, initialLoad] ); - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === '/' && (isMac ? event.metaKey : event.ctrlKey)) { - event.preventDefault(); - trackUiMetric(METRIC_TYPE.COUNT, 'shortcut_used'); - if (searchRef) { - searchRef.focus(); - } else if (buttonRef) { - (buttonRef.children[0] as HTMLButtonElement).click(); + const onKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key === '/' && (isMac ? event.metaKey : event.ctrlKey)) { + event.preventDefault(); + trackUiMetric(METRIC_TYPE.COUNT, 'shortcut_used'); + if (searchRef) { + searchRef.focus(); + } else if (buttonRef) { + (buttonRef.children[0] as HTMLButtonElement).click(); + } } - } - }; + }, + [buttonRef, searchRef, trackUiMetric] + ); const onChange = (selection: EuiSelectableTemplateSitewideOption[]) => { const selected = selection.find(({ checked }) => checked === 'on'); @@ -411,6 +419,7 @@ export function SearchBar({ }), onFocus: () => { trackUiMetric(METRIC_TYPE.COUNT, 'search_focus'); + setInitialLoad(true); }, }} popoverProps={{ diff --git a/x-pack/plugins/global_search_bar/public/search_syntax/index.ts b/x-pack/plugins/global_search_bar/public/search_syntax/index.ts index 87d370d0c9055c..7bf8ee39df1480 100644 --- a/x-pack/plugins/global_search_bar/public/search_syntax/index.ts +++ b/x-pack/plugins/global_search_bar/public/search_syntax/index.ts @@ -6,4 +6,4 @@ */ export { parseSearchParams } from './parse_search_params'; -export { ParsedSearchParams, FilterValues, FilterValueType } from './types'; +export type { ParsedSearchParams, FilterValues, FilterValueType } from './types'; diff --git a/x-pack/plugins/global_search_bar/public/suggestions/index.ts b/x-pack/plugins/global_search_bar/public/suggestions/index.ts index 0c19601f21ebb5..32fcdea79550f1 100644 --- a/x-pack/plugins/global_search_bar/public/suggestions/index.ts +++ b/x-pack/plugins/global_search_bar/public/suggestions/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { getSuggestions, SearchSuggestion } from './get_suggestions'; +export type { SearchSuggestion } from './get_suggestions'; +export { getSuggestions } from './get_suggestions'; diff --git a/x-pack/plugins/graph/public/helpers/format_http_error.ts b/x-pack/plugins/graph/public/helpers/format_http_error.ts index 79c1bc8a456381..13ab8c02848bfa 100644 --- a/x-pack/plugins/graph/public/helpers/format_http_error.ts +++ b/x-pack/plugins/graph/public/helpers/format_http_error.ts @@ -6,9 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import { IHttpFetchError } from 'kibana/public'; +import { IHttpFetchError, ResponseErrorBody } from 'kibana/public'; -export function formatHttpError(error: IHttpFetchError) { +export function formatHttpError( + error: IHttpFetchError +) { if (!error.response) { return i18n.translate('xpack.graph.fatalError.unavailableServerErrorMessage', { defaultMessage: @@ -20,9 +22,9 @@ export function formatHttpError(error: IHttpFetchError) { return i18n.translate('xpack.graph.fatalError.errorStatusMessage', { defaultMessage: 'Error {errStatus} {errStatusText}: {errMessage}', values: { - errStatus: error.body.status, - errStatusText: error.body.statusText, - errMessage: error.body.message, + errStatus: error.body?.status, + errStatusText: error.body?.statusText, + errMessage: error.body?.message, }, }); } diff --git a/x-pack/plugins/graph/public/helpers/use_graph_loader.ts b/x-pack/plugins/graph/public/helpers/use_graph_loader.ts index c133f6bf260cde..c895d4156fa7ba 100644 --- a/x-pack/plugins/graph/public/helpers/use_graph_loader.ts +++ b/x-pack/plugins/graph/public/helpers/use_graph_loader.ts @@ -7,7 +7,7 @@ import { useCallback, useState } from 'react'; import { ToastsStart } from 'kibana/public'; -import { IHttpFetchError, CoreStart } from 'kibana/public'; +import { IHttpFetchError, ResponseErrorBody, CoreStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { ExploreRequest, GraphExploreCallback, GraphSearchCallback, SearchRequest } from '../types'; import { formatHttpError } from './format_http_error'; @@ -21,7 +21,7 @@ export const useGraphLoader = ({ toastNotifications, coreStart }: UseGraphLoader const [loading, setLoading] = useState(false); const handleHttpError = useCallback( - (error: IHttpFetchError) => { + (error: IHttpFetchError) => { toastNotifications.addDanger(formatHttpError(error)); }, [toastNotifications] @@ -59,10 +59,10 @@ export const useGraphLoader = ({ toastNotifications, coreStart }: UseGraphLoader }; setLoading(true); return coreStart.http - .post('../api/graph/graphExplore', request) + .post<{ resp: { timed_out: unknown } }>('../api/graph/graphExplore', request) .then(function (data) { const response = data.resp; - if (response.timed_out) { + if (response?.timed_out) { toastNotifications.addWarning( i18n.translate('xpack.graph.exploreGraph.timedOutWarningText', { defaultMessage: 'Exploration timed out', @@ -88,7 +88,7 @@ export const useGraphLoader = ({ toastNotifications, coreStart }: UseGraphLoader }; setLoading(true); coreStart.http - .post('../api/graph/searchProxy', request) + .post<{ resp: unknown }>('../api/graph/searchProxy', request) .then(function (data) { const response = data.resp; responseHandler(response); diff --git a/x-pack/plugins/graph/public/services/fetch_top_nodes.ts b/x-pack/plugins/graph/public/services/fetch_top_nodes.ts index 34d34317d71165..1b1e91ac7277b6 100644 --- a/x-pack/plugins/graph/public/services/fetch_top_nodes.ts +++ b/x-pack/plugins/graph/public/services/fetch_top_nodes.ts @@ -96,8 +96,8 @@ export async function fetchTopNodes( .reduce((allAggs, subAgg) => ({ ...allAggs, ...subAgg })); const body = createSamplerSearchBody(aggs); - const response: TopTermsAggResponse = ( - await post('../api/graph/searchProxy', { + const response = ( + await post<{ resp: TopTermsAggResponse }>('../api/graph/searchProxy', { body: JSON.stringify({ index, body }), }) ).resp; diff --git a/x-pack/plugins/graph/server/sample_data/ecommerce.ts b/x-pack/plugins/graph/server/sample_data/ecommerce.ts index 36e125864600d2..fe2f319acef884 100644 --- a/x-pack/plugins/graph/server/sample_data/ecommerce.ts +++ b/x-pack/plugins/graph/server/sample_data/ecommerce.ts @@ -348,7 +348,6 @@ const wsState: any = { maxValuesPerDoc: 1, minDocCount: 3, }, - indexPatternRefName: 'indexPattern_0', }; export function registerEcommerceSampleData(sampleDataRegistry: SampleDataRegistrySetup) { @@ -365,16 +364,11 @@ export function registerEcommerceSampleData(sampleDataRegistry: SampleDataRegist numVertices: 12, version: 1, wsState: JSON.stringify(JSON.stringify(wsState)), + legacyIndexPatternRef: 'kibana_sample_data_ecommerce', }, - references: [ - { - name: 'indexPattern_0', - type: 'index-pattern', - id: 'kibana_sample_data_ecommerce', - }, - ], + references: [], migrationVersion: { - 'graph-workspace': '7.0.0', + 'graph-workspace': '7.11.0', }, updated_at: '2020-01-09T16:40:36.122Z', }, @@ -383,7 +377,11 @@ export function registerEcommerceSampleData(sampleDataRegistry: SampleDataRegist export function registerEcommerceSampleDataLink(sampleDataRegistry: SampleDataRegistrySetup) { sampleDataRegistry.addAppLinksToSampleDataset(datasetId, [ { - path: createWorkspacePath('46fa9d30-319c-11ea-bbe4-818d9c786051'), + sampleObject: { + type: 'graph-workspace', + id: '46fa9d30-319c-11ea-bbe4-818d9c786051', + }, + getPath: createWorkspacePath, label: i18n.translate('xpack.graph.sampleData.label', { defaultMessage: 'Graph' }), icon: APP_ICON, }, diff --git a/x-pack/plugins/graph/server/sample_data/flights.ts b/x-pack/plugins/graph/server/sample_data/flights.ts index 61beacc3552f75..f378c916e4b87e 100644 --- a/x-pack/plugins/graph/server/sample_data/flights.ts +++ b/x-pack/plugins/graph/server/sample_data/flights.ts @@ -1602,7 +1602,6 @@ const wsState: any = { maxValuesPerDoc: 1, minDocCount: 3, }, - indexPatternRefName: 'indexPattern_0', }; export function registerFlightsSampleData(sampleDataRegistry: SampleDataRegistrySetup) { @@ -1619,16 +1618,11 @@ export function registerFlightsSampleData(sampleDataRegistry: SampleDataRegistry numVertices: 91, version: 1, wsState: JSON.stringify(JSON.stringify(wsState)), + legacyIndexPatternRef: 'kibana_sample_data_flights', }, - references: [ - { - name: 'indexPattern_0', - type: 'index-pattern', - id: 'kibana_sample_data_flights', - }, - ], + references: [], migrationVersion: { - 'graph-workspace': '7.0.0', + 'graph-workspace': '7.11.0', }, updated_at: '2020-01-09T15:55:24.013Z', }, @@ -1637,7 +1631,11 @@ export function registerFlightsSampleData(sampleDataRegistry: SampleDataRegistry export function registerFlightsSampleDataLink(sampleDataRegistry: SampleDataRegistrySetup) { sampleDataRegistry.addAppLinksToSampleDataset(datasetId, [ { - path: createWorkspacePath('5dc018d0-32f8-11ea-bbe4-818d9c786051'), + sampleObject: { + type: 'graph-workspace', + id: '5dc018d0-32f8-11ea-bbe4-818d9c786051', + }, + getPath: createWorkspacePath, label: i18n.translate('xpack.graph.sampleData.label', { defaultMessage: 'Graph' }), icon: APP_ICON, }, diff --git a/x-pack/plugins/graph/server/sample_data/logs.ts b/x-pack/plugins/graph/server/sample_data/logs.ts index fbbbd8b493ceef..af1132f5d37bc7 100644 --- a/x-pack/plugins/graph/server/sample_data/logs.ts +++ b/x-pack/plugins/graph/server/sample_data/logs.ts @@ -419,7 +419,6 @@ const wsState: any = { maxValuesPerDoc: 1, minDocCount: 3, }, - indexPatternRefName: 'indexPattern_0', }; export function registerLogsSampleData(sampleDataRegistry: SampleDataRegistrySetup) { @@ -436,16 +435,11 @@ export function registerLogsSampleData(sampleDataRegistry: SampleDataRegistrySet numVertices: 27, version: 1, wsState: JSON.stringify(JSON.stringify(wsState)), + legacyIndexPatternRef: 'kibana_sample_data_logs', }, - references: [ - { - name: 'indexPattern_0', - type: 'index-pattern', - id: 'kibana_sample_data_logs', - }, - ], + references: [], migrationVersion: { - 'graph-workspace': '7.0.0', + 'graph-workspace': '7.11.0', }, updated_at: '2020-01-09T16:40:36.122Z', }, @@ -454,7 +448,11 @@ export function registerLogsSampleData(sampleDataRegistry: SampleDataRegistrySet export function registerLogsSampleDataLink(sampleDataRegistry: SampleDataRegistrySetup) { sampleDataRegistry.addAppLinksToSampleDataset(datasetId, [ { - path: createWorkspacePath('e2141080-32fa-11ea-bbe4-818d9c786051'), + sampleObject: { + type: 'graph-workspace', + id: 'e2141080-32fa-11ea-bbe4-818d9c786051', + }, + getPath: createWorkspacePath, label: i18n.translate('xpack.graph.sampleData.label', { defaultMessage: 'Graph' }), icon: APP_ICON, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts index f31fedfac66813..0439e9d5ec13db 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts @@ -19,10 +19,7 @@ export { ConfigurationProvider, useConfiguration } from './configuration_context export { FormErrorsProvider, useFormErrorsContext } from './form_errors_context'; -export { - PhaseTimingsProvider, - usePhaseTimings, - PhaseTimingConfiguration, -} from './phase_timings_context'; +export type { PhaseTimingConfiguration } from './phase_timings_context'; +export { PhaseTimingsProvider, usePhaseTimings } from './phase_timings_context'; export { useGlobalFields, globalFields } from './global_fields_context'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts index 607c62cd3ce8be..5bc887c5a3c73f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts @@ -5,13 +5,15 @@ * 2.0. */ +export type { + AbsoluteTimings, + PhaseAgeInMilliseconds, + RelativePhaseTimingInMs, +} from './absolute_timing_to_relative_timing'; export { calculateRelativeFromAbsoluteMilliseconds, formDataToAbsoluteTimings, getPhaseMinAgeInMilliseconds, - AbsoluteTimings, - PhaseAgeInMilliseconds, - RelativePhaseTimingInMs, } from './absolute_timing_to_relative_timing'; export { getDefaultRepository } from './get_default_repository'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx index d6d030c3ec7339..61ce87860d8972 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx @@ -191,7 +191,9 @@ export const PolicyTable: React.FunctionComponent = ({ policies }) => { direction: 'asc', }, }} - search={{ box: { incremental: true } }} + search={{ + box: { incremental: true, 'data-test-subj': 'ilmSearchBar' }, + }} tableLayout="auto" items={policies} columns={columns} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.ts index fc37b62e30eb25..5fcc0054cdeb00 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.ts @@ -5,10 +5,15 @@ * 2.0. */ -import { IHttpFetchError } from 'src/core/public'; +import { IHttpFetchError, ResponseErrorBody } from 'src/core/public'; import { fatalErrors, toasts } from './notification'; -function createToastConfig(error: IHttpFetchError, errorTitle: string) { +interface CommonErrorBody extends ResponseErrorBody { + error: string; + attributes: { causes: unknown[] }; +} + +function createToastConfig(error: IHttpFetchError, errorTitle: string) { if (error && error.body) { // Error body shape is defined by the API. const { error: errorString, statusCode, message: errorMessage, attributes } = error.body; @@ -23,7 +28,7 @@ function createToastConfig(error: IHttpFetchError, errorTitle: string) { } } -export function showApiWarning(error: IHttpFetchError, errorTitle: string) { +export function showApiWarning(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { @@ -35,7 +40,7 @@ export function showApiWarning(error: IHttpFetchError, errorTitle: string) { return fatalErrors.add(error, errorTitle); } -export function showApiError(error: IHttpFetchError, errorTitle: string) { +export function showApiError(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { diff --git a/x-pack/plugins/index_lifecycle_management/public/index.ts b/x-pack/plugins/index_lifecycle_management/public/index.ts index cbd23a14a6114e..2b8573902781be 100644 --- a/x-pack/plugins/index_lifecycle_management/public/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/index.ts @@ -14,4 +14,5 @@ export const plugin = (initializerContext: PluginInitializerContext) => { return new IndexLifecycleManagementPlugin(initializerContext); }; -export { ILM_LOCATOR_ID, IlmLocatorParams } from './locator'; +export type { IlmLocatorParams } from './locator'; +export { ILM_LOCATOR_ID } from './locator'; diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index e191c4bd799a1c..dab299c476eea9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -8,10 +8,7 @@ import { AppServicesContext } from './types'; import { useKibana as _useKibana } from '../../../../src/plugins/kibana_react/public'; -export { - useForm, - useFormData, - Form, +export type { FormHook, FieldHook, FormData, @@ -19,11 +16,16 @@ export { FieldConfig, OnFormUpdateArg, ValidationFunc, - getFieldValidityAndErrorMessage, - useFormContext, FormSchema, ValidationConfig, ValidationError, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { + useForm, + useFormData, + Form, + getFieldValidityAndErrorMessage, + useFormContext, UseMultiFields, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; diff --git a/x-pack/plugins/index_lifecycle_management/server/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/server/shared_imports.ts index 5c91a0a57ce5e0..4598814d63c029 100644 --- a/x-pack/plugins/index_lifecycle_management/server/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/server/shared_imports.ts @@ -6,4 +6,4 @@ */ export { handleEsError } from '../../../../src/plugins/es_ui_shared/server'; -export { ILicense, LicenseType } from '../../licensing/common/types'; +export type { ILicense, LicenseType } from '../../licensing/common/types'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts index 997f234cf30906..79df6e8e9f20c1 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts @@ -7,7 +7,8 @@ import './mocks'; -export { nextTick, getRandomString, findTestSubject, TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; export { setupEnvironment, @@ -16,4 +17,4 @@ export { kibanaVersion, } from './setup_environment'; -export { TestSubjects } from './test_subjects'; +export type { TestSubjects } from './test_subjects'; diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index 9908e9598412bc..0cc514b47024f0 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -13,6 +13,6 @@ export * from './mappings'; export * from './templates'; -export { DataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; +export type { DataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; export * from './component_templates'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/index.ts index 2545c47e47c22a..fd65eb04016081 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/index.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/index.ts @@ -5,8 +5,8 @@ * 2.0. */ +export type { Props as ComponentTemplateDetailsProps } from './component_template_details'; export { ComponentTemplateDetailsFlyoutContent, defaultFlyoutProps, - Props as ComponentTemplateDetailsProps, } from './component_template_details'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts index 15528f5b4e8e5b..2f5b98e59bb22a 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts @@ -5,11 +5,14 @@ * 2.0. */ -export { +export type { UseRequestConfig, UseRequestResponse, SendRequestConfig, SendRequestResponse, + Error, +} from '../../../../../../../src/plugins/es_ui_shared/public'; +export { sendRequest, useRequest, WithPrivileges, @@ -18,7 +21,6 @@ export { SectionLoading, PageLoading, PageError, - Error, useAuthorizationContext, NotAuthorizedSection, Forms, @@ -32,11 +34,13 @@ export { fieldFormatters, } from '../../../../../../../src/plugins/es_ui_shared/static/forms/helpers'; -export { +export type { FormSchema, + FieldConfig, +} from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { FIELD_TYPES, VALIDATION_TYPES, - FieldConfig, useForm, Form, getUseField, @@ -50,17 +54,17 @@ export { export { isJSON } from '../../../../../../../src/plugins/es_ui_shared/static/validators/string'; +export type { CommonWizardSteps } from '../shared'; export { TabMappings, TabSettings, TabAliases, - CommonWizardSteps, StepSettingsContainer, StepMappingsContainer, StepAliasesContainer, } from '../shared'; -export { +export type { ComponentTemplateSerialized, ComponentTemplateDeserialized, ComponentTemplateListItem, diff --git a/x-pack/plugins/index_management/public/application/components/index.ts b/x-pack/plugins/index_management/public/application/components/index.ts index eeba6e16b543c5..fe3e41c34a8707 100644 --- a/x-pack/plugins/index_management/public/application/components/index.ts +++ b/x-pack/plugins/index_management/public/application/components/index.ts @@ -5,7 +5,8 @@ * 2.0. */ -export { SectionError, Error } from './section_error'; +export type { Error } from './section_error'; +export { SectionError } from './section_error'; export { NoMatch } from './no_match'; export { TemplateDeleteModal } from './template_delete_modal'; export { TemplateForm } from './template_form'; diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/index.ts b/x-pack/plugins/index_management/public/application/components/index_templates/index.ts index d460175543ac53..f51fe3f5d6fe30 100644 --- a/x-pack/plugins/index_management/public/application/components/index_templates/index.ts +++ b/x-pack/plugins/index_management/public/application/components/index_templates/index.ts @@ -5,12 +5,11 @@ * 2.0. */ +export type { SimulateTemplateProps, SimulateTemplateFilters } from './simulate_template'; export { SimulateTemplateFlyoutContent, simulateTemplateFlyoutProps, - SimulateTemplateProps, SimulateTemplate, - SimulateTemplateFilters, } from './simulate_template'; export { LegacyIndexTemplatesDeprecation } from './legacy_index_template_deprecation'; diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts index 91273dc82f902f..2837eb8f1f40cf 100644 --- a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts +++ b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts @@ -5,10 +5,11 @@ * 2.0. */ +export type { Props as SimulateTemplateProps } from './simulate_template_flyout'; export { SimulateTemplateFlyoutContent, defaultFlyoutProps as simulateTemplateFlyoutProps, - Props as SimulateTemplateProps, } from './simulate_template_flyout'; -export { SimulateTemplate, Filters as SimulateTemplateFilters } from './simulate_template'; +export type { Filters as SimulateTemplateFilters } from './simulate_template'; +export { SimulateTemplate } from './simulate_template'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts index fbe24557ae6a16..d436492756659d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts @@ -12,11 +12,12 @@ import { getMappingsEditorDataFactory, } from './mappings_editor.helpers'; -export { nextTick, getRandomString, findTestSubject, TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; export { kibanaVersion } from './setup_environment'; export const componentHelpers = { mappingsEditor: { setup: mappingsEditorSetup, getMappingsEditorDataFactory }, }; -export { MappingsEditorTestBed, DomFields }; +export type { MappingsEditorTestBed, DomFields }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/index.ts index a171959daf2cbe..4b3e2657ba6ac7 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/index.ts @@ -5,11 +5,8 @@ * 2.0. */ -export { - EditFieldContainer, - defaultFlyoutProps, - Props as EditFieldContainerProps, -} from './edit_field_container'; +export type { Props as EditFieldContainerProps } from './edit_field_container'; +export { EditFieldContainer, defaultFlyoutProps } from './edit_field_container'; export * from './basic_parameters_section'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/index.ts index 9692e5d6c22b02..2d284297041e79 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/index.ts @@ -13,4 +13,4 @@ export { LoadMappingsFromJsonButton, LoadMappingsProvider } from './components/l export { MappingsEditorProvider } from './mappings_editor_context'; -export { IndexSettings, OnUpdateHandler } from './types'; +export type { IndexSettings, OnUpdateHandler } from './types'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts index 4ea8412d4ffe3d..de9c1985239f5a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts @@ -5,26 +5,28 @@ * 2.0. */ -export { - FIELD_TYPES, +export type { FieldConfig, FieldHook, - Form, - FormDataProvider, FormHook, FormSchema, - getUseField, OnFormUpdateArg, SerializerFunc, + ArrayItem, + ValidationFunc, + ValidationFuncArg, +} from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { + FIELD_TYPES, + Form, + FormDataProvider, + getUseField, UseField, UseArray, - ArrayItem, useForm, useFormContext, UseMultiFields, VALIDATION_TYPES, - ValidationFunc, - ValidationFuncArg, } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { @@ -47,20 +49,17 @@ export { fieldValidators, } from '../../../../../../../src/plugins/es_ui_shared/static/forms/helpers'; -export { - JsonEditor, - OnJsonEditorUpdateHandler, - GlobalFlyout, -} from '../../../../../../../src/plugins/es_ui_shared/public'; +export type { OnJsonEditorUpdateHandler } from '../../../../../../../src/plugins/es_ui_shared/public'; +export { JsonEditor, GlobalFlyout } from '../../../../../../../src/plugins/es_ui_shared/public'; export { documentationService } from '../../services/documentation'; -export { +export type { RuntimeField, - RuntimeFieldEditorFlyoutContent, RuntimeFieldEditorFlyoutContentProps, } from '../../../../../runtime_fields/public'; +export { RuntimeFieldEditorFlyoutContent } from '../../../../../runtime_fields/public'; export { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -export { DocLinksStart } from '../../../../../../../src/core/public'; +export type { DocLinksStart } from '../../../../../../../src/core/public'; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts index 6011bd6aed4f99..3c564c0caf6e37 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts +++ b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts @@ -7,11 +7,7 @@ export { TabAliases, TabMappings, TabSettings } from './details_panel'; -export { - StepAliasesContainer, - StepMappingsContainer, - StepSettingsContainer, - CommonWizardSteps, -} from './wizard_steps'; +export type { CommonWizardSteps } from './wizard_steps'; +export { StepAliasesContainer, StepMappingsContainer, StepSettingsContainer } from './wizard_steps'; export { TemplateContentIndicator } from './template_content_indicator'; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts index 224c753b823048..2197e5140fc982 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts @@ -9,4 +9,4 @@ export { StepAliasesContainer } from './step_aliases_container'; export { StepMappingsContainer } from './step_mappings_container'; export { StepSettingsContainer } from './step_settings_container'; -export { CommonWizardSteps } from './types'; +export type { CommonWizardSteps } from './types'; diff --git a/x-pack/plugins/index_management/public/application/components/shared/index.ts b/x-pack/plugins/index_management/public/application/components/shared/index.ts index 96b4131f8282db..06899e202ef82a 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/index.ts +++ b/x-pack/plugins/index_management/public/application/components/shared/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +export type { CommonWizardSteps } from './components'; export { TabAliases, TabMappings, @@ -12,6 +13,5 @@ export { StepAliasesContainer, StepMappingsContainer, StepSettingsContainer, - CommonWizardSteps, TemplateContentIndicator, } from './components'; diff --git a/x-pack/plugins/index_management/public/application/index.tsx b/x-pack/plugins/index_management/public/application/index.tsx index b7a4bd21351473..854826681adaef 100644 --- a/x-pack/plugins/index_management/public/application/index.tsx +++ b/x-pack/plugins/index_management/public/application/index.tsx @@ -93,4 +93,5 @@ const useKibana = () => { return useKibanaReactPlugin(); }; -export { AppDependencies, useKibana }; +export type { AppDependencies }; +export { useKibana }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/components/index.ts b/x-pack/plugins/index_management/public/application/sections/home/components/index.ts index df1218f6f0686a..8cd35ead5d308e 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/components/index.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/components/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { FilterListButton, Filters } from './filter_list_button'; +export type { Filters } from './filter_list_button'; +export { FilterListButton } from './filter_list_button'; diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 5929df2f2821d1..5cfb881cb22cfa 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -76,7 +76,7 @@ export async function deleteDataStreams(dataStreams: string[]) { } export async function loadIndices() { - const response = await httpService.httpClient.get(`${API_BASE_PATH}/indices`); + const response = await httpService.httpClient.get(`${API_BASE_PATH}/indices`); return response.data ? response.data : response; } @@ -87,7 +87,7 @@ export async function reloadIndices( const body = JSON.stringify({ indexNames, }); - const response = await httpService.httpClient.post(`${API_BASE_PATH}/indices/reload`, { + const response = await httpService.httpClient.post(`${API_BASE_PATH}/indices/reload`, { body, asSystemRequest, }); diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts index f9ccc788a36c09..01635ed2a9b8f5 100644 --- a/x-pack/plugins/index_management/public/index.ts +++ b/x-pack/plugins/index_management/public/index.ts @@ -14,7 +14,7 @@ export const plugin = (ctx: PluginInitializerContext) => { return new IndexMgmtUIPlugin(ctx); }; -export { IndexManagementPluginSetup } from './types'; +export type { IndexManagementPluginSetup } from './types'; export { getIndexListUri, getTemplateDetailsLink } from './application/services/routing'; diff --git a/x-pack/plugins/index_management/public/services/index.ts b/x-pack/plugins/index_management/public/services/index.ts index fe91e700236ccd..f32787a427b891 100644 --- a/x-pack/plugins/index_management/public/services/index.ts +++ b/x-pack/plugins/index_management/public/services/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ExtensionsService, ExtensionsSetup } from './extensions_service'; +export type { ExtensionsSetup } from './extensions_service'; +export { ExtensionsService } from './extensions_service'; diff --git a/x-pack/plugins/index_management/public/shared_imports.ts b/x-pack/plugins/index_management/public/shared_imports.ts index 4e1c4207959042..7fadcd9e71502c 100644 --- a/x-pack/plugins/index_management/public/shared_imports.ts +++ b/x-pack/plugins/index_management/public/shared_imports.ts @@ -7,11 +7,14 @@ export { APP_WRAPPER_CLASS } from '../../../../src/core/public'; -export { +export type { SendRequestConfig, SendRequestResponse, UseRequestConfig, UseRequestResponse, + Error, +} from '../../../../src/plugins/es_ui_shared/public'; +export { sendRequest, useRequest, Forms, @@ -20,16 +23,17 @@ export { attemptToURIDecode, PageLoading, PageError, - Error, SectionLoading, EuiCodeEditor, } from '../../../../src/plugins/es_ui_shared/public'; -export { +export type { FormSchema, + FieldConfig, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { FIELD_TYPES, VALIDATION_TYPES, - FieldConfig, useForm, useFormData, Form, diff --git a/x-pack/plugins/index_management/server/index.ts b/x-pack/plugins/index_management/server/index.ts index 29291116e44fc2..8eb882d29de2df 100644 --- a/x-pack/plugins/index_management/server/index.ts +++ b/x-pack/plugins/index_management/server/index.ts @@ -14,7 +14,7 @@ export { config } from './config'; export const plugin = (context: PluginInitializerContext) => new IndexMgmtServerPlugin(context); /** @public */ -export { Dependencies } from './types'; -export { IndexManagementPluginSetup } from './plugin'; -export { Index, LegacyTemplateSerialized } from '../common'; -export { IndexManagementConfig } from './config'; +export type { Dependencies } from './types'; +export type { IndexManagementPluginSetup } from './plugin'; +export type { Index, LegacyTemplateSerialized } from '../common'; +export type { IndexManagementConfig } from './config'; diff --git a/x-pack/plugins/index_management/server/services/index.ts b/x-pack/plugins/index_management/server/services/index.ts index 576d7c46fa086a..bd62f2df80cc82 100644 --- a/x-pack/plugins/index_management/server/services/index.ts +++ b/x-pack/plugins/index_management/server/services/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { IndexDataEnricher, Enricher } from './index_data_enricher'; +export type { Enricher } from './index_data_enricher'; +export { IndexDataEnricher } from './index_data_enricher'; diff --git a/x-pack/plugins/infra/common/constants.ts b/x-pack/plugins/infra/common/constants.ts index 1c3aa550f2f629..4c70e34c9899f1 100644 --- a/x-pack/plugins/infra/common/constants.ts +++ b/x-pack/plugins/infra/common/constants.ts @@ -8,7 +8,6 @@ export const DEFAULT_SOURCE_ID = 'default'; export const METRICS_INDEX_PATTERN = 'metrics-*,metricbeat-*'; export const LOGS_INDEX_PATTERN = 'logs-*,filebeat-*,kibana_sample_data_logs*'; -export const TIMESTAMP_FIELD = '@timestamp'; export const METRICS_APP = 'metrics'; export const LOGS_APP = 'logs'; @@ -16,3 +15,9 @@ export const METRICS_FEATURE_ID = 'infrastructure'; export const LOGS_FEATURE_ID = 'logs'; export type InfraFeatureId = typeof METRICS_FEATURE_ID | typeof LOGS_FEATURE_ID; + +export const TIMESTAMP_FIELD = '@timestamp'; +export const TIEBREAKER_FIELD = '_doc'; +export const HOST_FIELD = 'host.name'; +export const CONTAINER_FIELD = 'container.id'; +export const POD_FIELD = 'kubernetes.pod.uid'; diff --git a/x-pack/plugins/infra/common/http_api/host_details/process_list.ts b/x-pack/plugins/infra/common/http_api/host_details/process_list.ts index 79835a0a78f262..395b1527379a9a 100644 --- a/x-pack/plugins/infra/common/http_api/host_details/process_list.ts +++ b/x-pack/plugins/infra/common/http_api/host_details/process_list.ts @@ -14,7 +14,6 @@ const AggValueRT = rt.type({ export const ProcessListAPIRequestRT = rt.type({ hostTerm: rt.record(rt.string, rt.string), - timefield: rt.string, indexPattern: rt.string, to: rt.number, sortBy: rt.type({ @@ -102,7 +101,6 @@ export type ProcessListAPIResponse = rt.TypeOf; export const ProcessListAPIChartRequestRT = rt.type({ hostTerm: rt.record(rt.string, rt.string), - timefield: rt.string, indexPattern: rt.string, to: rt.number, command: rt.string, diff --git a/x-pack/plugins/infra/common/http_api/metrics_api.ts b/x-pack/plugins/infra/common/http_api/metrics_api.ts index c2449707647d74..315a42380397bd 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_api.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_api.ts @@ -10,7 +10,6 @@ import { MetricsUIAggregationRT } from '../inventory_models/types'; import { afterKeyObjectRT } from './metrics_explorer'; export const MetricsAPITimerangeRT = rt.type({ - field: rt.string, from: rt.number, to: rt.number, interval: rt.string, diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer.ts index 5617bd0954f5d9..de00d521126e36 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer.ts @@ -41,7 +41,6 @@ export const metricsExplorerMetricRT = rt.intersection([ ]); export const timeRangeRT = rt.type({ - field: rt.string, from: rt.number, to: rt.number, interval: rt.string, diff --git a/x-pack/plugins/infra/common/inventory_models/index.ts b/x-pack/plugins/infra/common/inventory_models/index.ts index 6350e76ca7f29c..81f89be8cd6a6a 100644 --- a/x-pack/plugins/infra/common/inventory_models/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/index.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { POD_FIELD, HOST_FIELD, CONTAINER_FIELD } from '../constants'; import { host } from './host'; import { pod } from './pod'; import { awsEC2 } from './aws_ec2'; @@ -30,31 +31,23 @@ export const findInventoryModel = (type: InventoryItemType) => { return model; }; -interface InventoryFields { - host: string; - pod: string; - container: string; - timestamp: string; - tiebreaker: string; -} - const LEGACY_TYPES = ['host', 'pod', 'container']; -const getFieldByType = (type: InventoryItemType, fields: InventoryFields) => { +export const getFieldByType = (type: InventoryItemType) => { switch (type) { case 'pod': - return fields.pod; + return POD_FIELD; case 'host': - return fields.host; + return HOST_FIELD; case 'container': - return fields.container; + return CONTAINER_FIELD; } }; -export const findInventoryFields = (type: InventoryItemType, fields?: InventoryFields) => { +export const findInventoryFields = (type: InventoryItemType) => { const inventoryModel = findInventoryModel(type); - if (fields && LEGACY_TYPES.includes(type)) { - const id = getFieldByType(type, fields) || inventoryModel.fields.id; + if (LEGACY_TYPES.includes(type)) { + const id = getFieldByType(type) || inventoryModel.fields.id; return { ...inventoryModel.fields, id, diff --git a/x-pack/plugins/infra/common/log_search_result/index.ts b/x-pack/plugins/infra/common/log_search_result/index.ts index d1b2672fbfbaf8..592b23dfc70ad7 100644 --- a/x-pack/plugins/infra/common/log_search_result/index.ts +++ b/x-pack/plugins/infra/common/log_search_result/index.ts @@ -5,9 +5,9 @@ * 2.0. */ +export type { SearchResult } from './log_search_result'; export { getSearchResultIndexBeforeTime, getSearchResultIndexAfterTime, getSearchResultKey, - SearchResult, } from './log_search_result'; diff --git a/x-pack/plugins/infra/common/log_search_summary/index.ts b/x-pack/plugins/infra/common/log_search_summary/index.ts index feb64dee7d31a9..32652753f7799a 100644 --- a/x-pack/plugins/infra/common/log_search_summary/index.ts +++ b/x-pack/plugins/infra/common/log_search_summary/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { SearchSummaryBucket } from './log_search_summary'; +export type { SearchSummaryBucket } from './log_search_summary'; diff --git a/x-pack/plugins/infra/common/log_sources/log_source_configuration.ts b/x-pack/plugins/infra/common/log_sources/log_source_configuration.ts index ab98ad75b8433f..5d46ce59457da2 100644 --- a/x-pack/plugins/infra/common/log_sources/log_source_configuration.ts +++ b/x-pack/plugins/infra/common/log_sources/log_source_configuration.ts @@ -16,11 +16,6 @@ export const logSourceConfigurationOriginRT = rt.keyof({ export type LogSourceConfigurationOrigin = rt.TypeOf; const logSourceFieldsConfigurationRT = rt.strict({ - container: rt.string, - host: rt.string, - pod: rt.string, - timestamp: rt.string, - tiebreaker: rt.string, message: rt.array(rt.string), }); diff --git a/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts b/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts index c6bc10901fcb86..d3459b30a060e3 100644 --- a/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts +++ b/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts @@ -8,6 +8,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DataView, DataViewsContract } from '../../../../../src/plugins/data_views/common'; import { ObjectEntries } from '../utility_types'; +import { TIMESTAMP_FIELD, TIEBREAKER_FIELD } from '../constants'; import { ResolveLogSourceConfigurationError } from './errors'; import { LogSourceColumnConfiguration, @@ -61,8 +62,8 @@ const resolveLegacyReference = async ( return { indices: sourceConfiguration.logIndices.indexName, - timestampField: sourceConfiguration.fields.timestamp, - tiebreakerField: sourceConfiguration.fields.tiebreaker, + timestampField: TIMESTAMP_FIELD, + tiebreakerField: TIEBREAKER_FIELD, messageField: sourceConfiguration.fields.message, fields, runtimeMappings: {}, @@ -91,8 +92,8 @@ const resolveKibanaIndexPatternReference = async ( return { indices: indexPattern.title, - timestampField: indexPattern.timeFieldName ?? '@timestamp', - tiebreakerField: '_doc', + timestampField: indexPattern.timeFieldName ?? TIMESTAMP_FIELD, + tiebreakerField: TIEBREAKER_FIELD, messageField: ['message'], fields: indexPattern.fields, runtimeMappings: resolveRuntimeMappings(indexPattern), diff --git a/x-pack/plugins/infra/common/metrics_sources/index.ts b/x-pack/plugins/infra/common/metrics_sources/index.ts index a697c65e5a0aa8..7fae908707a899 100644 --- a/x-pack/plugins/infra/common/metrics_sources/index.ts +++ b/x-pack/plugins/infra/common/metrics_sources/index.ts @@ -6,7 +6,6 @@ */ import * as rt from 'io-ts'; -import { omit } from 'lodash'; import { SourceConfigurationRT, SourceStatusRuntimeType, @@ -22,7 +21,6 @@ export const metricsSourceConfigurationPropertiesRT = rt.strict({ metricAlias: SourceConfigurationRT.props.metricAlias, inventoryDefaultView: SourceConfigurationRT.props.inventoryDefaultView, metricsExplorerDefaultView: SourceConfigurationRT.props.metricsExplorerDefaultView, - fields: rt.strict(omit(SourceConfigurationRT.props.fields.props, 'message')), anomalyThreshold: rt.number, }); @@ -32,9 +30,6 @@ export type MetricsSourceConfigurationProperties = rt.TypeOf< export const partialMetricsSourceConfigurationPropertiesRT = rt.partial({ ...metricsSourceConfigurationPropertiesRT.type.props, - fields: rt.partial({ - ...metricsSourceConfigurationPropertiesRT.type.props.fields.type.props, - }), }); export type PartialMetricsSourceConfigurationProperties = rt.TypeOf< diff --git a/x-pack/plugins/infra/common/source_configuration/source_configuration.ts b/x-pack/plugins/infra/common/source_configuration/source_configuration.ts index 257cccc86595cd..0c30c3d678b2a3 100644 --- a/x-pack/plugins/infra/common/source_configuration/source_configuration.ts +++ b/x-pack/plugins/infra/common/source_configuration/source_configuration.ts @@ -50,12 +50,7 @@ export const sourceConfigurationConfigFilePropertiesRT = rt.type({ sources: rt.type({ default: rt.partial({ fields: rt.partial({ - timestamp: rt.string, message: rt.array(rt.string), - tiebreaker: rt.string, - host: rt.string, - container: rt.string, - pod: rt.string, }), }), }), @@ -113,11 +108,6 @@ export type InfraSourceConfigurationColumn = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index 6a5019d4683c8b..4d57674c94dad4 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -19,6 +19,7 @@ import { EuiFieldSearch, EuiAccordion, EuiPanel, + EuiLink, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -58,7 +59,7 @@ export { defaultExpression }; export const Expressions: React.FC = (props) => { const { setAlertParams, alertParams, errors, metadata } = props; - const { http, notifications } = useKibanaContextForPlugin().services; + const { http, notifications, docLinks } = useKibanaContextForPlugin().services; const { source, createDerivedIndexPattern } = useSourceViaHttp({ sourceId: 'default', fetch: http.fetch, @@ -260,6 +261,9 @@ export const Expressions: React.FC = (props) => { [alertParams.groupBy] ); + // Test to see if any of the group fields in groupBy are already filtered down to a single + // group by the filterQuery. If this is the case, then a groupBy is unnecessary, as it would only + // ever produce one group instance const groupByFilterTestPatterns = useMemo(() => { if (!alertParams.groupBy) return null; const groups = !Array.isArray(alertParams.groupBy) @@ -456,10 +460,20 @@ export const Expressions: React.FC = (props) => { {redundantFilterGroupBy.join(', ')}, groupCount: redundantFilterGroupBy.length, + filteringAndGroupingLink: ( + + {i18n.translate( + 'xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError.docsLink', + { defaultMessage: 'the docs' } + )} + + ), }} /> diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx index ec97d01a1cd6f5..c2c1fa719bb95b 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx @@ -54,14 +54,6 @@ describe('ExpressionChart', () => { metricAlias: 'metricbeat-*', inventoryDefaultView: 'host', metricsExplorerDefaultView: 'host', - // @ts-ignore - fields: { - timestamp: '@timestamp', - container: 'container.id', - host: 'host.name', - pod: 'kubernetes.pod.uid', - tiebreaker: '_doc', - }, anomalyThreshold: 20, }, }; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx index b241147c8d6755..99580adfed5bb8 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx @@ -172,7 +172,7 @@ To use the component your plugin needs to follow certain criteria: - Ensure `"infra"` and `"data"` are specified as a `requiredPlugins` in your plugin's `kibana.json`. - Ensure the `` component is mounted inside the hierachy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). At a minimum, the kibana-react provider must pass `http` (from core start services) and `data` (from core plugin start dependencies). -- Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/master/src/plugins/kibana_react/common/eui_styled_components.tsx). +- Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/main/src/plugins/kibana_react/common/eui_styled_components.tsx). ## Usage diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx index 2698e975cebcab..02e595628d783c 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx @@ -5,20 +5,19 @@ * 2.0. */ -import { buildEsQuery, Query, Filter } from '@kbn/es-query'; -import React, { useMemo, useCallback, useEffect } from 'react'; -import { noop } from 'lodash'; +import { buildEsQuery, Filter, Query } from '@kbn/es-query'; import { JsonValue } from '@kbn/utility-types'; +import { noop } from 'lodash'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { euiStyled } from '../../../../../../src/plugins/kibana_react/common'; -import { LogEntryCursor } from '../../../common/log_entry'; - import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { LogEntryCursor } from '../../../common/log_entry'; import { useLogSource } from '../../containers/logs/log_source'; import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream'; - -import { ScrollableLogTextStreamView } from '../logging/log_text_stream'; import { LogColumnRenderConfiguration } from '../../utils/log_column_render_configuration'; +import { useKibanaQuerySettings } from '../../utils/use_kibana_query_settings'; +import { ScrollableLogTextStreamView } from '../logging/log_text_stream'; import { LogStreamErrorBoundary } from './log_stream_error_boundary'; interface LogStreamPluginDeps { @@ -105,11 +104,13 @@ export const LogStreamContent: React.FC = ({ ` cannot access kibana core services. Ensure the component is mounted within kibana-react's hierarchy. -Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_react/README.md" +Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_react/README.md" ` ); } + const kibanaQuerySettings = useKibanaQuerySettings(); + const { derivedIndexPattern, isLoading: isLoadingSource, @@ -123,11 +124,19 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re const parsedQuery = useMemo(() => { if (typeof query === 'object' && 'bool' in query) { - return mergeBoolQueries(query, buildEsQuery(derivedIndexPattern, [], filters ?? [])); + return mergeBoolQueries( + query, + buildEsQuery(derivedIndexPattern, [], filters ?? [], kibanaQuerySettings) + ); } else { - return buildEsQuery(derivedIndexPattern, coerceToQueries(query), filters ?? []); + return buildEsQuery( + derivedIndexPattern, + coerceToQueries(query), + filters ?? [], + kibanaQuerySettings + ); } - }, [derivedIndexPattern, filters, query]); + }, [derivedIndexPattern, filters, kibanaQuerySettings, query]); // Internal state const { diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/validation.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/validation.tsx index b78a63e61e5244..67b3fa164d90cb 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/validation.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/validation.tsx @@ -9,7 +9,8 @@ import * as rt from 'io-ts'; import { ValidationIndicesError, validationIndicesErrorRT } from '../../../../../common/http_api'; import { DatasetFilter } from '../../../../../common/log_analysis'; -export { ValidationIndicesError, validationIndicesErrorRT }; +export type { ValidationIndicesError }; +export { validationIndicesErrorRT }; export const timeRangeValidationErrorRT = rt.strict({ error: rt.literal('INVALID_TIME_RANGE'), diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/index.ts b/x-pack/plugins/infra/public/components/logging/log_text_stream/index.ts index c3716d8c97ae19..dbd5bd49d02407 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/index.ts +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/index.ts @@ -5,12 +5,8 @@ * 2.0. */ -export { - LogEntryColumn, - LogEntryColumnWidths, - useColumnWidths, - iconColumnId, -} from './log_entry_column'; +export type { LogEntryColumnWidths } from './log_entry_column'; +export { LogEntryColumn, useColumnWidths, iconColumnId } from './log_entry_column'; export { LogEntryFieldColumn } from './log_entry_field_column'; export { LogEntryMessageColumn } from './log_entry_message_column'; export { LogEntryRowWrapper } from './log_entry_row'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/get_latest_categories_datasets_stats.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/get_latest_categories_datasets_stats.ts index bfabcdb4af7c1b..d715fb9f45194a 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/get_latest_categories_datasets_stats.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/get_latest_categories_datasets_stats.ts @@ -15,7 +15,7 @@ import { } from '../../../../../common/http_api'; import { decodeOrThrow } from '../../../../../common/runtime_types'; -export { LogEntryCategoriesDatasetStats }; +export type { LogEntryCategoriesDatasetStats }; export const callGetLatestCategoriesDatasetsStatsAPI = async ( { diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/index.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/index.ts index 40ac39e3fa22a4..5cccbdb78e46ee 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/index.ts @@ -14,4 +14,4 @@ export * from './log_analysis_module_status'; export * from './log_analysis_module_types'; export * from './log_analysis_setup_state'; -export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; +export type { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts index 4ff8c0c3c08e07..c8ce9a7b0127bb 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts @@ -17,7 +17,7 @@ import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; import { GetMlModuleResponsePayload } from './api/ml_get_module'; import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; -export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; +export type { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; export interface ModuleDescriptor { moduleId: string; diff --git a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts index f4576158b9a255..55b554560b743e 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts @@ -5,17 +5,16 @@ * 2.0. */ +import { buildEsQuery, DataViewBase, Query } from '@kbn/es-query'; import createContainer from 'constate'; import { useCallback, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; -import { DataViewBase } from '@kbn/es-query'; -import { esQuery, Query } from '../../../../../../../src/plugins/data/public'; - -type ParsedQuery = ReturnType; +import { useKibanaQuerySettings } from '../../../utils/use_kibana_query_settings'; +import { BuiltEsQuery } from '../log_stream'; interface ILogFilterState { filterQuery: { - parsedQuery: ParsedQuery; + parsedQuery: BuiltEsQuery; serializedQuery: string; originalQuery: Query; } | null; @@ -36,10 +35,11 @@ const validationDebounceTimeout = 1000; // milliseconds export const useLogFilterState = ({ indexPattern }: { indexPattern: DataViewBase }) => { const [logFilterState, setLogFilterState] = useState(initialLogFilterState); + const kibanaQuerySettings = useKibanaQuerySettings(); const parseQuery = useCallback( - (filterQuery: Query) => esQuery.buildEsQuery(indexPattern, filterQuery, []), - [indexPattern] + (filterQuery: Query) => buildEsQuery(indexPattern, filterQuery, [], kibanaQuerySettings), + [indexPattern, kibanaQuerySettings] ); const setLogFilterQueryDraft = useCallback((filterQueryDraft: Query) => { diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts index 6021c728d32afa..204fae7dc0f2b5 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts @@ -73,11 +73,6 @@ export const createBasicSourceConfiguration = (sourceId: string): LogSourceConfi }, logColumns: [], fields: { - container: 'CONTAINER_FIELD', - host: 'HOST_FIELD', - pod: 'POD_FIELD', - tiebreaker: 'TIEBREAKER_FIELD', - timestamp: 'TIMESTAMP_FIELD', message: ['MESSAGE_FIELD'], }, name: sourceId, diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts index 8f744a1d6df6d0..54f3f70b98a4bd 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts @@ -26,14 +26,14 @@ import { callFetchLogSourceConfigurationAPI } from './api/fetch_log_source_confi import { callFetchLogSourceStatusAPI } from './api/fetch_log_source_status'; import { callPatchLogSourceConfigurationAPI } from './api/patch_log_source_configuration'; -export { +export type { LogIndexField, LogSourceConfiguration, LogSourceConfigurationProperties, LogSourceConfigurationPropertiesPatch, LogSourceStatus, - ResolveLogSourceConfigurationError, }; +export { ResolveLogSourceConfigurationError }; export const useLogSource = ({ sourceId, diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts index 9d85316d978ebe..dc9ab56aa9e865 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { isEqual } from 'lodash'; +import { buildEsQuery } from '@kbn/es-query'; import createContainer from 'constate'; +import { isEqual } from 'lodash'; import { useCallback, useEffect, useMemo, useState } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; import useSetState from 'react-use/lib/useSetState'; -import { esQuery } from '../../../../../../../src/plugins/data/public'; import { LogEntry, LogEntryCursor } from '../../../../common/log_entry'; import { useSubscription } from '../../../utils/use_observable'; import { LogSourceConfigurationProperties } from '../log_source'; @@ -18,7 +18,7 @@ import { useFetchLogEntriesAfter } from './use_fetch_log_entries_after'; import { useFetchLogEntriesAround } from './use_fetch_log_entries_around'; import { useFetchLogEntriesBefore } from './use_fetch_log_entries_before'; -export type BuiltEsQuery = ReturnType; +export type BuiltEsQuery = ReturnType; interface LogStreamProps { sourceId: string; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx index 198a99f3948507..22376648ca0033 100644 --- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx @@ -19,7 +19,7 @@ export const useInfraMLModule = ({ moduleDescriptor: ModuleDescriptor; }) => { const { services } = useKibanaContextForPlugin(); - const { spaceId, sourceId, timestampField } = sourceConfiguration; + const { spaceId, sourceId } = sourceConfiguration; const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes); const [, fetchJobStatus] = useTrackedPromise( @@ -64,7 +64,6 @@ export const useInfraMLModule = ({ indices: selectedIndices, sourceId, spaceId, - timestampField, }, partitionField, }, @@ -91,7 +90,7 @@ export const useInfraMLModule = ({ dispatchModuleStatus({ type: 'failedSetup' }); }, }, - [moduleDescriptor.setUpModule, spaceId, sourceId, timestampField] + [moduleDescriptor.setUpModule, spaceId, sourceId] ); const [cleanUpModuleRequest, cleanUpModule] = useTrackedPromise( diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts index 4c876c1705364c..c258debdddbca5 100644 --- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts @@ -45,8 +45,7 @@ export const isJobConfigurationOutdated = isSubset( new Set(jobConfiguration.indexPattern.split(',')), new Set(currentSourceConfiguration.indices) - ) && - jobConfiguration.timestampField === currentSourceConfiguration.timestampField + ) ); }; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts index 5a5272f7830530..9b172a7c82a989 100644 --- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts @@ -15,7 +15,7 @@ import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; import { GetMlModuleResponsePayload } from './api/ml_get_module'; import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; -export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; +export type { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; export interface SetUpModuleArgs { start?: number | undefined; @@ -49,12 +49,10 @@ export interface ModuleDescriptor { ) => Promise; validateSetupIndices?: ( indices: string[], - timestampField: string, fetch: HttpHandler ) => Promise; validateSetupDatasets?: ( indices: string[], - timestampField: string, startTime: number, endTime: number, fetch: HttpHandler @@ -65,7 +63,6 @@ export interface ModuleSourceConfiguration { indices: string[]; sourceId: string; spaceId: string; - timestampField: string; } interface ManyCategoriesWarningReason { diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx index f892ab62ee3d80..f200ab22c043f8 100644 --- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx @@ -17,21 +17,18 @@ export const useMetricHostsModule = ({ indexPattern, sourceId, spaceId, - timestampField, }: { indexPattern: string; sourceId: string; spaceId: string; - timestampField: string; }) => { const sourceConfiguration: ModuleSourceConfiguration = useMemo( () => ({ indices: indexPattern.split(','), sourceId, spaceId, - timestampField, }), - [indexPattern, sourceId, spaceId, timestampField] + [indexPattern, sourceId, spaceId] ); const infraMLModule = useInfraMLModule({ diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts index a7ab948d052aab..f87cd78f4ff347 100644 --- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts @@ -18,6 +18,7 @@ import { MetricsHostsJobType, bucketSpan, } from '../../../../../common/infra_ml'; +import { TIMESTAMP_FIELD } from '../../../../../common/constants'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import MemoryJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_memory_usage.json'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -68,7 +69,7 @@ const setUpModule = async (setUpModuleArgs: SetUpModuleArgs, fetch: HttpHandler) start, end, filter, - moduleSourceConfiguration: { spaceId, sourceId, indices, timestampField }, + moduleSourceConfiguration: { spaceId, sourceId, indices }, partitionField, } = setUpModuleArgs; @@ -93,13 +94,13 @@ const setUpModule = async (setUpModuleArgs: SetUpModuleArgs, fetch: HttpHandler) return { job_id: id, data_description: { - time_field: timestampField, + time_field: TIMESTAMP_FIELD, }, analysis_config, custom_settings: { metrics_source_config: { indexPattern: indexNamePattern, - timestampField, + timestampField: TIMESTAMP_FIELD, bucketSpan, }, }, diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx index eadc374434817b..08f4f49058dbe2 100644 --- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx @@ -17,21 +17,18 @@ export const useMetricK8sModule = ({ indexPattern, sourceId, spaceId, - timestampField, }: { indexPattern: string; sourceId: string; spaceId: string; - timestampField: string; }) => { const sourceConfiguration: ModuleSourceConfiguration = useMemo( () => ({ indices: indexPattern.split(','), sourceId, spaceId, - timestampField, }), - [indexPattern, sourceId, spaceId, timestampField] + [indexPattern, sourceId, spaceId] ); const infraMLModule = useInfraMLModule({ diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts index 4c5eb5fd4bf239..388a7dd0a56568 100644 --- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts @@ -18,6 +18,7 @@ import { MetricK8sJobType, bucketSpan, } from '../../../../../common/infra_ml'; +import { TIMESTAMP_FIELD } from '../../../../../common/constants'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import MemoryJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_memory_usage.json'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -69,7 +70,7 @@ const setUpModule = async (setUpModuleArgs: SetUpModuleArgs, fetch: HttpHandler) start, end, filter, - moduleSourceConfiguration: { spaceId, sourceId, indices, timestampField }, + moduleSourceConfiguration: { spaceId, sourceId, indices }, partitionField, } = setUpModuleArgs; @@ -93,13 +94,13 @@ const setUpModule = async (setUpModuleArgs: SetUpModuleArgs, fetch: HttpHandler) return { job_id: id, data_description: { - time_field: timestampField, + time_field: TIMESTAMP_FIELD, }, analysis_config, custom_settings: { metrics_source_config: { indexPattern: indexNamePattern, - timestampField, + timestampField: TIMESTAMP_FIELD, bucketSpan, }, }, diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index 97a3f8eabbe4ed..a37a9af7d93200 100644 --- a/x-pack/plugins/infra/public/lib/lib.ts +++ b/x-pack/plugins/infra/public/lib/lib.ts @@ -14,7 +14,6 @@ import { SnapshotNodeMetric, SnapshotNodePath, } from '../../common/http_api/snapshot_api'; -import { MetricsSourceConfigurationProperties } from '../../common/metrics_sources'; import { WaffleSortOption } from '../pages/metrics/inventory_view/hooks/use_waffle_options'; export interface InfraWaffleMapNode { @@ -124,7 +123,6 @@ export enum InfraWaffleMapRuleOperator { } export interface InfraWaffleMapOptions { - fields?: Omit | null; formatter: InfraFormatterType; formatTemplate: string; metric: SnapshotMetricInput; diff --git a/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx b/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx index f9c80edd2c1995..cfcf8db771b788 100644 --- a/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx @@ -151,7 +151,7 @@ describe('LinkToLogsPage component', () => { const searchParams = new URLSearchParams(history.location.search); expect(searchParams.get('sourceId')).toEqual('default'); expect(searchParams.get('logFilter')).toMatchInlineSnapshot( - `"(language:kuery,query:'HOST_FIELD: HOST_NAME')"` + `"(language:kuery,query:'host.name: HOST_NAME')"` ); expect(searchParams.get('logPosition')).toEqual(null); }); @@ -172,7 +172,7 @@ describe('LinkToLogsPage component', () => { const searchParams = new URLSearchParams(history.location.search); expect(searchParams.get('sourceId')).toEqual('default'); expect(searchParams.get('logFilter')).toMatchInlineSnapshot( - `"(language:kuery,query:'(HOST_FIELD: HOST_NAME) and (FILTER_FIELD:FILTER_VALUE)')"` + `"(language:kuery,query:'(host.name: HOST_NAME) and (FILTER_FIELD:FILTER_VALUE)')"` ); expect(searchParams.get('logPosition')).toMatchInlineSnapshot( `"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"` @@ -193,7 +193,7 @@ describe('LinkToLogsPage component', () => { const searchParams = new URLSearchParams(history.location.search); expect(searchParams.get('sourceId')).toEqual('OTHER_SOURCE'); expect(searchParams.get('logFilter')).toMatchInlineSnapshot( - `"(language:kuery,query:'HOST_FIELD: HOST_NAME')"` + `"(language:kuery,query:'host.name: HOST_NAME')"` ); expect(searchParams.get('logPosition')).toEqual(null); }); @@ -229,7 +229,7 @@ describe('LinkToLogsPage component', () => { const searchParams = new URLSearchParams(history.location.search); expect(searchParams.get('sourceId')).toEqual('default'); expect(searchParams.get('logFilter')).toMatchInlineSnapshot( - `"(language:kuery,query:'CONTAINER_FIELD: CONTAINER_ID')"` + `"(language:kuery,query:'container.id: CONTAINER_ID')"` ); expect(searchParams.get('logPosition')).toEqual(null); }); @@ -250,7 +250,7 @@ describe('LinkToLogsPage component', () => { const searchParams = new URLSearchParams(history.location.search); expect(searchParams.get('sourceId')).toEqual('default'); expect(searchParams.get('logFilter')).toMatchInlineSnapshot( - `"(language:kuery,query:'(CONTAINER_FIELD: CONTAINER_ID) and (FILTER_FIELD:FILTER_VALUE)')"` + `"(language:kuery,query:'(container.id: CONTAINER_ID) and (FILTER_FIELD:FILTER_VALUE)')"` ); expect(searchParams.get('logPosition')).toMatchInlineSnapshot( `"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"` @@ -287,7 +287,7 @@ describe('LinkToLogsPage component', () => { const searchParams = new URLSearchParams(history.location.search); expect(searchParams.get('sourceId')).toEqual('default'); expect(searchParams.get('logFilter')).toMatchInlineSnapshot( - `"(language:kuery,query:'POD_FIELD: POD_UID')"` + `"(language:kuery,query:'kubernetes.pod.uid: POD_UID')"` ); expect(searchParams.get('logPosition')).toEqual(null); }); @@ -306,7 +306,7 @@ describe('LinkToLogsPage component', () => { const searchParams = new URLSearchParams(history.location.search); expect(searchParams.get('sourceId')).toEqual('default'); expect(searchParams.get('logFilter')).toMatchInlineSnapshot( - `"(language:kuery,query:'(POD_FIELD: POD_UID) and (FILTER_FIELD:FILTER_VALUE)')"` + `"(language:kuery,query:'(kubernetes.pod.uid: POD_UID) and (FILTER_FIELD:FILTER_VALUE)')"` ); expect(searchParams.get('logPosition')).toMatchInlineSnapshot( `"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"` diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index bc8c5699229d81..a8d339cfe979ab 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -34,12 +34,11 @@ export const RedirectToNodeLogs = ({ location, }: RedirectToNodeLogsType) => { const { services } = useKibanaContextForPlugin(); - const { isLoading, loadSource, sourceConfiguration } = useLogSource({ + const { isLoading, loadSource } = useLogSource({ fetch: services.http.fetch, sourceId, indexPatternsService: services.data.indexPatterns, }); - const fields = sourceConfiguration?.configuration.fields; useMount(() => { loadSource(); @@ -57,11 +56,9 @@ export const RedirectToNodeLogs = ({ })} /> ); - } else if (fields == null) { - return null; } - const nodeFilter = `${findInventoryFields(nodeType, fields).id}: ${nodeId}`; + const nodeFilter = `${findInventoryFields(nodeType).id}: ${nodeId}`; const userFilter = getFilterFromLocation(location); const filter = userFilter ? `(${nodeFilter}) and (${userFilter})` : nodeFilter; diff --git a/x-pack/plugins/infra/public/pages/link_to/use_host_ip_to_name.ts b/x-pack/plugins/infra/public/pages/link_to/use_host_ip_to_name.ts index f42e17c147a450..e2c25ba6fcfe25 100644 --- a/x-pack/plugins/infra/public/pages/link_to/use_host_ip_to_name.ts +++ b/x-pack/plugins/infra/public/pages/link_to/use_host_ip_to_name.ts @@ -24,7 +24,7 @@ export const useHostIpToName = (ipAddress: string | null, indexPattern: string | throw new Error('HTTP service is unavailable'); } if (ipAddress && indexPattern) { - const response = await fetch('/api/infra/ip_to_host', { + const response = await fetch('/api/infra/ip_to_host', { method: 'POST', body: JSON.stringify({ ip: ipAddress, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index de0a56c5be73da..f46a379f52d50f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -18,7 +18,6 @@ import { PageContent } from '../../../../components/page'; import { useWaffleTimeContext } from '../hooks/use_waffle_time'; import { useWaffleFiltersContext } from '../hooks/use_waffle_filters'; import { DEFAULT_LEGEND, useWaffleOptionsContext } from '../hooks/use_waffle_options'; -import { useSourceContext } from '../../../../containers/metrics_source'; import { InfraFormatterType } from '../../../../lib/lib'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { Toolbar } from './toolbars/toolbar'; @@ -41,7 +40,6 @@ interface Props { export const Layout = React.memo( ({ shouldLoadDefault, currentView, reload, interval, nodes, loading }: Props) => { const [showLoading, setShowLoading] = useState(true); - const { source } = useSourceContext(); const { metric, groupBy, @@ -65,7 +63,6 @@ export const Layout = React.memo( legend: createLegend(legendPalette, legendSteps, legendReverseColors), metric, sort, - fields: source?.configuration?.fields, groupBy, }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx index 4e28fb4202bdc2..1fcec291fcc296 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx @@ -67,13 +67,11 @@ export const AnomalyDetectionFlyout = () => { indexPattern={source?.configuration.metricAlias ?? ''} sourceId={'default'} spaceId={space.id} - timestampField={source?.configuration.fields.timestamp ?? ''} > {screenName === 'home' && ( diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx index b792078c394e99..8b5224068589cf 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx @@ -25,15 +25,13 @@ const TabComponent = (props: TabProps) => { const endTimestamp = props.currentTime; const startTimestamp = endTimestamp - 60 * 60 * 1000; // 60 minutes const { nodeType } = useWaffleOptionsContext(); - const { options, node } = props; + const { node } = props; const throttledTextQuery = useThrottle(textQuery, textQueryThrottleInterval); const filter = useMemo(() => { const query = [ - ...(options.fields != null - ? [`${findInventoryFields(nodeType, options.fields).id}: "${node.id}"`] - : []), + `${findInventoryFields(nodeType).id}: "${node.id}"`, ...(throttledTextQuery !== '' ? [throttledTextQuery] : []), ].join(' and '); @@ -41,7 +39,7 @@ const TabComponent = (props: TabProps) => { language: 'kuery', query, }; - }, [options.fields, nodeType, node.id, throttledTextQuery]); + }, [nodeType, node.id, throttledTextQuery]); const onQueryChange = useCallback((e: React.ChangeEvent) => { setTextQuery(e.target.value); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx index fbb8bd469c1e1f..7ff4720aec01e3 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx @@ -71,14 +71,12 @@ const TabComponent = (props: TabProps) => { ]); const { sourceId, createDerivedIndexPattern } = useSourceContext(); const { nodeType, accountId, region, customMetrics } = useWaffleOptionsContext(); - const { currentTime, options, node } = props; + const { currentTime, node } = props; const derivedIndexPattern = useMemo( () => createDerivedIndexPattern('metrics'), [createDerivedIndexPattern] ); - let filter = options.fields - ? `${findInventoryFields(nodeType, options.fields).id}: "${node.id}"` - : ''; + let filter = `${findInventoryFields(nodeType).id}: "${node.id}"`; if (filter) { filter = convertKueryToElasticSearchQuery(filter, derivedIndexPattern); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx index c227a31edc4ab3..2bed7681b8d56f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx @@ -17,6 +17,7 @@ import { EuiIconTip, Query, } from '@elastic/eui'; +import { getFieldByType } from '../../../../../../../../common/inventory_models'; import { useProcessList, SortBy, @@ -28,7 +29,7 @@ import { SummaryTable } from './summary_table'; import { ProcessesTable } from './processes_table'; import { parseSearchString } from './parse_search_string'; -const TabComponent = ({ currentTime, node, nodeType, options }: TabProps) => { +const TabComponent = ({ currentTime, node, nodeType }: TabProps) => { const [searchBarState, setSearchBarState] = useState(Query.MATCH_ALL); const [searchFilter, setSearchFilter] = useState(''); const [sortBy, setSortBy] = useState({ @@ -36,22 +37,17 @@ const TabComponent = ({ currentTime, node, nodeType, options }: TabProps) => { isAscending: false, }); - const timefield = options.fields!.timestamp; - const hostTerm = useMemo(() => { - const field = - options.fields && Reflect.has(options.fields, nodeType) - ? Reflect.get(options.fields, nodeType) - : nodeType; + const field = getFieldByType(nodeType) ?? nodeType; return { [field]: node.name }; - }, [options, node, nodeType]); + }, [node, nodeType]); const { loading, error, response, makeRequest: reload, - } = useProcessList(hostTerm, timefield, currentTime, sortBy, parseSearchString(searchFilter)); + } = useProcessList(hostTerm, currentTime, sortBy, parseSearchString(searchFilter)); const debouncedSearchOnChange = useMemo( () => debounce<(queryText: string) => void>((queryText) => setSearchFilter(queryText), 500), @@ -73,7 +69,7 @@ const TabComponent = ({ currentTime, node, nodeType, options }: TabProps) => { return ( - + { {isAlertFlyoutVisible && ( = withTheme return { label: host.ip, value: node.ip }; } } else { - if (options.fields) { - const { id } = findInventoryFields(nodeType, options.fields); - return { - label: {id}, - value: node.id, - }; - } + const { id } = findInventoryFields(nodeType); + return { + label: {id}, + value: node.id, + }; } return { label: '', value: '' }; - }, [nodeType, node.ip, node.id, options.fields]); + }, [nodeType, node.ip, node.id]); const nodeLogsMenuItemLinkProps = useLinkProps( getNodeLogsUrl({ @@ -184,11 +182,7 @@ export const NodeContextMenu: React.FC = withTheme {flyoutVisible && ( { injectTimes={currentMoment ? [currentMoment] : []} isLoading={isAutoReloading} onChange={handleChangeDate} - popperPlacement="top-end" + popoverPlacement="top-end" selected={currentMoment} shouldCloseOnSelect showTimeSelect diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts index e74abb2ecc4597..c653bb701eda0a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts @@ -22,7 +22,6 @@ export interface SortBy { export function useProcessList( hostTerm: Record, - timefield: string, to: number, sortBy: SortBy, searchFilter: object @@ -51,7 +50,6 @@ export function useProcessList( 'POST', JSON.stringify({ hostTerm, - timefield, indexPattern, to, sortBy: parsedSortBy, @@ -75,15 +73,11 @@ export function useProcessList( }; } -function useProcessListParams(props: { - hostTerm: Record; - timefield: string; - to: number; -}) { - const { hostTerm, timefield, to } = props; +function useProcessListParams(props: { hostTerm: Record; to: number }) { + const { hostTerm, to } = props; const { createDerivedIndexPattern } = useSourceContext(); const indexPattern = createDerivedIndexPattern('metrics').title; - return { hostTerm, indexPattern, timefield, to }; + return { hostTerm, indexPattern, to }; } const ProcessListContext = createContainter(useProcessListParams); export const [ProcessListContextProvider, useProcessListContext] = ProcessListContext; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list_row_chart.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list_row_chart.ts index 30d4e5960ba5eb..0d718ffbe210c8 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list_row_chart.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list_row_chart.ts @@ -25,14 +25,13 @@ export function useProcessListRowChart(command: string) { fold(throwErrors(createPlainError), identity) ); }; - const { hostTerm, timefield, indexPattern, to } = useProcessListContext(); + const { hostTerm, indexPattern, to } = useProcessListContext(); const { error, loading, response, makeRequest } = useHTTPRequest( '/api/metrics/process_list/chart', 'POST', JSON.stringify({ hostTerm, - timefield, indexPattern, to, command, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts index dbe45a387891ce..af93f6c0d62cee 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts @@ -10,13 +10,6 @@ import { InfraWaffleMapOptions, InfraFormatterType } from '../../../../lib/lib'; import { SnapshotMetricType } from '../../../../../common/inventory_models/types'; const options: InfraWaffleMapOptions = { - fields: { - container: 'container.id', - pod: 'kubernetes.pod.uid', - host: 'host.name', - timestamp: '@timestanp', - tiebreaker: '@timestamp', - }, formatter: InfraFormatterType.percent, formatTemplate: '{{value}}', metric: { type: 'cpu' }, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.ts index 5c02893b867dec..b6fa4fe4273abc 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { get } from 'lodash'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../lib/lib'; import { InventoryItemType } from '../../../../../common/inventory_models/types'; +import { getFieldByType } from '../../../../../common/inventory_models'; import { LinkDescriptor } from '../../../../hooks/use_link_props'; export const createUptimeLink = ( @@ -24,7 +24,7 @@ export const createUptimeLink = ( }, }; } - const field = get(options, ['fields', nodeType], ''); + const field = getFieldByType(nodeType); return { app: 'uptime', hash: '/', diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts b/x-pack/plugins/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts index 2339319926da81..d1ba4502f37c33 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts @@ -8,6 +8,7 @@ import { InfraMetadataFeature } from '../../../../../common/http_api/metadata_api'; import { InventoryMetric } from '../../../../../common/inventory_models/types'; import { metrics } from '../../../../../common/inventory_models/metrics'; +import { TIMESTAMP_FIELD } from '../../../../../common/constants'; export const getFilteredMetrics = ( requiredMetrics: InventoryMetric[], @@ -20,7 +21,7 @@ export const getFilteredMetrics = ( const metricModelCreator = metrics.tsvb[metric]; // We just need to get a dummy version of the model so we can filter // using the `requires` attribute. - const metricModel = metricModelCreator('@timestamp', 'test', '>=1m'); + const metricModel = metricModelCreator(TIMESTAMP_FIELD, 'test', '>=1m'); return metricMetadata.some((m) => m && metricModel.requires.includes(m)); }); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx index 005dd5cc8c078e..581eec3e824ae0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx @@ -27,6 +27,7 @@ import { import { createTSVBLink } from './helpers/create_tsvb_link'; import { getNodeDetailUrl } from '../../../link_to/redirect_to_node_detail'; import { InventoryItemType } from '../../../../../common/inventory_models/types'; +import { HOST_FIELD, POD_FIELD, CONTAINER_FIELD } from '../../../../../common/constants'; import { useLinkProps } from '../../../../hooks/use_link_props'; export interface Props { @@ -44,13 +45,13 @@ const fieldToNodeType = ( groupBy: string | string[] ): InventoryItemType | undefined => { const fields = Array.isArray(groupBy) ? groupBy : [groupBy]; - if (fields.includes(source.fields.host)) { + if (fields.includes(HOST_FIELD)) { return 'host'; } - if (fields.includes(source.fields.pod)) { + if (fields.includes(POD_FIELD)) { return 'pod'; } - if (fields.includes(source.fields.container)) { + if (fields.includes(CONTAINER_FIELD)) { return 'container'; } }; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts index a9e65bc30a3c61..472e86200cba35 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts @@ -79,7 +79,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -97,7 +97,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'system.network.name:lo* and host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'system.network.name:lo* and host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -161,7 +161,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metric*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metric*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metric*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metric*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts index 84d87ee4ad1b77..5d1f9bafdedaf9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts @@ -8,6 +8,7 @@ import { encode } from 'rison-node'; import uuid from 'uuid'; import { set } from '@elastic/safer-lodash-set'; +import { TIMESTAMP_FIELD } from '../../../../../../common/constants'; import { MetricsSourceConfigurationProperties } from '../../../../../../common/metrics_sources'; import { colorTransformer, Color } from '../../../../../../common/color_palette'; import { MetricsExplorerSeries } from '../../../../../../common/http_api/metrics_explorer'; @@ -169,7 +170,7 @@ export const createTSVBLink = ( series: options.metrics.map(mapMetricToSeries(chartOptions)), show_grid: 1, show_legend: 1, - time_field: (source && source.fields.timestamp) || '@timestamp', + time_field: TIMESTAMP_FIELD, type: 'timeseries', filter: createFilterFromOptions(options, series), }, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts index c0d0b15217df3e..788760a0dfe1c5 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts @@ -84,7 +84,6 @@ export function useMetricsExplorerData( void 0, timerange: { ...timerange, - field: source.fields.timestamp, from: from.valueOf(), to: to.valueOf(), }, diff --git a/x-pack/plugins/infra/public/utils/loading_state/index.ts b/x-pack/plugins/infra/public/utils/loading_state/index.ts index f0f42f1afa6a4a..f93d0638b33669 100644 --- a/x-pack/plugins/infra/public/utils/loading_state/index.ts +++ b/x-pack/plugins/infra/public/utils/loading_state/index.ts @@ -5,18 +5,21 @@ * 2.0. */ -export { initialLoadingState, LoadingState } from './loading_state'; +export type { LoadingState } from './loading_state'; +export { initialLoadingState } from './loading_state'; -export { isManualLoadingPolicy, isIntervalLoadingPolicy, LoadingPolicy } from './loading_policy'; +export type { LoadingPolicy } from './loading_policy'; +export { isManualLoadingPolicy, isIntervalLoadingPolicy } from './loading_policy'; +export type { LoadingProgress } from './loading_progress'; export { createRunningProgressReducer, createIdleProgressReducer, isIdleLoadingProgress, isRunningLoadingProgress, - LoadingProgress, } from './loading_progress'; +export type { LoadingResult } from './loading_result'; export { createFailureResult, createFailureResultReducer, @@ -27,5 +30,4 @@ export { isFailureLoadingResult, isSuccessLoadingResult, isUninitializedLoadingResult, - LoadingResult, } from './loading_result'; diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts index 44f65b9e8071ab..6843bc631ce27f 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts @@ -8,7 +8,7 @@ import { encode } from 'rison-node'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { FetchData, FetchDataParams, LogsFetchDataResponse } from '../../../observability/public'; -import { DEFAULT_SOURCE_ID } from '../../common/constants'; +import { DEFAULT_SOURCE_ID, TIMESTAMP_FIELD } from '../../common/constants'; import { callFetchLogSourceConfigurationAPI } from '../containers/logs/log_source/api/fetch_log_source_configuration'; import { callFetchLogSourceStatusAPI } from '../containers/logs/log_source/api/fetch_log_source_status'; import { InfraClientCoreSetup, InfraClientStartDeps } from '../types'; @@ -30,7 +30,6 @@ interface StatsAggregation { interface LogParams { index: string; - timestampField: string; } type StatsAndSeries = Pick; @@ -63,7 +62,6 @@ export function getLogsOverviewDataFetcher( const { stats, series } = await fetchLogsOverview( { index: resolvedLogSourceConfiguration.indices, - timestampField: resolvedLogSourceConfiguration.timestampField, }, params, data @@ -117,7 +115,7 @@ async function fetchLogsOverview( function buildLogOverviewQuery(logParams: LogParams, params: FetchDataParams) { return { range: { - [logParams.timestampField]: { + [TIMESTAMP_FIELD]: { gt: new Date(params.absoluteTime.start).toISOString(), lte: new Date(params.absoluteTime.end).toISOString(), format: 'strict_date_optional_time', @@ -137,7 +135,7 @@ function buildLogOverviewAggregations(logParams: LogParams, params: FetchDataPar aggs: { series: { date_histogram: { - field: logParams.timestampField, + field: TIMESTAMP_FIELD, fixed_interval: params.intervalString, }, }, diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts index d0349ab20710f4..8a1920f534cd65 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts @@ -150,7 +150,6 @@ describe('Logs UI Observability Homepage Functions', () => { type: 'index_pattern', indexPatternId: 'test-index-pattern', }, - fields: { timestamp: '@timestamp', tiebreaker: '_doc' }, }, }, } as GetLogSourceConfigurationSuccessResponsePayload); diff --git a/x-pack/plugins/infra/public/utils/use_kibana_query_settings.ts b/x-pack/plugins/infra/public/utils/use_kibana_query_settings.ts new file mode 100644 index 00000000000000..5c75e67f623067 --- /dev/null +++ b/x-pack/plugins/infra/public/utils/use_kibana_query_settings.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EsQueryConfig } from '@kbn/es-query'; +import { SerializableRecord } from '@kbn/utility-types'; +import { useMemo } from 'react'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/public'; +import { useUiSetting$ } from '../../../../../src/plugins/kibana_react/public'; + +export const useKibanaQuerySettings = (): EsQueryConfig => { + const [allowLeadingWildcards] = useUiSetting$(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS); + const [queryStringOptions] = useUiSetting$(UI_SETTINGS.QUERY_STRING_OPTIONS); + const [dateFormatTZ] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); + const [ignoreFilterIfFieldNotInIndex] = useUiSetting$( + UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX + ); + + return useMemo( + () => ({ + allowLeadingWildcards, + queryStringOptions, + dateFormatTZ, + ignoreFilterIfFieldNotInIndex, + }), + [allowLeadingWildcards, dateFormatTZ, ignoreFilterIfFieldNotInIndex, queryStringOptions] + ); +}; diff --git a/x-pack/plugins/infra/server/deprecations.ts b/x-pack/plugins/infra/server/deprecations.ts index 4c2f5f6a9b3d14..5e016bc0948262 100644 --- a/x-pack/plugins/infra/server/deprecations.ts +++ b/x-pack/plugins/infra/server/deprecations.ts @@ -13,6 +13,13 @@ import { DeprecationsDetails, GetDeprecationsContext, } from 'src/core/server'; +import { + TIMESTAMP_FIELD, + TIEBREAKER_FIELD, + CONTAINER_FIELD, + HOST_FIELD, + POD_FIELD, +} from '../common/constants'; import { InfraSources } from './lib/sources'; const deprecatedFieldMessage = (fieldName: string, defaultValue: string, configNames: string[]) => @@ -28,11 +35,11 @@ const deprecatedFieldMessage = (fieldName: string, defaultValue: string, configN }); const DEFAULT_VALUES = { - timestamp: '@timestamp', - tiebreaker: '_doc', - container: 'container.id', - host: 'host.name', - pod: 'kubernetes.pod.uid', + timestamp: TIMESTAMP_FIELD, + tiebreaker: TIEBREAKER_FIELD, + container: CONTAINER_FIELD, + host: HOST_FIELD, + pod: POD_FIELD, }; const FIELD_DEPRECATION_FACTORIES: Record DeprecationsDetails> = diff --git a/x-pack/plugins/infra/server/index.ts b/x-pack/plugins/infra/server/index.ts index a25bba48d673ec..93be23356dfc30 100644 --- a/x-pack/plugins/infra/server/index.ts +++ b/x-pack/plugins/infra/server/index.ts @@ -8,8 +8,9 @@ import { PluginInitializerContext } from 'src/core/server'; import { config, InfraConfig, InfraServerPlugin, InfraPluginSetup } from './plugin'; -export { config, InfraConfig, InfraPluginSetup }; -export { InfraRequestHandlerContext } from './types'; +export type { InfraConfig, InfraPluginSetup }; +export { config }; +export type { InfraRequestHandlerContext } from './types'; export function plugin(context: PluginInitializerContext) { return new InfraServerPlugin(context); diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index 75a86ae654d6c3..7e8f5ebfd5af48 100644 --- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -24,6 +24,7 @@ import { import { SortedSearchHit } from '../framework'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { ResolvedLogSourceConfiguration } from '../../../../common/log_sources'; +import { TIMESTAMP_FIELD, TIEBREAKER_FIELD } from '../../../../common/constants'; const TIMESTAMP_FORMAT = 'epoch_millis'; @@ -64,8 +65,8 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { : {}; const sort = { - [resolvedLogSourceConfiguration.timestampField]: sortDirection, - [resolvedLogSourceConfiguration.tiebreakerField]: sortDirection, + [TIMESTAMP_FIELD]: sortDirection, + [TIEBREAKER_FIELD]: sortDirection, }; const esQuery = { @@ -83,7 +84,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { ...createFilterClauses(query, highlightQuery), { range: { - [resolvedLogSourceConfiguration.timestampField]: { + [TIMESTAMP_FIELD]: { gte: startTimestamp, lte: endTimestamp, format: TIMESTAMP_FORMAT, @@ -146,7 +147,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { aggregations: { count_by_date: { date_range: { - field: resolvedLogSourceConfiguration.timestampField, + field: TIMESTAMP_FIELD, format: TIMESTAMP_FORMAT, ranges: bucketIntervalStarts.map((bucketIntervalStart) => ({ from: bucketIntervalStart.getTime(), @@ -157,10 +158,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { top_hits_by_key: { top_hits: { size: 1, - sort: [ - { [resolvedLogSourceConfiguration.timestampField]: 'asc' }, - { [resolvedLogSourceConfiguration.tiebreakerField]: 'asc' }, - ], + sort: [{ [TIMESTAMP_FIELD]: 'asc' }, { [TIEBREAKER_FIELD]: 'asc' }], _source: false, }, }, @@ -173,7 +171,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { ...createQueryFilterClauses(filterQuery), { range: { - [resolvedLogSourceConfiguration.timestampField]: { + [TIMESTAMP_FIELD]: { gte: startTimestamp, lte: endTimestamp, format: TIMESTAMP_FORMAT, diff --git a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index 730da9511dc380..e05a5b647ad2b9 100644 --- a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { flatten, get } from 'lodash'; import { KibanaRequest } from 'src/core/server'; +import { TIMESTAMP_FIELD } from '../../../../common/constants'; import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { InfraMetricsAdapter, InfraMetricsRequestOptions } from './adapter_types'; @@ -36,7 +37,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { rawRequest: KibanaRequest ): Promise { const indexPattern = `${options.sourceConfiguration.metricAlias}`; - const fields = findInventoryFields(options.nodeType, options.sourceConfiguration.fields); + const fields = findInventoryFields(options.nodeType); const nodeField = fields.id; const search = (searchOptions: object) => @@ -122,11 +123,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { max: options.timerange.to, }; - const model = createTSVBModel( - options.sourceConfiguration.fields.timestamp, - indexPattern, - options.timerange.interval - ); + const model = createTSVBModel(TIMESTAMP_FIELD, indexPattern, options.timerange.interval); const client = ( opts: CallWithRequestParams @@ -137,7 +134,6 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { client, { indexPattern: `${options.sourceConfiguration.metricAlias}`, - timestampField: options.sourceConfiguration.fields.timestamp, timerange: options.timerange, }, model.requires diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/mocks/index.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/mocks/index.ts index 296a540b4a9201..f02dac2139097f 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/mocks/index.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/mocks/index.ts @@ -17,7 +17,6 @@ export const libsMock = { type: 'index_pattern', indexPatternId: 'some-id', }, - fields: { timestamp: '@timestamp' }, }, }); }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index 71c18d9f7cf042..8991c884336d30 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -74,7 +74,6 @@ export const evaluateAlert = { timeSize: 1, } as MetricExpressionParams; - const timefield = '@timestamp'; const groupBy = 'host.doggoname'; const timeframe = { start: moment().subtract(5, 'minutes').valueOf(), @@ -25,7 +24,7 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { }; describe('when passed no filterQuery', () => { - const searchBody = getElasticsearchMetricQuery(expressionParams, timefield, timeframe, groupBy); + const searchBody = getElasticsearchMetricQuery(expressionParams, timeframe, groupBy); test('includes a range filter', () => { expect( searchBody.query.bool.filter.find((filter) => filter.hasOwnProperty('range')) @@ -47,7 +46,6 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { const searchBody = getElasticsearchMetricQuery( expressionParams, - timefield, timeframe, groupBy, filterQuery diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts index 59dc398973f8c1..588b77250e6a64 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TIMESTAMP_FIELD } from '../../../../../common/constants'; import { networkTraffic } from '../../../../../common/inventory_models/shared/metrics/snapshot/network_traffic'; import { MetricExpressionParams, Aggregators } from '../types'; import { createPercentileAggregation } from './create_percentile_aggregation'; @@ -21,7 +22,6 @@ const getParsedFilterQuery: (filterQuery: string | undefined) => Record { }); const createMockStaticConfiguration = (sources: any) => ({ - enabled: true, inventory: { compositeSize: 2000, }, diff --git a/x-pack/plugins/infra/server/lib/host_details/process_list.ts b/x-pack/plugins/infra/server/lib/host_details/process_list.ts index a9125c73fe5d09..6e608bfa2ddcab 100644 --- a/x-pack/plugins/infra/server/lib/host_details/process_list.ts +++ b/x-pack/plugins/infra/server/lib/host_details/process_list.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TIMESTAMP_FIELD } from '../../../common/constants'; import { ProcessListAPIRequest, ProcessListAPIQueryAggregation } from '../../../common/http_api'; import { ESSearchClient } from '../metrics/types'; import { CMDLINE_FIELD } from './common'; @@ -13,7 +14,7 @@ const TOP_N = 10; export const getProcessList = async ( search: ESSearchClient, - { hostTerm, timefield, indexPattern, to, sortBy, searchFilter }: ProcessListAPIRequest + { hostTerm, indexPattern, to, sortBy, searchFilter }: ProcessListAPIRequest ) => { const body = { size: 0, @@ -22,7 +23,7 @@ export const getProcessList = async ( filter: [ { range: { - [timefield]: { + [TIMESTAMP_FIELD]: { gte: to - 60 * 1000, // 1 minute lte: to, }, @@ -47,7 +48,7 @@ export const getProcessList = async ( size: 1, sort: [ { - [timefield]: { + [TIMESTAMP_FIELD]: { order: 'desc', }, }, @@ -93,7 +94,7 @@ export const getProcessList = async ( size: 1, sort: [ { - [timefield]: { + [TIMESTAMP_FIELD]: { order: 'desc', }, }, diff --git a/x-pack/plugins/infra/server/lib/host_details/process_list_chart.ts b/x-pack/plugins/infra/server/lib/host_details/process_list_chart.ts index 413a97cb7a0583..7ff66a80e967b6 100644 --- a/x-pack/plugins/infra/server/lib/host_details/process_list_chart.ts +++ b/x-pack/plugins/infra/server/lib/host_details/process_list_chart.ts @@ -6,6 +6,7 @@ */ import { first } from 'lodash'; +import { TIMESTAMP_FIELD } from '../../../common/constants'; import { ProcessListAPIChartRequest, ProcessListAPIChartQueryAggregation, @@ -17,7 +18,7 @@ import { CMDLINE_FIELD } from './common'; export const getProcessListChart = async ( search: ESSearchClient, - { hostTerm, timefield, indexPattern, to, command }: ProcessListAPIChartRequest + { hostTerm, indexPattern, to, command }: ProcessListAPIChartRequest ) => { const body = { size: 0, @@ -26,7 +27,7 @@ export const getProcessListChart = async ( filter: [ { range: { - [timefield]: { + [TIMESTAMP_FIELD]: { gte: to - 60 * 1000, // 1 minute lte: to, }, @@ -60,7 +61,7 @@ export const getProcessListChart = async ( aggs: { timeseries: { date_histogram: { - field: timefield, + field: TIMESTAMP_FIELD, fixed_interval: '1m', extended_bounds: { min: to - 60 * 15 * 1000, // 15 minutes, diff --git a/x-pack/plugins/infra/server/lib/infra_ml/index.ts b/x-pack/plugins/infra/server/lib/infra_ml/index.ts index 82093b1a359d0a..853ef8d0d60b22 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/index.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/index.ts @@ -8,4 +8,4 @@ export * from './errors'; export * from './metrics_hosts_anomalies'; export * from './metrics_k8s_anomalies'; -export { MappedAnomalyHit } from './common'; +export type { MappedAnomalyHit } from './common'; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts index ab50986c3b3d5b..9c0f4313c6bdbc 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts @@ -6,6 +6,7 @@ */ import * as rt from 'io-ts'; +import { TIEBREAKER_FIELD } from '../../../../common/constants'; import { ANOMALY_THRESHOLD } from '../../../../common/infra_ml'; import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; import { @@ -20,9 +21,6 @@ import { import { InfluencerFilter } from '../common'; import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; -// TODO: Reassess validity of this against ML docs -const TIEBREAKER_FIELD = '_doc'; - const sortToMlFieldMap = { dataset: 'partition_field_value', anomalyScore: 'record_score', diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts index 8fb8df5eef3d75..23592aad2e3224 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts @@ -6,6 +6,7 @@ */ import * as rt from 'io-ts'; +import { TIEBREAKER_FIELD } from '../../../../common/constants'; import { ANOMALY_THRESHOLD } from '../../../../common/infra_ml'; import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; import { @@ -20,9 +21,6 @@ import { import { InfluencerFilter } from '../common'; import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; -// TODO: Reassess validity of this against ML docs -const TIEBREAKER_FIELD = '_doc'; - const sortToMlFieldMap = { dataset: 'partition_field_value', anomalyScore: 'record_score', diff --git a/x-pack/plugins/infra/server/lib/metrics/index.ts b/x-pack/plugins/infra/server/lib/metrics/index.ts index d291dbf88b49a7..c4641e265ea555 100644 --- a/x-pack/plugins/infra/server/lib/metrics/index.ts +++ b/x-pack/plugins/infra/server/lib/metrics/index.ts @@ -7,6 +7,7 @@ import { set } from '@elastic/safer-lodash-set'; import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; +import { TIMESTAMP_FIELD } from '../../../common/constants'; import { MetricsAPIRequest, MetricsAPIResponse, afterKeyObjectRT } from '../../../common/http_api'; import { ESSearchClient, @@ -36,7 +37,7 @@ export const query = async ( const filter: Array> = [ { range: { - [options.timerange.field]: { + [TIMESTAMP_FIELD]: { gte: options.timerange.from, lte: options.timerange.to, format: 'epoch_millis', diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/calculate_interval.ts b/x-pack/plugins/infra/server/lib/metrics/lib/calculate_interval.ts index f6bdfb2de0a299..ee309ad449b2de 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/calculate_interval.ts +++ b/x-pack/plugins/infra/server/lib/metrics/lib/calculate_interval.ts @@ -21,7 +21,6 @@ export const calculatedInterval = async (search: ESSearchClient, options: Metric search, { indexPattern: options.indexPattern, - timestampField: options.timerange.field, timerange: { from: options.timerange.from, to: options.timerange.to }, }, options.modules diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.test.ts b/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.test.ts index b49560f8c25f6e..8fe22e6f81d716 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.test.ts +++ b/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.test.ts @@ -13,7 +13,6 @@ const keys = ['example-0']; const options: MetricsAPIRequest = { timerange: { - field: '@timestamp', from: moment('2020-01-01T00:00:00Z').valueOf(), to: moment('2020-01-01T00:00:00Z').add(5, 'minute').valueOf(), interval: '1m', diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.test.ts b/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.test.ts index 91bf544b7e48f5..9b92793129d443 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.test.ts +++ b/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.test.ts @@ -11,7 +11,6 @@ import { MetricsAPIRequest } from '../../../../common/http_api'; const options: MetricsAPIRequest = { timerange: { - field: '@timestamp', from: moment('2020-01-01T00:00:00Z').valueOf(), to: moment('2020-01-01T01:00:00Z').valueOf(), interval: '>=1m', diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.ts b/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.ts index 65cd4ebe2d501b..769ccce409e65a 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.ts +++ b/x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TIMESTAMP_FIELD } from '../../../../common/constants'; import { MetricsAPIRequest } from '../../../../common/http_api/metrics_api'; import { calculateDateHistogramOffset } from './calculate_date_histogram_offset'; import { createMetricsAggregations } from './create_metrics_aggregations'; @@ -15,7 +16,7 @@ export const createAggregations = (options: MetricsAPIRequest) => { const histogramAggregation = { histogram: { date_histogram: { - field: options.timerange.field, + field: TIMESTAMP_FIELD, fixed_interval: intervalString, offset: options.alignDataToEnd ? calculateDateHistogramOffset(options.timerange) : '0s', extended_bounds: { diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts b/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts index 27fe491d3964b5..2e2d1736e59259 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts +++ b/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts @@ -11,7 +11,6 @@ import { createMetricsAggregations } from './create_metrics_aggregations'; const options: MetricsAPIRequest = { timerange: { - field: '@timestamp', from: moment('2020-01-01T00:00:00Z').valueOf(), to: moment('2020-01-01T01:00:00Z').valueOf(), interval: '>=1m', diff --git a/x-pack/plugins/infra/server/lib/sources/defaults.ts b/x-pack/plugins/infra/server/lib/sources/defaults.ts index b6139613cfce35..db262a432b3fcd 100644 --- a/x-pack/plugins/infra/server/lib/sources/defaults.ts +++ b/x-pack/plugins/infra/server/lib/sources/defaults.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - METRICS_INDEX_PATTERN, - LOGS_INDEX_PATTERN, - TIMESTAMP_FIELD, -} from '../../../common/constants'; +import { METRICS_INDEX_PATTERN, LOGS_INDEX_PATTERN } from '../../../common/constants'; import { InfraSourceConfiguration } from '../../../common/source_configuration/source_configuration'; export const defaultSourceConfiguration: InfraSourceConfiguration = { @@ -21,12 +17,7 @@ export const defaultSourceConfiguration: InfraSourceConfiguration = { indexName: LOGS_INDEX_PATTERN, }, fields: { - container: 'container.id', - host: 'host.name', message: ['message', '@message'], - pod: 'kubernetes.pod.uid', - tiebreaker: '_doc', - timestamp: TIMESTAMP_FIELD, }, inventoryDefaultView: '0', metricsExplorerDefaultView: '0', diff --git a/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts b/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts index 9f6f9cd284c678..fb550390e25beb 100644 --- a/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts +++ b/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts @@ -101,12 +101,7 @@ const sourceConfigurationWithIndexPatternReference: InfraSourceConfiguration = { name: 'NAME', description: 'DESCRIPTION', fields: { - container: 'CONTAINER_FIELD', - host: 'HOST_FIELD', message: ['MESSAGE_FIELD'], - pod: 'POD_FIELD', - tiebreaker: 'TIEBREAKER_FIELD', - timestamp: 'TIMESTAMP_FIELD', }, logColumns: [], logIndices: { diff --git a/x-pack/plugins/infra/server/lib/sources/sources.test.ts b/x-pack/plugins/infra/server/lib/sources/sources.test.ts index 904f51d12673fc..396d2c22a100f0 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.test.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.test.ts @@ -24,13 +24,6 @@ describe('the InfraSources lib', () => { attributes: { metricAlias: 'METRIC_ALIAS', logIndices: { type: 'index_pattern', indexPatternId: 'log_index_pattern_0' }, - fields: { - container: 'CONTAINER', - host: 'HOST', - pod: 'POD', - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, }, references: [ { @@ -50,13 +43,6 @@ describe('the InfraSources lib', () => { configuration: { metricAlias: 'METRIC_ALIAS', logIndices: { type: 'index_pattern', indexPatternId: 'LOG_INDEX_PATTERN' }, - fields: { - container: 'CONTAINER', - host: 'HOST', - pod: 'POD', - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, }, }); }); @@ -67,12 +53,6 @@ describe('the InfraSources lib', () => { default: { metricAlias: 'METRIC_ALIAS', logIndices: { type: 'index_pattern', indexPatternId: 'LOG_ALIAS' }, - fields: { - host: 'HOST', - pod: 'POD', - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, }, }), }); @@ -82,11 +62,7 @@ describe('the InfraSources lib', () => { version: 'foo', type: infraSourceConfigurationSavedObjectName, updated_at: '2000-01-01T00:00:00.000Z', - attributes: { - fields: { - container: 'CONTAINER', - }, - }, + attributes: {}, references: [], }); @@ -99,13 +75,6 @@ describe('the InfraSources lib', () => { configuration: { metricAlias: 'METRIC_ALIAS', logIndices: { type: 'index_pattern', indexPatternId: 'LOG_ALIAS' }, - fields: { - container: 'CONTAINER', - host: 'HOST', - pod: 'POD', - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, }, }); }); @@ -133,13 +102,6 @@ describe('the InfraSources lib', () => { configuration: { metricAlias: expect.any(String), logIndices: expect.any(Object), - fields: { - container: expect.any(String), - host: expect.any(String), - pod: expect.any(String), - tiebreaker: expect.any(String), - timestamp: expect.any(String), - }, }, }); }); diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 247888fc2ae70b..4e655f200d94fc 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -53,12 +53,7 @@ export const config: PluginConfigDescriptor = { schema.object({ fields: schema.maybe( schema.object({ - timestamp: schema.maybe(schema.string()), message: schema.maybe(schema.arrayOf(schema.string())), - tiebreaker: schema.maybe(schema.string()), - host: schema.maybe(schema.string()), - container: schema.maybe(schema.string()), - pod: schema.maybe(schema.string()), }) ), }) @@ -158,7 +153,8 @@ export class InfraServerPlugin implements Plugin { plugins.home.sampleData.addAppLinksToSampleDataset('logs', [ { - path: `/app/logs`, + sampleObject: null, // indicates that there is no sample object associated with this app link's path + getPath: () => `/app/logs`, label: logsSampleDataLinkLabel, icon: 'logsApp', }, diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts index c721ca75ea9788..5c4ae1981c5cd0 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TIMESTAMP_FIELD } from '../../../../common/constants'; import { InventoryCloudAccount } from '../../../../common/http_api/inventory_meta_api'; import { InfraMetadataAggregationResponse, @@ -49,7 +50,7 @@ export const getCloudMetadata = async ( must: [ { range: { - [sourceConfiguration.fields.timestamp]: { + [TIMESTAMP_FIELD]: { gte: currentTime - 86400000, // 24 hours ago lte: currentTime, format: 'epoch_millis', diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts index d9da7bce2246fe..126d1485cb702c 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts @@ -13,6 +13,7 @@ import { import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { CLOUD_METRICS_MODULES } from '../../../lib/constants'; +import { TIMESTAMP_FIELD } from '../../../../common/constants'; export interface InfraCloudMetricsAdapterResponse { buckets: InfraMetadataAggregationBucket[]; @@ -36,7 +37,7 @@ export const getCloudMetricsMetadata = async ( { match: { 'cloud.instance.id': instanceId } }, { range: { - [sourceConfiguration.fields.timestamp]: { + [TIMESTAMP_FIELD]: { gte: timeRange.from, lte: timeRange.to, format: 'epoch_millis', diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts index bfa0884bfe1999..1962a24f7d4dbe 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts @@ -15,6 +15,7 @@ import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framewor import { InfraSourceConfiguration } from '../../../lib/sources'; import { findInventoryFields } from '../../../../common/inventory_models'; import { InventoryItemType } from '../../../../common/inventory_models/types'; +import { TIMESTAMP_FIELD } from '../../../../common/constants'; export interface InfraMetricsAdapterResponse { id: string; @@ -30,7 +31,7 @@ export const getMetricMetadata = async ( nodeType: InventoryItemType, timeRange: { from: number; to: number } ): Promise => { - const fields = findInventoryFields(nodeType, sourceConfiguration.fields); + const fields = findInventoryFields(nodeType); const metricQuery = { allow_no_indices: true, ignore_unavailable: true, @@ -45,7 +46,7 @@ export const getMetricMetadata = async ( }, { range: { - [sourceConfiguration.fields.timestamp]: { + [TIMESTAMP_FIELD]: { gte: timeRange.from, lte: timeRange.to, format: 'epoch_millis', diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts index 94becdf6d28117..97a0707a4c2159 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts @@ -15,6 +15,7 @@ import { getPodNodeName } from './get_pod_node_name'; import { CLOUD_METRICS_MODULES } from '../../../lib/constants'; import { findInventoryFields } from '../../../../common/inventory_models'; import { InventoryItemType } from '../../../../common/inventory_models/types'; +import { TIMESTAMP_FIELD } from '../../../../common/constants'; export const getNodeInfo = async ( framework: KibanaFramework, @@ -50,8 +51,7 @@ export const getNodeInfo = async ( } return {}; } - const fields = findInventoryFields(nodeType, sourceConfiguration.fields); - const timestampField = sourceConfiguration.fields.timestamp; + const fields = findInventoryFields(nodeType); const params = { allow_no_indices: true, ignore_unavailable: true, @@ -60,14 +60,14 @@ export const getNodeInfo = async ( body: { size: 1, _source: ['host.*', 'cloud.*', 'agent.*'], - sort: [{ [timestampField]: 'desc' }], + sort: [{ [TIMESTAMP_FIELD]: 'desc' }], query: { bool: { filter: [ { match: { [fields.id]: nodeId } }, { range: { - [timestampField]: { + [TIMESTAMP_FIELD]: { gte: timeRange.from, lte: timeRange.to, format: 'epoch_millis', diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts index 164d94d9f692fc..3afb6a8abcb582 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts @@ -10,6 +10,7 @@ import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framewor import { InfraSourceConfiguration } from '../../../lib/sources'; import { findInventoryFields } from '../../../../common/inventory_models'; import type { InfraPluginRequestHandlerContext } from '../../../types'; +import { TIMESTAMP_FIELD } from '../../../../common/constants'; export const getPodNodeName = async ( framework: KibanaFramework, @@ -19,8 +20,7 @@ export const getPodNodeName = async ( nodeType: 'host' | 'pod' | 'container', timeRange: { from: number; to: number } ): Promise => { - const fields = findInventoryFields(nodeType, sourceConfiguration.fields); - const timestampField = sourceConfiguration.fields.timestamp; + const fields = findInventoryFields(nodeType); const params = { allow_no_indices: true, ignore_unavailable: true, @@ -29,7 +29,7 @@ export const getPodNodeName = async ( body: { size: 1, _source: ['kubernetes.node.name'], - sort: [{ [timestampField]: 'desc' }], + sort: [{ [TIMESTAMP_FIELD]: 'desc' }], query: { bool: { filter: [ @@ -37,7 +37,7 @@ export const getPodNodeName = async ( { exists: { field: `kubernetes.node.name` } }, { range: { - [timestampField]: { + [TIMESTAMP_FIELD]: { gte: timeRange.from, lte: timeRange.to, format: 'epoch_millis', diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_request_to_metrics_api_options.test.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_request_to_metrics_api_options.test.ts index 539e9a1fee6ef0..a6848e4f7a2ddd 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_request_to_metrics_api_options.test.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_request_to_metrics_api_options.test.ts @@ -10,7 +10,6 @@ import { convertRequestToMetricsAPIOptions } from './convert_request_to_metrics_ const BASE_REQUEST: MetricsExplorerRequestBody = { timerange: { - field: '@timestamp', from: new Date('2020-01-01T00:00:00Z').getTime(), to: new Date('2020-01-01T01:00:00Z').getTime(), interval: '1m', @@ -22,7 +21,6 @@ const BASE_REQUEST: MetricsExplorerRequestBody = { const BASE_METRICS_UI_OPTIONS: MetricsAPIRequest = { timerange: { - field: '@timestamp', from: new Date('2020-01-01T00:00:00Z').getTime(), to: new Date('2020-01-01T01:00:00Z').getTime(), interval: '1m', diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts index 9ca8c085eac44d..62e99cf8ffd320 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts @@ -44,7 +44,6 @@ export const findIntervalForMetrics = async ( client, { indexPattern: options.indexPattern, - timestampField: options.timerange.field, timerange: options.timerange, }, modules.filter(Boolean) as string[] diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts index 640d62c3667260..97154a7361c965 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TIMESTAMP_FIELD } from '../../../../common/constants'; import { ESSearchClient } from '../../../lib/metrics/types'; interface EventDatasetHit { @@ -19,7 +20,7 @@ export const getDatasetForField = async ( client: ESSearchClient, field: string, indexPattern: string, - timerange: { field: string; to: number; from: number } + timerange: { to: number; from: number } ) => { const params = { allow_no_indices: true, @@ -33,7 +34,7 @@ export const getDatasetForField = async ( { exists: { field } }, { range: { - [timerange.field]: { + [TIMESTAMP_FIELD]: { gte: timerange.from, lte: timerange.to, format: 'epoch_millis', @@ -45,7 +46,7 @@ export const getDatasetForField = async ( }, size: 1, _source: ['event.dataset'], - sort: [{ [timerange.field]: { order: 'desc' } }], + sort: [{ [TIMESTAMP_FIELD]: { order: 'desc' } }], }, }; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts index a2bf778d5016dd..b2e22752609c1f 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/query_total_groupings.ts @@ -6,6 +6,7 @@ */ import { isArray } from 'lodash'; +import { TIMESTAMP_FIELD } from '../../../../common/constants'; import { MetricsAPIRequest } from '../../../../common/http_api'; import { ESSearchClient } from '../../../lib/metrics/types'; @@ -26,7 +27,7 @@ export const queryTotalGroupings = async ( let filters: Array> = [ { range: { - [options.timerange.field]: { + [TIMESTAMP_FIELD]: { gte: options.timerange.from, lte: options.timerange.to, format: 'epoch_millis', diff --git a/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts b/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts index 7533f2801607ca..ccead528749cdd 100644 --- a/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts +++ b/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts @@ -7,6 +7,7 @@ import { MetricsSourceConfiguration } from '../../../../common/metrics_sources'; import { TopNodesRequest } from '../../../../common/http_api/overview_api'; +import { TIMESTAMP_FIELD } from '../../../../common/constants'; export const createTopNodesQuery = ( options: TopNodesRequest, @@ -22,7 +23,7 @@ export const createTopNodesQuery = ( filter: [ { range: { - [source.configuration.fields.timestamp]: { + [TIMESTAMP_FIELD]: { gte: options.timerange.from, lte: options.timerange.to, }, @@ -49,7 +50,7 @@ export const createTopNodesQuery = ( { field: 'host.name' }, { field: 'cloud.provider' }, ], - sort: { '@timestamp': 'desc' }, + sort: { [TIMESTAMP_FIELD]: 'desc' }, size: 1, }, }, diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts index 7de63ae59a3296..2931555fc06b0c 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts @@ -42,10 +42,7 @@ export const applyMetadataToLastPath = ( if (firstMetaDoc && lastPath) { // We will need the inventory fields so we can use the field paths to get // the values from the metadata document - const inventoryFields = findInventoryFields( - snapshotRequest.nodeType, - source.configuration.fields - ); + const inventoryFields = findInventoryFields(snapshotRequest.nodeType); // Set the label as the name and fallback to the id OR path.value lastPath.label = (firstMetaDoc[inventoryFields.name] ?? lastPath.value) as string; // If the inventory fields contain an ip address, we need to try and set that diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts index 7473907b7410b8..bf6e51b9fe94fb 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts @@ -25,7 +25,6 @@ const createInterval = async (client: ESSearchClient, options: InfraSnapshotRequ client, { indexPattern: options.sourceConfiguration.metricAlias, - timestampField: options.sourceConfiguration.fields.timestamp, timerange: { from: timerange.from, to: timerange.to }, }, modules, @@ -81,7 +80,6 @@ const aggregationsToModules = async ( async (field) => await getDatasetForField(client, field as string, options.sourceConfiguration.metricAlias, { ...options.timerange, - field: options.sourceConfiguration.fields.timestamp, }) ) ); diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts index f59756e0c5b25d..a3ca2cfd683bb0 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts @@ -16,7 +16,6 @@ import { LogQueryFields } from '../../../services/log_queries/get_log_query_fiel export interface SourceOverrides { indexPattern: string; - timestamp: string; } const transformAndQueryData = async ({ diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.test.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.test.ts index b4e6983a099004..aac5f9e1450220 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.test.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.test.ts @@ -47,12 +47,7 @@ const source: InfraSource = { indexPatternId: 'kibana_index_pattern', }, fields: { - container: 'container.id', - host: 'host.name', message: ['message', '@message'], - pod: 'kubernetes.pod.uid', - tiebreaker: '_doc', - timestamp: '@timestamp', }, inventoryDefaultView: '0', metricsExplorerDefaultView: '0', @@ -80,7 +75,7 @@ const snapshotRequest: SnapshotRequest = { const metricsApiRequest = { indexPattern: 'metrics-*,metricbeat-*', - timerange: { field: '@timestamp', from: 1605705900000, to: 1605706200000, interval: '60s' }, + timerange: { from: 1605705900000, to: 1605706200000, interval: '60s' }, metrics: [ { id: 'cpu', diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts index 3901c8677ae9bf..b7e389cae9126e 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TIMESTAMP_FIELD } from '../../../../common/constants'; import { findInventoryFields, findInventoryModel } from '../../../../common/inventory_models'; import { MetricsAPIRequest, SnapshotRequest } from '../../../../common/http_api'; import { ESSearchClient } from '../../../lib/metrics/types'; @@ -37,7 +38,6 @@ export const transformRequestToMetricsAPIRequest = async ({ const metricsApiRequest: MetricsAPIRequest = { indexPattern: sourceOverrides?.indexPattern ?? source.configuration.metricAlias, timerange: { - field: sourceOverrides?.timestamp ?? source.configuration.fields.timestamp, from: timeRangeWithIntervalApplied.from, to: timeRangeWithIntervalApplied.to, interval: timeRangeWithIntervalApplied.interval, @@ -69,10 +69,7 @@ export const transformRequestToMetricsAPIRequest = async ({ inventoryModel.nodeFilter?.forEach((f) => filters.push(f)); } - const inventoryFields = findInventoryFields( - snapshotRequest.nodeType, - source.configuration.fields - ); + const inventoryFields = findInventoryFields(snapshotRequest.nodeType); if (snapshotRequest.groupBy) { const groupBy = snapshotRequest.groupBy.map((g) => g.field).filter(Boolean) as string[]; metricsApiRequest.groupBy = [...groupBy, inventoryFields.id]; @@ -86,7 +83,7 @@ export const transformRequestToMetricsAPIRequest = async ({ size: 1, metrics: [{ field: inventoryFields.name }], sort: { - [source.configuration.fields.timestamp]: 'desc', + [TIMESTAMP_FIELD]: 'desc', }, }, }, diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts index b0d2eeb987861b..e48c990d7822fc 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts @@ -289,12 +289,7 @@ const createSourceConfigurationMock = (): InfraSource => ({ }, ], fields: { - pod: 'POD_FIELD', - host: 'HOST_FIELD', - container: 'CONTAINER_FIELD', message: ['MESSAGE_FIELD'], - timestamp: 'TIMESTAMP_FIELD', - tiebreaker: 'TIEBREAKER_FIELD', }, anomalyThreshold: 20, }, diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts index 1f03878ba6febb..685f11cb00a86e 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts @@ -244,12 +244,7 @@ const createSourceConfigurationMock = (): InfraSource => ({ metricsExplorerDefaultView: 'DEFAULT_VIEW', logColumns: [], fields: { - pod: 'POD_FIELD', - host: 'HOST_FIELD', - container: 'CONTAINER_FIELD', message: ['MESSAGE_FIELD'], - timestamp: 'TIMESTAMP_FIELD', - tiebreaker: 'TIEBREAKER_FIELD', }, anomalyThreshold: 20, }, diff --git a/x-pack/plugins/infra/server/services/log_queries/get_log_query_fields.ts b/x-pack/plugins/infra/server/services/log_queries/get_log_query_fields.ts index 55491db97dbd63..db1696854db838 100644 --- a/x-pack/plugins/infra/server/services/log_queries/get_log_query_fields.ts +++ b/x-pack/plugins/infra/server/services/log_queries/get_log_query_fields.ts @@ -12,7 +12,6 @@ import { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_a export interface LogQueryFields { indexPattern: string; - timestamp: string; } export const createGetLogQueryFields = (sources: InfraSources, framework: KibanaFramework) => { @@ -29,7 +28,6 @@ export const createGetLogQueryFields = (sources: InfraSources, framework: Kibana return { indexPattern: resolvedLogSourceConfiguration.indices, - timestamp: resolvedLogSourceConfiguration.timestampField, }; }; }; diff --git a/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts b/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts index 3357b1a842183f..cb754153c66151 100644 --- a/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts +++ b/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TIMESTAMP_FIELD } from '../../common/constants'; import { findInventoryModel } from '../../common/inventory_models'; // import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter'; import { InventoryItemType } from '../../common/inventory_models/types'; @@ -12,7 +13,6 @@ import { ESSearchClient } from '../lib/metrics/types'; interface Options { indexPattern: string; - timestampField: string; timerange: { from: number; to: number; @@ -44,7 +44,7 @@ export const calculateMetricInterval = async ( filter: [ { range: { - [options.timestampField]: { + [TIMESTAMP_FIELD]: { gte: from, lte: options.timerange.to, format: 'epoch_millis', diff --git a/x-pack/plugins/ingest_pipelines/README.md b/x-pack/plugins/ingest_pipelines/README.md index dd7c130c7a72dc..04cfc23cfbbff6 100644 --- a/x-pack/plugins/ingest_pipelines/README.md +++ b/x-pack/plugins/ingest_pipelines/README.md @@ -13,7 +13,7 @@ It requires a Basic license and the following cluster privileges: `manage_pipeli A new app called Ingest Pipelines is registered in the Management section and follows a typical CRUD UI pattern. The client-side portion of this app lives in [public/application](public/application) and uses endpoints registered in [server/routes/api](server/routes/api). For more information on the pipeline processors editor component, check out the [component readme](public/application/components/pipeline_processors_editor/README.md). -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions on setting up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions on setting up your development environment. ### Test coverage diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/index.ts index 5c59e6c0e24490..0d63e9cd2462bf 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/index.ts @@ -5,9 +5,11 @@ * 2.0. */ -export { ProcessorForm, ProcessorFormOnSubmitArg, OnSubmitHandler } from './processor_form'; +export type { ProcessorFormOnSubmitArg, OnSubmitHandler } from './processor_form'; +export { ProcessorForm } from './processor_form'; -export { ProcessorsTree, ProcessorInfo, OnActionHandler } from './processors_tree'; +export type { ProcessorInfo, OnActionHandler } from './processors_tree'; +export { ProcessorsTree } from './processors_tree'; export { PipelineProcessorsEditor } from './pipeline_processors_editor'; @@ -15,11 +17,13 @@ export { PipelineProcessorsEditorItem } from './pipeline_processors_editor_item' export { ProcessorRemoveModal } from './processor_remove_modal'; -export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json'; +export type { OnDoneLoadJsonHandler } from './load_from_json'; +export { LoadFromJsonButton } from './load_from_json'; export { TestPipelineActions } from './test_pipeline'; -export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip'; +export type { Position } from './pipeline_processors_editor_item_tooltip'; +export { PipelineProcessorsItemTooltip } from './pipeline_processors_editor_item_tooltip'; export { ProcessorsEmptyPrompt } from './processors_empty_prompt'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/index.ts index 2e89e4b907f075..1d8fd204b4583a 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/index.ts @@ -6,4 +6,4 @@ */ export { LoadFromJsonButton } from './button'; -export { OnDoneLoadJsonHandler } from './modal_provider'; +export type { OnDoneLoadJsonHandler } from './modal_provider'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/index.ts index a88f51cda2ebd4..8b029747c1cbff 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/index.ts @@ -7,4 +7,4 @@ export { PipelineProcessorsEditorItem } from './pipeline_processors_editor_item.container'; -export { Handlers } from './types'; +export type { Handlers } from './types'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/index.ts index e51d286a2b0134..f8967a8945b2ae 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip'; +export type { Position } from './pipeline_processors_editor_item_tooltip'; +export { PipelineProcessorsItemTooltip } from './pipeline_processors_editor_item_tooltip'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/index.ts index 75c0e4ec7cccd7..1418c919cb6b1a 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/index.ts @@ -5,8 +5,5 @@ * 2.0. */ -export { - ProcessorFormContainer as ProcessorForm, - ProcessorFormOnSubmitArg, - OnSubmitHandler, -} from './processor_form.container'; +export type { ProcessorFormOnSubmitArg, OnSubmitHandler } from './processor_form.container'; +export { ProcessorFormContainer as ProcessorForm } from './processor_form.container'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts index 1a2422b40d0b01..e3a0fae36e5773 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts @@ -45,4 +45,4 @@ export { UrlDecode } from './url_decode'; export { UserAgent } from './user_agent'; export { UriParts } from './uri_parts'; -export { FormFieldsComponent } from './shared'; +export type { FormFieldsComponent } from './shared'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processors_tree/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processors_tree/index.ts index e8ac8bce8c4c83..3cb4f1f0f3505d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processors_tree/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processors_tree/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ProcessorsTree, OnActionHandler, ProcessorInfo } from './processors_tree'; +export type { OnActionHandler, ProcessorInfo } from './processors_tree'; +export { ProcessorsTree } from './processors_tree'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/index.ts index d62ebab7c650c1..8d71908bca5e4b 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/index.ts @@ -5,10 +5,7 @@ * 2.0. */ -export { - getProcessorDescriptor, - mapProcessorTypeToDescriptor, - ProcessorType, -} from './map_processor_type_to_form'; +export type { ProcessorType } from './map_processor_type_to_form'; +export { getProcessorDescriptor, mapProcessorTypeToDescriptor } from './map_processor_type_to_form'; export { ErrorIcon, ErrorIgnoredIcon, SkippedIcon } from './status_icons'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/index.ts index 725f65f28ab917..82ba99a438522c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/index.ts @@ -5,7 +5,8 @@ * 2.0. */ -export { Tabs, TestPipelineFlyoutTab } from './test_pipeline_tabs'; +export type { TestPipelineFlyoutTab } from './test_pipeline_tabs'; +export { Tabs } from './test_pipeline_tabs'; export { DocumentsTab } from './tab_documents'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/index.ts index 261393e35a2bb0..6a83b29dc0d065 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/index.ts @@ -7,15 +7,11 @@ export { ProcessorsEditorContextProvider } from './context'; -export { - TestPipelineContextProvider, - useTestPipelineContext, - TestPipelineData, - TestPipelineContext, -} from './test_pipeline_context'; +export type { TestPipelineData, TestPipelineContext } from './test_pipeline_context'; +export { TestPipelineContextProvider, useTestPipelineContext } from './test_pipeline_context'; +export type { Props } from './processors_context'; export { PipelineProcessorsContextProvider, usePipelineProcessorsContext, - Props, } from './processors_context'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/index.ts index 3149abeb8e1e67..a3996721ed748c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/index.ts @@ -5,12 +5,13 @@ * 2.0. */ -export { Props, ProcessorsEditorContextProvider } from './context'; +export type { Props } from './context'; +export { ProcessorsEditorContextProvider } from './context'; -export { OnUpdateHandlerArg, OnUpdateHandler } from './types'; +export type { OnUpdateHandlerArg, OnUpdateHandler } from './types'; -export { SerializeResult } from './serialize'; +export type { SerializeResult } from './serialize'; -export { OnDoneLoadJsonHandler } from './components'; +export type { OnDoneLoadJsonHandler } from './components'; export { PipelineEditor } from './pipeline_editor'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/index.ts index 664e82e81a12c7..59753e5cfdbbad 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/index.ts @@ -5,13 +5,8 @@ * 2.0. */ -export { - State, - reducer, - useProcessorsState, - ProcessorsDispatch, - Action, -} from './processors_reducer'; +export type { State, ProcessorsDispatch, Action } from './processors_reducer'; +export { reducer, useProcessorsState } from './processors_reducer'; export * from './constants'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/types.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/types.ts index efb2c4a42c7631..fba905d8e26bf3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/types.ts @@ -31,7 +31,7 @@ export interface ProcessorInternal { onFailure?: ProcessorInternal[]; } -export { OnFormUpdateArg }; +export type { OnFormUpdateArg }; export interface FormValidityState { validate: OnFormUpdateArg['validate']; diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index 29be11430bf646..bcc0060c1e5d44 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -10,51 +10,55 @@ import { AppServices } from './application'; export { CodeEditor }; +export type { + Error, + SendRequestConfig, + SendRequestResponse, + UseRequestConfig, + OnJsonEditorUpdateHandler, +} from '../../../../src/plugins/es_ui_shared/public/'; export { AuthorizationProvider, - Error, NotAuthorizedSection, SectionError, SectionLoading, sendRequest, - SendRequestConfig, - SendRequestResponse, useAuthorizationContext, useRequest, - UseRequestConfig, WithPrivileges, XJson, JsonEditor, - OnJsonEditorUpdateHandler, attemptToURIDecode, } from '../../../../src/plugins/es_ui_shared/public/'; -export { +export type { FormSchema, - FIELD_TYPES, FormConfig, - useForm, - Form, - getUseField, ValidationFuncArg, FormData, - UseField, - UseArray, ArrayItem, FormHook, - useFormContext, - UseMultiFields, - FormDataProvider, OnFormUpdateArg, FieldConfig, FieldHook, - getFieldValidityAndErrorMessage, ValidationFunc, ValidationConfig, - useFormData, FormOptions, SerializerFunc, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { + FIELD_TYPES, + useForm, + Form, + getUseField, + UseField, + UseArray, + useFormContext, + UseMultiFields, + FormDataProvider, + getFieldValidityAndErrorMessage, + useFormData, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { fieldFormatters, diff --git a/x-pack/plugins/lens/public/_mixins.scss b/x-pack/plugins/lens/public/_mixins.scss index 7282de214636c5..1ac02039faf420 100644 --- a/x-pack/plugins/lens/public/_mixins.scss +++ b/x-pack/plugins/lens/public/_mixins.scss @@ -50,16 +50,10 @@ // Removes EUI focus ring @mixin removeEuiFocusRing { - @include kbnThemeStyle('v7') { - animation: none !important; // sass-lint:disable-line no-important - } - - @include kbnThemeStyle('v8') { - outline: none; + outline: none; - &:focus-visible { - outline-style: none; - } + &:focus-visible { + outline-style: none; } } @@ -69,23 +63,14 @@ #{$target} { @include euiFocusBackground; - - @include kbnThemeStyle('v7') { - @include euiFocusRing; - } - - @include kbnThemeStyle('v8') { - outline: $euiFocusRingSize solid currentColor; // Safari & Firefox - } + outline: $euiFocusRingSize solid currentColor; // Safari & Firefox } - @include kbnThemeStyle('v8') { - &:focus-visible #{$target} { - outline-style: auto; // Chrome - } + &:focus-visible #{$target} { + outline-style: auto; // Chrome + } - &:not(:focus-visible) #{$target} { - outline: none; - } + &:not(:focus-visible) #{$target} { + outline: none; } -} \ No newline at end of file +} diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.scss b/x-pack/plugins/lens/public/drag_drop/drag_drop.scss index 5522b65fca261b..814640ba515eb5 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.scss +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.scss @@ -20,15 +20,8 @@ transform: translate(-12px, 8px); z-index: $lnsZLevel3; pointer-events: none; - - @include kbnThemeStyle('v7') { - box-shadow: 0 0 0 $euiFocusRingSize $euiFocusRingColor; - } - - @include kbnThemeStyle('v8') { - outline: $euiFocusRingSize solid currentColor; // Safari & Firefox - outline-style: auto; // Chrome - } + outline: $euiFocusRingSize solid currentColor; // Safari & Firefox + outline-style: auto; // Chrome } // Draggable item @@ -211,4 +204,4 @@ &.lnsDragDrop-incompatibleExtraDrop { color: $euiColorWarningText; } -} \ No newline at end of file +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index a8436edb63f638..cd26cd3197587b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -7,7 +7,13 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { createMockFramePublicAPI, visualizationMap, datasourceMap } from '../../../mocks'; +import { + createMockFramePublicAPI, + mockVisualizationMap, + mockDatasourceMap, + mockStoreDeps, + MountStoreProps, +} from '../../../mocks'; import { Visualization } from '../../../types'; import { LayerPanels } from './config_panel'; import { LayerPanel } from './layer_panel'; @@ -16,6 +22,7 @@ import { generateId } from '../../../id_generator'; import { mountWithProvider } from '../../../mocks'; import { layerTypes } from '../../../../common'; import { ReactWrapper } from 'enzyme'; +import { addLayer } from '../../../state_management'; jest.mock('../../../id_generator'); @@ -39,8 +46,40 @@ afterEach(() => { describe('ConfigPanel', () => { const frame = createMockFramePublicAPI(); - - function getDefaultProps() { + function prepareAndMountComponent( + props: ReturnType, + customStoreProps?: Partial + ) { + (generateId as jest.Mock).mockReturnValue(`newId`); + return mountWithProvider( + , + { + preloadedState: { + datasourceStates: { + testDatasource: { + isLoading: false, + state: 'state', + }, + }, + activeDatasourceId: 'testDatasource', + }, + storeDeps: mockStoreDeps({ + datasourceMap: props.datasourceMap, + visualizationMap: props.visualizationMap, + }), + ...customStoreProps, + }, + { + attachTo: container, + } + ); + } + function getDefaultProps( + { datasourceMap = mockDatasourceMap(), visualizationMap = mockVisualizationMap() } = { + datasourceMap: mockDatasourceMap(), + visualizationMap: mockVisualizationMap(), + } + ) { frame.datasourceLayers = { first: datasourceMap.testDatasource.publicAPIMock, }; @@ -75,22 +114,13 @@ describe('ConfigPanel', () => { it('should fail to render layerPanels if the public API is out of date', async () => { const props = getDefaultProps(); props.framePublicAPI.datasourceLayers = {}; - const { instance } = await mountWithProvider(); + const { instance } = await prepareAndMountComponent(props); expect(instance.find(LayerPanel).exists()).toBe(false); }); it('allow datasources and visualizations to use setters', async () => { const props = getDefaultProps(); - const { instance, lensStore } = await mountWithProvider(, { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }); + const { instance, lensStore } = await prepareAndMountComponent(props); const { updateDatasource, updateAll } = instance.find(LayerPanel).props(); const updater = () => 'updated'; @@ -116,22 +146,7 @@ describe('ConfigPanel', () => { describe('focus behavior when adding or removing layers', () => { it('should focus the only layer when resetting the layer', async () => { - const { instance } = await mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }, - { - attachTo: container, - } - ); + const { instance } = await prepareAndMountComponent(getDefaultProps()); const firstLayerFocusable = instance .find(LayerPanel) .first() @@ -146,29 +161,15 @@ describe('ConfigPanel', () => { }); it('should focus the second layer when removing the first layer', async () => { - const defaultProps = getDefaultProps(); + const datasourceMap = mockDatasourceMap(); + const defaultProps = getDefaultProps({ datasourceMap }); // overwriting datasourceLayers to test two layers frame.datasourceLayers = { first: datasourceMap.testDatasource.publicAPIMock, second: datasourceMap.testDatasource.publicAPIMock, }; - const { instance } = await mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }, - { - attachTo: container, - } - ); + const { instance } = await prepareAndMountComponent(defaultProps); const secondLayerFocusable = instance .find(LayerPanel) .at(1) @@ -183,28 +184,14 @@ describe('ConfigPanel', () => { }); it('should focus the first layer when removing the second layer', async () => { - const defaultProps = getDefaultProps(); + const datasourceMap = mockDatasourceMap(); + const defaultProps = getDefaultProps({ datasourceMap }); // overwriting datasourceLayers to test two layers frame.datasourceLayers = { first: datasourceMap.testDatasource.publicAPIMock, second: datasourceMap.testDatasource.publicAPIMock, }; - const { instance } = await mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }, - { - attachTo: container, - } - ); + const { instance } = await prepareAndMountComponent(defaultProps); const firstLayerFocusable = instance .find(LayerPanel) .first() @@ -219,31 +206,22 @@ describe('ConfigPanel', () => { }); it('should focus the added layer', async () => { - (generateId as jest.Mock).mockReturnValue(`second`); + const datasourceMap = mockDatasourceMap(); + frame.datasourceLayers = { + first: datasourceMap.testDatasource.publicAPIMock, + newId: datasourceMap.testDatasource.publicAPIMock, + }; - const { instance } = await mountWithProvider( - , + const defaultProps = getDefaultProps({ datasourceMap }); + + const { instance } = await prepareAndMountComponent(defaultProps, { + dispatch: jest.fn((x) => { + if (x.type === addLayer.type) { + frame.datasourceLayers.newId = datasourceMap.testDatasource.publicAPIMock; + } + }), + }); - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - activeDatasourceId: 'testDatasource', - }, - dispatch: jest.fn((x) => { - if (x.payload.subType === 'ADD_LAYER') { - frame.datasourceLayers.second = datasourceMap.testDatasource.publicAPIMock; - } - }), - }, - { - attachTo: container, - } - ); act(() => { instance.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click'); }); @@ -253,26 +231,6 @@ describe('ConfigPanel', () => { }); describe('initial default value', () => { - function prepareAndMountComponent(props: ReturnType) { - (generateId as jest.Mock).mockReturnValue(`newId`); - return mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - activeDatasourceId: 'testDatasource', - }, - }, - { - attachTo: container, - } - ); - } function clickToAddLayer(instance: ReactWrapper) { act(() => { instance.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click'); @@ -297,8 +255,10 @@ describe('ConfigPanel', () => { } it('should not add an initial dimension when not specified', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + const visualizationMap = mockVisualizationMap(); + + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer' }, { type: layerTypes.REFERENCELINE, @@ -306,6 +266,7 @@ describe('ConfigPanel', () => { }, ]); datasourceMap.testDatasource.initializeDimension = jest.fn(); + const props = getDefaultProps({ datasourceMap, visualizationMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddLayer(instance); @@ -315,8 +276,11 @@ describe('ConfigPanel', () => { }); it('should not add an initial dimension when initialDimensions are not available for the given layer type', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + const visualizationMap = mockVisualizationMap(); + datasourceMap.testDatasource.initializeDimension = jest.fn(); + + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer', @@ -335,8 +299,7 @@ describe('ConfigPanel', () => { label: 'Reference layer', }, ]); - datasourceMap.testDatasource.initializeDimension = jest.fn(); - + const props = getDefaultProps({ datasourceMap, visualizationMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddLayer(instance); @@ -345,8 +308,9 @@ describe('ConfigPanel', () => { }); it('should use group initial dimension value when adding a new layer if available', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + const visualizationMap = mockVisualizationMap(); + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer' }, { type: layerTypes.REFERENCELINE, @@ -363,6 +327,7 @@ describe('ConfigPanel', () => { }, ]); datasourceMap.testDatasource.initializeDimension = jest.fn(); + const props = getDefaultProps({ datasourceMap, visualizationMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddLayer(instance); @@ -378,8 +343,10 @@ describe('ConfigPanel', () => { }); it('should add an initial dimension value when clicking on the empty dimension button', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + + const visualizationMap = mockVisualizationMap(); + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer', @@ -395,7 +362,7 @@ describe('ConfigPanel', () => { }, ]); datasourceMap.testDatasource.initializeDimension = jest.fn(); - + const props = getDefaultProps({ visualizationMap, datasourceMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddDimension(instance); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index 0b6223ac87ce26..d3574abe4f57af 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -7,26 +7,26 @@ import React, { useMemo, memo } from 'react'; import { EuiForm } from '@elastic/eui'; -import { mapValues } from 'lodash'; import { Visualization } from '../../../types'; import { LayerPanel } from './layer_panel'; import { trackUiEvent } from '../../../lens_ui_telemetry'; import { generateId } from '../../../id_generator'; -import { appendLayer } from './layer_actions'; import { ConfigPanelWrapperProps } from './types'; import { useFocusUpdate } from './use_focus_update'; import { + setLayerDefaultDimension, useLensDispatch, + removeOrClearLayer, + addLayer, updateState, updateDatasourceState, updateVisualizationState, setToggleFullscreen, useLensSelector, selectVisualization, - VisualizationState, - LensAppState, } from '../../../state_management'; -import { AddLayerButton, getLayerType } from './add_layer'; +import { AddLayerButton } from './add_layer'; +import { getRemoveOperation } from '../../../utils'; export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) { const visualization = useLensSelector(selectVisualization); @@ -39,18 +39,6 @@ export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: Config ) : null; }); -function getRemoveOperation( - activeVisualization: Visualization, - visualizationState: VisualizationState['state'], - layerId: string, - layerCount: number -) { - if (activeVisualization.getRemoveOperation) { - return activeVisualization.getRemoveOperation(visualizationState, layerId); - } - // fallback to generic count check - return layerCount === 1 ? 'clear' : 'remove'; -} export function LayerPanels( props: ConfigPanelWrapperProps & { activeVisualization: Visualization; @@ -73,8 +61,7 @@ export function LayerPanels( dispatchLens( updateVisualizationState({ visualizationId: activeVisualization.id, - updater: newState, - clearStagedPreview: false, + newState, }) ); }, @@ -110,7 +97,6 @@ export function LayerPanels( setTimeout(() => { dispatchLens( updateState({ - subType: 'UPDATE_ALL_STATES', updater: (prevState) => { const updatedDatasourceState = typeof newDatasourceState === 'function' @@ -133,7 +119,6 @@ export function LayerPanels( ...prevState.visualization, state: updatedVisualizationState, }, - stagedPreview: undefined, }; }, }) @@ -183,66 +168,22 @@ export function LayerPanels( datasourceMap[activeDatasourceId]?.initializeDimension ) { dispatchLens( - updateState({ - subType: 'LAYER_DEFAULT_DIMENSION', - updater: (state) => - addInitialValueIfAvailable({ - ...props, - state, - activeDatasourceId, - layerId, - layerType: getLayerType( - activeVisualization, - state.visualization.state, - layerId - ), - columnId, - groupId, - }), + setLayerDefaultDimension({ + layerId, + columnId, + groupId, }) ); } }} onRemoveLayer={() => { dispatchLens( - updateState({ - subType: 'REMOVE_OR_CLEAR_LAYER', - updater: (state) => { - const isOnlyLayer = - getRemoveOperation( - activeVisualization, - state.visualization.state, - layerId, - layerIds.length - ) === 'clear'; - - return { - ...state, - datasourceStates: mapValues( - state.datasourceStates, - (datasourceState, datasourceId) => { - const datasource = datasourceMap[datasourceId!]; - return { - ...datasourceState, - state: isOnlyLayer - ? datasource.clearLayer(datasourceState.state, layerId) - : datasource.removeLayer(datasourceState.state, layerId), - }; - } - ), - visualization: { - ...state.visualization, - state: - isOnlyLayer || !activeVisualization.removeLayer - ? activeVisualization.clearLayer(state.visualization.state, layerId) - : activeVisualization.removeLayer(state.visualization.state, layerId), - }, - stagedPreview: undefined, - }; - }, + removeOrClearLayer({ + visualizationId: activeVisualization.id, + layerId, + layerIds, }) ); - removeLayerRef(layerId); }} toggleFullscreen={toggleFullscreen} @@ -254,96 +195,12 @@ export function LayerPanels( visualizationState={visualization.state} layersMeta={props.framePublicAPI} onAddLayerClick={(layerType) => { - const id = generateId(); - dispatchLens( - updateState({ - subType: 'ADD_LAYER', - updater: (state) => { - const newState = appendLayer({ - activeVisualization, - generateId: () => id, - trackUiEvent, - activeDatasource: datasourceMap[activeDatasourceId!], - state, - layerType, - }); - return addInitialValueIfAvailable({ - ...props, - activeDatasourceId: activeDatasourceId!, - state: newState, - layerId: id, - layerType, - }); - }, - }) - ); - setNextFocusedLayerId(id); + const layerId = generateId(); + dispatchLens(addLayer({ layerId, layerType })); + trackUiEvent('layer_added'); + setNextFocusedLayerId(layerId); }} /> ); } - -function addInitialValueIfAvailable({ - state, - activeVisualization, - framePublicAPI, - layerType, - activeDatasourceId, - datasourceMap, - layerId, - columnId, - groupId, -}: ConfigPanelWrapperProps & { - state: LensAppState; - activeDatasourceId: string; - activeVisualization: Visualization; - layerId: string; - layerType: string; - columnId?: string; - groupId?: string; -}) { - const layerInfo = activeVisualization - .getSupportedLayers(state.visualization.state, framePublicAPI) - .find(({ type }) => type === layerType); - - const activeDatasource = datasourceMap[activeDatasourceId]; - - if (layerInfo?.initialDimensions && activeDatasource?.initializeDimension) { - const info = groupId - ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) - : // pick the first available one if not passed - layerInfo.initialDimensions[0]; - - if (info) { - return { - ...state, - datasourceStates: { - ...state.datasourceStates, - [activeDatasourceId]: { - ...state.datasourceStates[activeDatasourceId], - state: activeDatasource.initializeDimension( - state.datasourceStates[activeDatasourceId].state, - layerId, - { - ...info, - columnId: columnId || info.columnId, - } - ), - }, - }, - visualization: { - ...state.visualization, - state: activeVisualization.setDimension({ - groupId: info.groupId, - layerId, - columnId: columnId || info.columnId, - prevState: state.visualization.state, - frame: framePublicAPI, - }), - }, - }; - } - } - return state; -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts deleted file mode 100644 index 44cefb0bf8ec44..00000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts +++ /dev/null @@ -1,130 +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 { layerTypes } from '../../../../common'; -import { LensAppState } from '../../../state_management/types'; -import { removeLayer, appendLayer } from './layer_actions'; - -function createTestArgs(initialLayerIds: string[]) { - const trackUiEvent = jest.fn(); - const testDatasource = (datasourceId: string) => ({ - id: datasourceId, - clearLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).map((id: string) => - id === layerId ? `${datasourceId}_clear_${layerId}` : id - ), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), - insertLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], - }); - - const activeVisualization = { - clearLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).map((id: string) => (id === layerId ? `vis_clear_${layerId}` : id)), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), - getLayerIds: (layerIds: unknown) => layerIds as string[], - appendLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], - }; - - const datasourceStates = { - ds1: { - isLoading: false, - state: initialLayerIds.slice(0, 1), - }, - ds2: { - isLoading: false, - state: initialLayerIds.slice(1), - }, - }; - - return { - state: { - activeDatasourceId: 'ds1', - datasourceStates, - title: 'foo', - visualization: { - activeId: 'testVis', - state: initialLayerIds, - }, - } as unknown as LensAppState, - activeVisualization, - datasourceMap: { - ds1: testDatasource('ds1'), - ds2: testDatasource('ds2'), - }, - trackUiEvent, - stagedPreview: { - visualization: { - activeId: 'testVis', - state: initialLayerIds, - }, - datasourceStates, - }, - }; -} - -describe('removeLayer', () => { - it('should clear the layer if it is the only layer', () => { - const { state, trackUiEvent, datasourceMap, activeVisualization } = createTestArgs(['layer1']); - const newState = removeLayer({ - activeVisualization, - datasourceMap, - layerId: 'layer1', - state, - trackUiEvent, - }); - - expect(newState.visualization.state).toEqual(['vis_clear_layer1']); - expect(newState.datasourceStates.ds1.state).toEqual(['ds1_clear_layer1']); - expect(newState.datasourceStates.ds2.state).toEqual([]); - expect(newState.stagedPreview).not.toBeDefined(); - expect(trackUiEvent).toHaveBeenCalledWith('layer_cleared'); - }); - - it('should remove the layer if it is not the only layer', () => { - const { state, trackUiEvent, datasourceMap, activeVisualization } = createTestArgs([ - 'layer1', - 'layer2', - ]); - const newState = removeLayer({ - activeVisualization, - datasourceMap, - layerId: 'layer1', - state, - trackUiEvent, - }); - - expect(newState.visualization.state).toEqual(['layer2']); - expect(newState.datasourceStates.ds1.state).toEqual([]); - expect(newState.datasourceStates.ds2.state).toEqual(['layer2']); - expect(newState.stagedPreview).not.toBeDefined(); - expect(trackUiEvent).toHaveBeenCalledWith('layer_removed'); - }); -}); - -describe('appendLayer', () => { - it('should add the layer to the datasource and visualization', () => { - const { state, trackUiEvent, datasourceMap, activeVisualization } = createTestArgs([ - 'layer1', - 'layer2', - ]); - const newState = appendLayer({ - activeDatasource: datasourceMap.ds1, - activeVisualization, - generateId: () => 'foo', - state, - trackUiEvent, - layerType: layerTypes.DATA, - }); - - expect(newState.visualization.state).toEqual(['layer1', 'layer2', 'foo']); - expect(newState.datasourceStates.ds1.state).toEqual(['layer1', 'foo']); - expect(newState.datasourceStates.ds2.state).toEqual(['layer2']); - expect(newState.stagedPreview).not.toBeDefined(); - expect(trackUiEvent).toHaveBeenCalledWith('layer_added'); - }); -}); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts deleted file mode 100644 index c0f0847e8ff5c3..00000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts +++ /dev/null @@ -1,95 +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 { mapValues } from 'lodash'; -import type { LayerType } from '../../../../common'; -import { LensAppState } from '../../../state_management'; - -import { Datasource, Visualization } from '../../../types'; - -interface RemoveLayerOptions { - trackUiEvent: (name: string) => void; - state: LensAppState; - layerId: string; - activeVisualization: Pick; - datasourceMap: Record>; -} - -interface AppendLayerOptions { - trackUiEvent: (name: string) => void; - state: LensAppState; - generateId: () => string; - activeDatasource: Pick; - activeVisualization: Pick; - layerType: LayerType; -} - -export function removeLayer(opts: RemoveLayerOptions): LensAppState { - const { state, trackUiEvent: trackUiEvent, activeVisualization, layerId, datasourceMap } = opts; - const isOnlyLayer = activeVisualization - .getLayerIds(state.visualization.state) - .every((id) => id === opts.layerId); - - trackUiEvent(isOnlyLayer ? 'layer_cleared' : 'layer_removed'); - - return { - ...state, - datasourceStates: mapValues(state.datasourceStates, (datasourceState, datasourceId) => { - const datasource = datasourceMap[datasourceId!]; - return { - ...datasourceState, - state: isOnlyLayer - ? datasource.clearLayer(datasourceState.state, layerId) - : datasource.removeLayer(datasourceState.state, layerId), - }; - }), - visualization: { - ...state.visualization, - state: - isOnlyLayer || !activeVisualization.removeLayer - ? activeVisualization.clearLayer(state.visualization.state, layerId) - : activeVisualization.removeLayer(state.visualization.state, layerId), - }, - stagedPreview: undefined, - }; -} - -export function appendLayer({ - trackUiEvent, - activeVisualization, - state, - generateId, - activeDatasource, - layerType, -}: AppendLayerOptions): LensAppState { - trackUiEvent('layer_added'); - - if (!activeVisualization.appendLayer) { - return state; - } - - const layerId = generateId(); - - return { - ...state, - datasourceStates: { - ...state.datasourceStates, - [activeDatasource.id]: { - ...state.datasourceStates[activeDatasource.id], - state: activeDatasource.insertLayer( - state.datasourceStates[activeDatasource.id].state, - layerId - ), - }, - }, - visualization: { - ...state.visualization, - state: activeVisualization.appendLayer(state.visualization.state, layerId, layerType), - }, - stagedPreview: undefined, - }; -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss index 4c699ff899bba6..343cd746ba2ac7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss @@ -104,10 +104,6 @@ min-height: $euiSizeXXL - 2; word-break: break-word; font-weight: $euiFontWeightRegular; - - @include kbnThemeStyle('v7') { - font-size: $euiFontSizeS; - } } .lnsLayerPanel__triggerTextLabel { @@ -152,4 +148,4 @@ @include passDownFocusRing('.lnsLayerPanel__triggerTextLabel'); background-color: transparent; } -} \ No newline at end of file +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index d289b69f4105e6..37191ffa89fdcf 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -38,6 +38,7 @@ import { createMockDatasource, DatasourceMock, createExpressionRendererMock, + mockStoreDeps, } from '../../mocks'; import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; import { ReactExpressionRendererType } from 'src/plugins/expressions/public'; @@ -144,21 +145,19 @@ describe('editor_frame', () => { ExpressionRenderer: expressionRendererMock, }; - const lensStore = ( - await mountWithProvider(, { - preloadedState: { - activeDatasourceId: 'testDatasource', - datasourceStates: { - testDatasource: { - isLoading: true, - state: { - internalState1: '', - }, + const { lensStore } = await mountWithProvider(, { + preloadedState: { + activeDatasourceId: 'testDatasource', + datasourceStates: { + testDatasource: { + isLoading: true, + state: { + internalState1: '', }, }, }, - }) - ).lensStore; + }, + }); expect(mockDatasource.renderDataPanel).not.toHaveBeenCalled(); lensStore.dispatch( setState({ @@ -553,6 +552,7 @@ describe('editor_frame', () => { } beforeEach(async () => { + mockVisualization2.initialize.mockReturnValue({ initial: true }); mockDatasource.getLayers.mockReturnValue(['first', 'second']); mockDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -567,20 +567,27 @@ describe('editor_frame', () => { }, ]); + const visualizationMap = { + testVis: mockVisualization, + testVis2: mockVisualization2, + }; + + const datasourceMap = { + testDatasource: mockDatasource, + testDatasource2: mockDatasource2, + }; + const props = { ...getDefaultProps(), - visualizationMap: { - testVis: mockVisualization, - testVis2: mockVisualization2, - }, - datasourceMap: { - testDatasource: mockDatasource, - testDatasource2: mockDatasource2, - }, - + visualizationMap, + datasourceMap, ExpressionRenderer: expressionRendererMock, }; - instance = (await mountWithProvider()).instance; + instance = ( + await mountWithProvider(, { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), + }) + ).instance; // necessary to flush elements to dom synchronously instance.update(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.scss index b9f233d2b29508..37a4a88c32f22e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.scss @@ -1,10 +1,6 @@ @import '../../mixins'; @import '../../variables'; -.lnsSuggestionPanel__title { - margin-left: $euiSizeXS / 2; -} - .lnsSuggestionPanel__suggestions { @include euiScrollBar; @include lnsOverflowShadowHorizontal; @@ -16,7 +12,10 @@ // Padding / negative margins to make room for overflow shadow padding-left: $euiSizeXS; margin-left: -$euiSizeXS; - padding-bottom: $euiSizeXS; +} + +.lnsSuggestionPanel { + padding-bottom: $euiSizeS; } .lnsSuggestionPanel__button { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index 26e0be35557145..47070822a80801 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -18,7 +18,7 @@ import { act } from 'react-dom/test-utils'; import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { SuggestionPanel, SuggestionPanelProps, SuggestionPanelWrapper } from './suggestion_panel'; import { getSuggestions, Suggestion } from './suggestion_helpers'; -import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; +import { EuiIcon, EuiPanel, EuiToolTip, EuiAccordion } from '@elastic/eui'; import { LensIconChartDatatable } from '../../assets/chart_datatable'; import { mountWithProvider } from '../../mocks'; import { LensAppState, PreviewState, setState, setToggleFullscreen } from '../../state_management'; @@ -264,8 +264,10 @@ describe('suggestion_panel', () => { preloadedState, }); - expect(instance.find(EuiIcon)).toHaveLength(1); - expect(instance.find(EuiIcon).prop('type')).toEqual(LensIconChartDatatable); + expect(instance.find('[data-test-subj="lnsSuggestionsPanel"]').find(EuiIcon)).toHaveLength(1); + expect( + instance.find('[data-test-subj="lnsSuggestionsPanel"]').find(EuiIcon).prop('type') + ).toEqual(LensIconChartDatatable); }); it('should return no suggestion if visualization has missing index-patterns', async () => { @@ -292,6 +294,16 @@ describe('suggestion_panel', () => { expect(instance.html()).toEqual(null); }); + it('should hide the selections when the accordion is hidden', async () => { + const { instance } = await mountWithProvider(); + expect(instance.find(EuiAccordion)).toHaveLength(1); + act(() => { + instance.find(EuiAccordion).at(0).simulate('change'); + }); + + expect(instance.find('[data-test-subj="lnsSuggestionsPanel"]')).toEqual({}); + }); + it('should render preview expression if there is one', () => { mockDatasource.getLayers.mockReturnValue(['first']); (getSuggestions as jest.Mock).mockReturnValue([ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 5e5e19ea29e846..2f5ca01774ba17 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -8,17 +8,17 @@ import './suggestion_panel.scss'; import { camelCase, pick } from 'lodash'; -import React, { useState, useEffect, useMemo, useRef } from 'react'; +import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { EuiIcon, EuiTitle, EuiPanel, EuiIconTip, EuiToolTip, - EuiFlexGroup, - EuiFlexItem, EuiButtonEmpty, + EuiAccordion, } from '@elastic/eui'; import { IconType } from '@elastic/eui/src/components/icon/icon'; import { Ast, toExpression } from '@kbn/interpreter/common'; @@ -58,6 +58,7 @@ import { } from '../../state_management'; const MAX_SUGGESTIONS_DISPLAYED = 5; +const LOCAL_STORAGE_SUGGESTIONS_PANEL = 'LENS_SUGGESTIONS_PANEL_HIDDEN'; export interface SuggestionPanelProps { datasourceMap: DatasourceMap; @@ -189,6 +190,15 @@ export function SuggestionPanel({ const existsStagedPreview = useLensSelector((state) => Boolean(state.lens.stagedPreview)); const currentVisualization = useLensSelector(selectCurrentVisualization); const currentDatasourceStates = useLensSelector(selectCurrentDatasourceStates); + // get user's selection from localStorage, this key defines if the suggestions panel will be hidden or not + const [hideSuggestions, setHideSuggestions] = useLocalStorage( + LOCAL_STORAGE_SUGGESTIONS_PANEL, + false + ); + + const toggleSuggestions = useCallback(() => { + setHideSuggestions(!hideSuggestions); + }, [setHideSuggestions, hideSuggestions]); const missingIndexPatterns = getMissingIndexPattern( activeDatasourceId ? datasourceMap[activeDatasourceId] : null, @@ -322,9 +332,10 @@ export function SuggestionPanel({ return (
- - - +

-
- {existsStagedPreview && ( - + } + forceState={hideSuggestions ? 'closed' : 'open'} + onToggle={toggleSuggestions} + extraAction={ + existsStagedPreview && + !hideSuggestions && ( - - )} -
- -
- {currentVisualization.activeId && ( - - )} - {suggestions.map((suggestion, index) => { - return ( + ) + } + > +
+ {currentVisualization.activeId && !hideSuggestions && ( { - trackUiEvent('suggestion_clicked'); - if (lastSelectedSuggestion === index) { - rollbackToCurrentVisualization(); - } else { - setLastSelectedSuggestion(index); - switchToSuggestion(dispatchLens, suggestion); - } - }} - selected={index === lastSelectedSuggestion} + onSelect={rollbackToCurrentVisualization} + selected={lastSelectedSuggestion === -1} + showTitleAsLabel /> - ); - })} -
+ )} + {!hideSuggestions && + suggestions.map((suggestion, index) => { + return ( + { + trackUiEvent('suggestion_clicked'); + if (lastSelectedSuggestion === index) { + rollbackToCurrentVisualization(); + } else { + setLastSelectedSuggestion(index); + switchToSuggestion(dispatchLens, suggestion); + } + }} + selected={index === lastSelectedSuggestion} + /> + ); + })} +
+
); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx index 7cb97882a5e039..c325e6d516c8b7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx @@ -9,8 +9,9 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import { createMockVisualization, + mockStoreDeps, createMockFramePublicAPI, - createMockDatasource, + mockDatasourceMap, mockDatasourceStates, } from '../../../mocks'; import { mountWithProvider } from '../../../mocks'; @@ -71,7 +72,7 @@ describe('chart_switch', () => { * - Allows a switch to subvisC3 * - Allows a switch to subvisC1 */ - function mockVisualizations() { + function mockVisualizationMap() { return { visA: generateVisualization('visA'), visB: generateVisualization('visB'), @@ -143,27 +144,6 @@ describe('chart_switch', () => { } as FramePublicAPI; } - function mockDatasourceMap() { - const datasource = createMockDatasource('testDatasource'); - datasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ - { - state: {}, - table: { - columns: [], - isMultiRow: true, - layerId: 'a', - changeType: 'unchanged', - }, - keptLayerIds: ['a'], - }, - ]); - - datasource.getLayers.mockReturnValue(['a']); - return { - testDatasource: datasource, - }; - } - function showFlyout(instance: ReactWrapper) { instance.find('[data-test-subj="lnsChartSwitchPopover"]').first().simulate('click'); } @@ -178,10 +158,10 @@ describe('chart_switch', () => { return instance.find(`[data-test-subj="lnsChartSwitchPopover_${subType}"]`).first(); } it('should use suggested state if there is a suggestion from the target visualization', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const { instance, lensStore } = await mountWithProvider( , @@ -212,19 +192,20 @@ describe('chart_switch', () => { }); it('should use initial state if there is no suggestion from the target visualization', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a']); (frame.datasourceLayers.a.getTableSpec as jest.Mock).mockReturnValue([]); const datasourceMap = mockDatasourceMap(); const datasourceStates = mockDatasourceStates(); const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { datasourceStates, activeDatasourceId: 'testDatasource', @@ -249,18 +230,14 @@ describe('chart_switch', () => { }, }); expect(lensStore.dispatch).toHaveBeenCalledWith({ - type: 'lens/updateLayer', - payload: expect.objectContaining({ - datasourceId: 'testDatasource', - layerId: 'a', - }), + type: 'lens/removeLayers', + payload: { layerIds: ['a'], visualizationId: 'visA' }, }); }); it('should indicate data loss if not all columns will be used', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a']); - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -299,9 +276,9 @@ describe('chart_switch', () => { const { instance } = await mountWithProvider( , { preloadedState: { @@ -322,12 +299,11 @@ describe('chart_switch', () => { }); it('should indicate data loss if not all layers will be used', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a', 'b']); - const { instance } = await mountWithProvider( , @@ -350,9 +326,8 @@ describe('chart_switch', () => { }); it('should support multi-layer suggestions without data loss', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a', 'b']); - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -378,7 +353,7 @@ describe('chart_switch', () => { const { instance } = await mountWithProvider( , @@ -398,13 +373,13 @@ describe('chart_switch', () => { }); it('should indicate data loss if no data will be used', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a']); const { instance } = await mountWithProvider( , @@ -427,14 +402,14 @@ describe('chart_switch', () => { }); it('should not indicate data loss if there is no data', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a']); (frame.datasourceLayers.a.getTableSpec as jest.Mock).mockReturnValue([]); const { instance } = await mountWithProvider( , @@ -456,20 +431,19 @@ describe('chart_switch', () => { it('should not show a warning when the subvisualization is the same', async () => { const frame = mockFrame(['a', 'b', 'c']); - const visualizations = mockVisualizations(); - visualizations.visC.getVisualizationTypeId.mockReturnValue('subvisC2'); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visC.getVisualizationTypeId.mockReturnValue('subvisC2'); const switchVisualizationType = jest.fn(() => ({ type: 'subvisC1' })); - visualizations.visC.switchVisualizationType = switchVisualizationType; + visualizationMap.visC.switchVisualizationType = switchVisualizationType; - const datasourceMap = mockDatasourceMap(); const datasourceStates = mockDatasourceStates(); const { instance } = await mountWithProvider( , { preloadedState: { @@ -491,8 +465,8 @@ describe('chart_switch', () => { }); it('should get suggestions when switching subvisualization', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a', 'b', 'c']); const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getLayers.mockReturnValue(['a', 'b', 'c']); @@ -500,11 +474,12 @@ describe('chart_switch', () => { const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { datasourceStates, visualization: { @@ -519,7 +494,7 @@ describe('chart_switch', () => { expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'a'); expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'b'); expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'c'); - expect(visualizations.visB.getSuggestions).toHaveBeenCalledWith( + expect(visualizationMap.visB.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ keptLayerIds: ['a'], }) @@ -540,19 +515,18 @@ describe('chart_switch', () => { }); it('should query main palette from active chart and pass into suggestions', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const mockPalette: PaletteOutput = { type: 'palette', name: 'mock' }; - visualizations.visA.getMainPalette = jest.fn(() => mockPalette); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + visualizationMap.visA.getMainPalette = jest.fn(() => mockPalette); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a', 'b', 'c']); const currentVisState = {}; - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getLayers.mockReturnValue(['a', 'b', 'c']); const { instance } = await mountWithProvider( , @@ -563,14 +537,15 @@ describe('chart_switch', () => { state: currentVisState, }, }, + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), } ); switchTo('visB', instance); - expect(visualizations.visA.getMainPalette).toHaveBeenCalledWith(currentVisState); + expect(visualizationMap.visA.getMainPalette).toHaveBeenCalledWith(currentVisState); - expect(visualizations.visB.getSuggestions).toHaveBeenCalledWith( + expect(visualizationMap.visB.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ keptLayerIds: ['a'], mainPalette: mockPalette, @@ -580,14 +555,14 @@ describe('chart_switch', () => { it('should not remove layers when switching between subtypes', async () => { const frame = mockFrame(['a', 'b', 'c']); - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const switchVisualizationType = jest.fn(() => 'switched'); - visualizations.visC.switchVisualizationType = switchVisualizationType; + visualizationMap.visC.switchVisualizationType = switchVisualizationType; const datasourceMap = mockDatasourceMap(); const { instance, lensStore } = await mountWithProvider( , @@ -598,6 +573,7 @@ describe('chart_switch', () => { state: { type: 'subvisC1' }, }, }, + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), } ); @@ -622,13 +598,13 @@ describe('chart_switch', () => { it('should not remove layers and initialize with existing state when switching between subtypes without data', async () => { const frame = mockFrame(['a']); frame.datasourceLayers.a.getTableSpec = jest.fn().mockReturnValue([]); - const visualizations = mockVisualizations(); - visualizations.visC.getSuggestions = jest.fn().mockReturnValue([]); - visualizations.visC.switchVisualizationType = jest.fn(() => 'switched'); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visC.getSuggestions = jest.fn().mockReturnValue([]); + visualizationMap.visC.switchVisualizationType = jest.fn(() => 'switched'); const datasourceMap = mockDatasourceMap(); const { instance } = await mountWithProvider( , @@ -639,21 +615,21 @@ describe('chart_switch', () => { state: { type: 'subvisC1' }, }, }, + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), } ); switchTo('subvisC3', instance); - expect(visualizations.visC.switchVisualizationType).toHaveBeenCalledWith('subvisC3', { + expect(visualizationMap.visC.switchVisualizationType).toHaveBeenCalledWith('subvisC3', { type: 'subvisC1', }); expect(datasourceMap.testDatasource.removeLayer).not.toHaveBeenCalled(); }); it('should switch to the updated datasource state', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a', 'b']); - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -684,14 +660,14 @@ describe('chart_switch', () => { keptLayerIds: [], }, ]); - const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { visualization: { activeId: 'visA', @@ -718,20 +694,21 @@ describe('chart_switch', () => { }); it('should ensure the new visualization has the proper subtype', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const switchVisualizationType = jest.fn( (visualizationType, state) => `${state} ${visualizationType}` ); - visualizations.visB.switchVisualizationType = switchVisualizationType; - + visualizationMap.visB.switchVisualizationType = switchVisualizationType; + const datasourceMap = mockDatasourceMap(); const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { visualization: { activeId: 'visA', @@ -758,16 +735,16 @@ describe('chart_switch', () => { }); it('should use the suggestion that matches the subtype', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const switchVisualizationType = jest.fn(); - visualizations.visC.switchVisualizationType = switchVisualizationType; - + visualizationMap.visC.switchVisualizationType = switchVisualizationType; + const datasourceMap = mockDatasourceMap(); const { instance } = await mountWithProvider( , { preloadedState: { @@ -787,11 +764,12 @@ describe('chart_switch', () => { }); it('should show all visualization types', async () => { + const datasourceMap = mockDatasourceMap(); const { instance } = await mountWithProvider( , { preloadedState: { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index a5ba12941cf7f5..250359822e068d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -31,8 +31,8 @@ import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_he import { trackUiEvent } from '../../../lens_ui_telemetry'; import { ToolbarButton } from '../../../../../../../src/plugins/kibana_react/public'; import { - updateLayer, - updateVisualizationState, + insertLayer, + removeLayers, useLensDispatch, useLensSelector, VisualizationState, @@ -120,41 +120,6 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { const visualization = useLensSelector(selectVisualization); const datasourceStates = useLensSelector(selectDatasourceStates); - function removeLayers(layerIds: string[]) { - const activeVisualization = - visualization.activeId && props.visualizationMap[visualization.activeId]; - if (activeVisualization && activeVisualization.removeLayer && visualization.state) { - dispatchLens( - updateVisualizationState({ - visualizationId: activeVisualization.id, - updater: layerIds.reduce( - (acc, layerId) => - activeVisualization.removeLayer ? activeVisualization.removeLayer(acc, layerId) : acc, - visualization.state - ), - }) - ); - } - layerIds.forEach((layerId) => { - const [layerDatasourceId] = - Object.entries(props.datasourceMap).find(([datasourceId, datasource]) => { - return ( - datasourceStates[datasourceId] && - datasource.getLayers(datasourceStates[datasourceId].state).includes(layerId) - ); - }) ?? []; - if (layerDatasourceId) { - dispatchLens( - updateLayer({ - layerId, - datasourceId: layerDatasourceId, - updater: props.datasourceMap[layerDatasourceId].removeLayer, - }) - ); - } - }); - } - const commitSelection = (selection: VisualizationSelection) => { setFlyoutOpen(false); @@ -173,7 +138,12 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { (!selection.datasourceId && !selection.sameDatasources) || selection.dataLoss === 'everything' ) { - removeLayers(Object.keys(props.framePublicAPI.datasourceLayers)); + dispatchLens( + removeLayers({ + visualizationId: visualization.activeId, + layerIds: Object.keys(props.framePublicAPI.datasourceLayers), + }) + ); } }; @@ -231,13 +201,11 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { function addNewLayer() { const newLayerId = generateId(); dispatchLens( - updateLayer({ + insertLayer({ datasourceId: activeDatasourceId!, layerId: newLayerId, - updater: props.datasourceMap[activeDatasourceId!].insertLayer, }) ); - return newLayerId; } @@ -368,6 +336,7 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { {v.selection.dataLoss !== 'nothing' ? ( 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 a03c9292d2da84..4b98b2842a01b9 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 @@ -52,7 +52,7 @@ import { DefaultInspectorAdapters } from '../../../../../../../src/plugins/expre import { onActiveDataChange, useLensDispatch, - updateVisualizationState, + editVisualizationAction, updateDatasourceState, setSaveable, useLensSelector, @@ -246,9 +246,9 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ } if (isLensEditEvent(event) && activeVisualization?.onEditAction) { dispatchLens( - updateVisualizationState({ + editVisualizationAction({ visualizationId: activeVisualization.id, - updater: (oldState: unknown) => activeVisualization.onEditAction!(oldState, event), + event, }) ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss index e57455e5bd5abf..9a87f1ba46e94a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss @@ -10,6 +10,7 @@ height: 100%; .lnsWorkspacePanelWrapper__pageContentBody { + @include euiBottomShadowMedium; @include euiScrollBar; flex-grow: 1; display: flex; @@ -19,14 +20,6 @@ background: $euiColorEmptyShade; height: 100%; - @include kbnThemeStyle('v7') { - @include euiBottomShadowSmall; - } - - @include kbnThemeStyle('v8') { - @include euiBottomShadowMedium; - } - > * { flex: 1 1 100%; display: flex; @@ -42,13 +35,7 @@ } .lnsWorkspacePanel__dragDrop { - @include kbnThemeStyle('v7') { - border: $euiBorderThin; - } - - @include kbnThemeStyle('v8') { - border: $euiBorderWidthThin solid transparent; - } + border: $euiBorderWidthThin solid transparent; &.lnsDragDrop-isDropTarget { @include lnsDroppable; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx index d8959e714d16e5..f13ecb78593d9c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx @@ -54,8 +54,7 @@ export function WorkspacePanelWrapper({ dispatchLens( updateVisualizationState({ visualizationId: activeVisualization.id, - updater: newState, - clearStagedPreview: false, + newState, }) ); }, diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 9be07a4f44dcdf..fb7cefb22d1753 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -58,6 +58,6 @@ export type { } from './indexpattern_datasource/types'; export type { LensEmbeddableInput } from './embeddable'; -export { LensPublicStart } from './plugin'; +export type { LensPublicStart } from './plugin'; export const plugin = () => new LensPlugin(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.scss b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.scss index 8b509e9c39b7b2..d4c91f80317fda 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.scss +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.scss @@ -54,9 +54,3 @@ padding-top: 0; padding-bottom: 0; } - -.lnsIndexPatternDimensionEditor__warning { - @include kbnThemeStyle('v7') { - border: none; - } -} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 7aa972612c82f1..5d8ba778e30d14 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -165,7 +165,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { setState((s) => ({ ...s, isLoading: true })); core.http - .post(`/api/lens/index_stats/${indexPattern.id}/field`, { + .post>(`/api/lens/index_stats/${indexPattern.id}/field`, { body: JSON.stringify({ dslQuery: esQuery.buildEsQuery( indexPattern, @@ -178,7 +178,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { fieldName: field.name, }), }) - .then((results: FieldStatsResponse) => { + .then((results) => { setState((s) => ({ ...s, isLoading: false, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index d90dc83bc1fef5..bdcc0e621cc368 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -58,7 +58,8 @@ import { GeoFieldWorkspacePanel } from '../editor_frame_service/editor_frame/wor import { DraggingIdentifier } from '../drag_drop'; import { getStateTimeShiftWarningMessages } from './time_shift_utils'; -export { OperationType, IndexPatternColumn, deleteColumn } from './operations'; +export type { OperationType, IndexPatternColumn } from './operations'; +export { deleteColumn } from './operations'; export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: string): Operation { const { dataType, label, isBucketed, scale } = column; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 9315b61adcc543..5df02482a27456 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -1952,6 +1952,64 @@ describe('IndexPattern Data Source suggestions', () => { suggestions.forEach((suggestion) => expect(suggestion.table.columns.length).toBe(1)); }); + it("should not propose an over time suggestion if there's a top values aggregation with an high size", () => { + const initialState = testInitialState(); + (initialState.layers.first.columns.col1 as { params: { size: number } }).params!.size = 6; + const suggestions = getDatasourceSuggestionsFromCurrentState({ + ...initialState, + indexPatterns: { 1: { ...initialState.indexPatterns['1'], timeFieldName: undefined } }, + }); + suggestions.forEach((suggestion) => expect(suggestion.table.columns.length).toBe(1)); + }); + + it('should not propose an over time suggestion if there are multiple bucket dimensions', () => { + const initialState = testInitialState(); + const state: IndexPatternPrivateState = { + ...initialState, + layers: { + first: { + indexPatternId: '1', + columnOrder: ['col1', 'col2', 'col3'], + columns: { + ...initialState.layers.first.columns, + col2: { + label: 'My Op', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + scale: 'ratio', + }, + col3: { + label: 'My Op', + customLabel: true, + dataType: 'string', + isBucketed: true, + + // Private + operationType: 'terms', + sourceField: 'dest', + params: { + size: 5, + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + }, + }, + }, + }, + }, + }; + const suggestions = getDatasourceSuggestionsFromCurrentState({ + ...state, + indexPatterns: { 1: { ...state.indexPatterns['1'], timeFieldName: undefined } }, + }); + suggestions.forEach((suggestion) => { + const firstBucket = suggestion.table.columns.find(({ columnId }) => columnId === 'col1'); + expect(firstBucket?.operation).not.toBe('date'); + }); + }); + it('returns simplified versions of table with more than 2 columns', () => { const initialState = testInitialState(); const fields = [ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index d3c292b7e019be..8b940ec1f05af7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -21,6 +21,7 @@ import { getExistingColumnGroups, isReferenced, getReferencedColumnIds, + hasTermsWithManyBuckets, } from './operations'; import { hasField } from './utils'; import type { @@ -424,7 +425,7 @@ export function getDatasourceSuggestionsFromCurrentState( ); if (!references.length && metrics.length && buckets.length === 0) { - if (timeField) { + if (timeField && buckets.length < 1 && !hasTermsWithManyBuckets(layer)) { // suggest current metric over time if there is a default time field suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId, timeField)); } @@ -436,7 +437,13 @@ export function getDatasourceSuggestionsFromCurrentState( // base range intervals are of number dataType. // Custom range/intervals have a different dataType so they still receive the Over Time suggestion - if (!timeDimension && timeField && !hasNumericDimension) { + if ( + !timeDimension && + timeField && + buckets.length < 2 && + !hasNumericDimension && + !hasTermsWithManyBuckets(layer) + ) { // suggest current configuration over time if there is a default time field // and no time dimension yet suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId, timeField)); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts index a7741bc60d6469..1ffbdea00b7758 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts @@ -5,17 +5,23 @@ * 2.0. */ -export { counterRateOperation, CounterRateIndexPatternColumn } from './counter_rate'; -export { cumulativeSumOperation, CumulativeSumIndexPatternColumn } from './cumulative_sum'; -export { derivativeOperation, DerivativeIndexPatternColumn } from './differences'; -export { movingAverageOperation, MovingAverageIndexPatternColumn } from './moving_average'; +export type { CounterRateIndexPatternColumn } from './counter_rate'; +export { counterRateOperation } from './counter_rate'; +export type { CumulativeSumIndexPatternColumn } from './cumulative_sum'; +export { cumulativeSumOperation } from './cumulative_sum'; +export type { DerivativeIndexPatternColumn } from './differences'; +export { derivativeOperation } from './differences'; +export type { MovingAverageIndexPatternColumn } from './moving_average'; +export { movingAverageOperation } from './moving_average'; +export type { + OverallSumIndexPatternColumn, + OverallMinIndexPatternColumn, + OverallMaxIndexPatternColumn, + OverallAverageIndexPatternColumn, +} from './overall_metric'; export { overallSumOperation, - OverallSumIndexPatternColumn, overallMinOperation, - OverallMinIndexPatternColumn, overallMaxOperation, - OverallMaxIndexPatternColumn, overallAverageOperation, - OverallAverageIndexPatternColumn, } from './overall_metric'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/index.ts index bafde0d37b3e9f..5ff0c4e2d4bd7d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/index.ts @@ -5,6 +5,8 @@ * 2.0. */ -export { formulaOperation, FormulaIndexPatternColumn } from './formula'; +export type { FormulaIndexPatternColumn } from './formula'; +export { formulaOperation } from './formula'; export { regenerateLayerFromAst } from './parse'; -export { mathOperation, MathIndexPatternColumn } from './math'; +export type { MathIndexPatternColumn } from './math'; +export { mathOperation } from './math'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 0212c73f468790..392b2b135ca22d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -93,21 +93,21 @@ export type IndexPatternColumn = export type FieldBasedIndexPatternColumn = Extract; -export { IncompleteColumn } from './column_types'; +export type { IncompleteColumn } from './column_types'; -export { TermsIndexPatternColumn } from './terms'; -export { FiltersIndexPatternColumn } from './filters'; -export { CardinalityIndexPatternColumn } from './cardinality'; -export { PercentileIndexPatternColumn } from './percentile'; -export { +export type { TermsIndexPatternColumn } from './terms'; +export type { FiltersIndexPatternColumn } from './filters'; +export type { CardinalityIndexPatternColumn } from './cardinality'; +export type { PercentileIndexPatternColumn } from './percentile'; +export type { MinIndexPatternColumn, AvgIndexPatternColumn, SumIndexPatternColumn, MaxIndexPatternColumn, MedianIndexPatternColumn, } from './metrics'; -export { DateHistogramIndexPatternColumn } from './date_histogram'; -export { +export type { DateHistogramIndexPatternColumn } from './date_histogram'; +export type { CumulativeSumIndexPatternColumn, CounterRateIndexPatternColumn, DerivativeIndexPatternColumn, @@ -117,11 +117,11 @@ export { OverallMaxIndexPatternColumn, OverallAverageIndexPatternColumn, } from './calculations'; -export { CountIndexPatternColumn } from './count'; -export { LastValueIndexPatternColumn } from './last_value'; -export { RangeIndexPatternColumn } from './ranges'; -export { FormulaIndexPatternColumn, MathIndexPatternColumn } from './formula'; -export { StaticValueIndexPatternColumn } from './static_value'; +export type { CountIndexPatternColumn } from './count'; +export type { LastValueIndexPatternColumn } from './last_value'; +export type { RangeIndexPatternColumn } from './ranges'; +export type { FormulaIndexPatternColumn, MathIndexPatternColumn } from './formula'; +export type { StaticValueIndexPatternColumn } from './static_value'; // List of all operation definitions registered to this data source. // If you want to implement a new operation, add the definition to this array and diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts index 7899ce9efcedf0..86add22b2b8ced 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts @@ -8,7 +8,7 @@ export * from './operations'; export * from './layer_helpers'; export * from './time_scale_utils'; -export { +export type { OperationType, IndexPatternColumn, FieldBasedIndexPatternColumn, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 77a2b334a9e20f..3dc0677f3b9b6b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -15,6 +15,7 @@ import { deleteColumn, updateLayerIndexPattern, getErrorMessages, + hasTermsWithManyBuckets, } from './layer_helpers'; import { operationDefinitionMap, OperationType } from '../operations'; import { TermsIndexPatternColumn } from './definitions/terms'; @@ -3005,4 +3006,79 @@ describe('state_helpers', () => { ); }); }); + + describe('hasTermsWithManyBuckets', () => { + it('should return false for a bucketed non terms operation', () => { + const layer: IndexPatternLayer = { + columnOrder: ['col1'], + columns: { + col1: { + dataType: 'date', + isBucketed: true, + label: '', + operationType: 'date_histogram', + sourceField: 'fieldD', + params: { + interval: 'd', + }, + }, + }, + indexPatternId: 'original', + }; + + expect(hasTermsWithManyBuckets(layer)).toBeFalsy(); + }); + + it('should return false if all terms operation have a lower size', () => { + const layer: IndexPatternLayer = { + columnOrder: ['col1'], + columns: { + col1: { + label: 'My Op', + customLabel: true, + dataType: 'string', + isBucketed: true, + + // Private + operationType: 'terms', + sourceField: 'dest', + params: { + size: 5, + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + }, + }, + }, + indexPatternId: 'original', + }; + + expect(hasTermsWithManyBuckets(layer)).toBeFalsy(); + }); + + it('should return true if the size is high', () => { + const layer: IndexPatternLayer = { + columnOrder: ['col1'], + columns: { + col1: { + label: 'My Op', + customLabel: true, + dataType: 'string', + isBucketed: true, + + // Private + operationType: 'terms', + sourceField: 'dest', + params: { + size: 15, + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + }, + }, + }, + indexPatternId: 'original', + }; + + expect(hasTermsWithManyBuckets(layer)).toBeTruthy(); + }); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 1ae17ca9ec2b2d..67546ca6009cf8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -1369,6 +1369,17 @@ export function getReferencedColumnIds(layer: IndexPatternLayer, columnId: strin return referencedIds; } +export function hasTermsWithManyBuckets(layer: IndexPatternLayer): boolean { + return layer.columnOrder.some((columnId) => { + const column = layer.columns[columnId]; + if (column) { + return ( + column.isBucketed && column.params && 'size' in column.params && column.params.size > 5 + ); + } + }); +} + export function isOperationAllowedAsReference({ operationType, validation, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 588b2595202729..515693b4dd5c8c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -11,7 +11,7 @@ import type { FieldSpec } from '../../../../../src/plugins/data/common'; import type { DragDropIdentifier } from '../drag_drop/providers'; import type { FieldFormatParams } from '../../../../../src/plugins/field_formats/common'; -export { +export type { FieldBasedIndexPatternColumn, IndexPatternColumn, OperationType, diff --git a/x-pack/plugins/lens/public/mocks/index.ts b/x-pack/plugins/lens/public/mocks/index.ts index 2dd32a1679f1b3..e9f03cde2642ed 100644 --- a/x-pack/plugins/lens/public/mocks/index.ts +++ b/x-pack/plugins/lens/public/mocks/index.ts @@ -16,12 +16,12 @@ export { datasourceMap, mockDatasourceMap, createMockDatasource } from './dataso export type { DatasourceMock } from './datasource_mock'; export { createExpressionRendererMock } from './expression_renderer_mock'; export { defaultDoc, exactMatchDoc, makeDefaultServices } from './services_mock'; +export type { MountStoreProps } from './store_mocks'; export { mockStoreDeps, mockDatasourceStates, defaultState, makeLensStore, - MountStoreProps, mountWithProvider, } from './store_mocks'; export { lensPluginMock } from './lens_plugin_mock'; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index f268d6816910e0..9ffddaa1a135b7 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -6,7 +6,8 @@ */ export * from './empty_placeholder'; -export { ToolbarPopoverProps, ToolbarPopover } from './toolbar_popover'; +export type { ToolbarPopoverProps } from './toolbar_popover'; +export { ToolbarPopover } from './toolbar_popover'; export { LegendSettingsPopover } from './legend_settings_popover'; export { PalettePicker } from './palette_picker'; export { TooltipWrapper } from './tooltip_wrapper'; diff --git a/x-pack/plugins/lens/public/state_management/index.ts b/x-pack/plugins/lens/public/state_management/index.ts index 0aa7185931c5af..5f3b60d95d77d5 100644 --- a/x-pack/plugins/lens/public/state_management/index.ts +++ b/x-pack/plugins/lens/public/state_management/index.ts @@ -25,13 +25,18 @@ export const { updateState, updateDatasourceState, updateVisualizationState, - updateLayer, + insertLayer, switchVisualization, rollbackSuggestion, submitSuggestion, switchDatasource, setToggleFullscreen, initEmpty, + editVisualizationAction, + removeLayers, + removeOrClearLayer, + addLayer, + setLayerDefaultDimension, } = lensActions; export const makeConfigureStore = ( diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts index 7d88e6ceb616c7..85061f36ce35e7 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts @@ -13,8 +13,13 @@ import { updateState, updateDatasourceState, updateVisualizationState, + removeOrClearLayer, + addLayer, + LensRootStore, } from '.'; -import { makeLensStore, defaultState } from '../mocks'; +import { layerTypes } from '../../common'; +import { makeLensStore, defaultState, mockStoreDeps } from '../mocks'; +import { DatasourceMap, VisualizationMap } from '../types'; describe('lensSlice', () => { const { store } = makeLensStore({}); @@ -31,7 +36,7 @@ describe('lensSlice', () => { it('updateState: updates state with updater', () => { const customUpdater = jest.fn((state) => ({ ...state, query: customQuery })); - store.dispatch(updateState({ subType: 'UPDATE', updater: customUpdater })); + store.dispatch(updateState({ updater: customUpdater })); const changedState = store.getState().lens; expect(changedState).toEqual({ ...defaultState, query: customQuery }); }); @@ -40,7 +45,7 @@ describe('lensSlice', () => { store.dispatch( updateVisualizationState({ visualizationId: 'testVis', - updater: newVisState, + newState: newVisState, }) ); @@ -117,7 +122,7 @@ describe('lensSlice', () => { expect(store.getState().lens.datasourceStates.testDatasource2.isLoading).toEqual(true); }); - it('not initialize already initialized datasource on switch', () => { + it('should not initialize already initialized datasource on switch', () => { const datasource2State = {}; const { store: customStore } = makeLensStore({ preloadedState: { @@ -146,5 +151,109 @@ describe('lensSlice', () => { datasource2State ); }); + + describe('adding or removing layer', () => { + const testDatasource = (datasourceId: string) => { + return { + id: datasourceId, + getPublicAPI: () => ({ + datasourceId: 'testDatasource', + getOperationForColumnId: jest.fn(), + getTableSpec: jest.fn(), + }), + getLayers: () => ['layer1'], + clearLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).map((id: string) => + id === layerId ? `${datasourceId}_clear_${layerId}` : id + ), + removeLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).filter((id: string) => id !== layerId), + insertLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], + }; + }; + const datasourceStates = { + testDatasource: { + isLoading: false, + state: ['layer1'], + }, + testDatasource2: { + isLoading: false, + state: ['layer2'], + }, + }; + const datasourceMap = { + testDatasource: testDatasource('testDatasource'), + testDatasource2: testDatasource('testDatasource2'), + }; + const visualizationMap = { + testVis: { + clearLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).map((id: string) => + id === layerId ? `vis_clear_${layerId}` : id + ), + removeLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).filter((id: string) => id !== layerId), + getLayerIds: (layerIds: unknown) => layerIds as string[], + appendLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], + getSupportedLayers: jest.fn(() => [{ type: layerTypes.DATA, label: 'Data Layer' }]), + }, + }; + + let customStore: LensRootStore; + beforeEach(() => { + customStore = makeLensStore({ + preloadedState: { + activeDatasourceId: 'testDatasource', + datasourceStates, + visualization: { + activeId: 'testVis', + state: ['layer1', 'layer2'], + }, + stagedPreview: { + visualization: { + activeId: 'testVis', + state: ['layer1', 'layer2'], + }, + datasourceStates, + }, + }, + storeDeps: mockStoreDeps({ + visualizationMap: visualizationMap as unknown as VisualizationMap, + datasourceMap: datasourceMap as unknown as DatasourceMap, + }), + }).store; + }); + + it('addLayer: should add the layer to the datasource and visualization', () => { + customStore.dispatch( + addLayer({ + layerId: 'foo', + layerType: layerTypes.DATA, + }) + ); + const state = customStore.getState().lens; + + expect(state.visualization.state).toEqual(['layer1', 'layer2', 'foo']); + expect(state.datasourceStates.testDatasource.state).toEqual(['layer1', 'foo']); + expect(state.datasourceStates.testDatasource2.state).toEqual(['layer2']); + expect(state.stagedPreview).not.toBeDefined(); + }); + + it('removeLayer: should remove the layer if it is not the only layer', () => { + customStore.dispatch( + removeOrClearLayer({ + visualizationId: 'testVis', + layerId: 'layer1', + layerIds: ['layer1', 'layer2'], + }) + ); + const state = customStore.getState().lens; + + expect(state.visualization.state).toEqual(['layer2']); + expect(state.datasourceStates.testDatasource.state).toEqual([]); + expect(state.datasourceStates.testDatasource2.state).toEqual(['layer2']); + expect(state.stagedPreview).not.toBeDefined(); + }); + }); }); }); diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index df178cadf6c300..af9897581fcf4b 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -7,16 +7,22 @@ import { createAction, createReducer, current, PayloadAction } from '@reduxjs/toolkit'; import { VisualizeFieldContext } from 'src/plugins/ui_actions/public'; +import { mapValues } from 'lodash'; import { History } from 'history'; import { LensEmbeddableInput } from '..'; +import { getDatasourceLayers } from '../editor_frame_service/editor_frame'; import { TableInspectorAdapter } from '../editor_frame_service/types'; -import { getInitialDatasourceId, getResolvedDateRange } from '../utils'; -import { LensAppState, LensStoreDeps } from './types'; +import { getInitialDatasourceId, getResolvedDateRange, getRemoveOperation } from '../utils'; +import { LensAppState, LensStoreDeps, VisualizationState } from './types'; +import { Datasource, Visualization } from '../types'; import { generateId } from '../id_generator'; +import type { LayerType } from '../../common/types'; +import { getLayerType } from '../editor_frame_service/editor_frame/config_panel/add_layer'; import { getVisualizeFieldSuggestions, Suggestion, } from '../editor_frame_service/editor_frame/suggestion_helpers'; +import { FramePublicAPI, LensEditContextMapping, LensEditEvent } from '../types'; export const initialState: LensAppState = { persistedDoc: undefined, @@ -80,7 +86,6 @@ export const setState = createAction>('lens/setState'); export const onActiveDataChange = createAction('lens/onActiveDataChange'); export const setSaveable = createAction('lens/setSaveable'); export const updateState = createAction<{ - subType: string; updater: (prevState: LensAppState) => LensAppState; }>('lens/updateState'); export const updateDatasourceState = createAction<{ @@ -90,15 +95,13 @@ export const updateDatasourceState = createAction<{ }>('lens/updateDatasourceState'); export const updateVisualizationState = createAction<{ visualizationId: string; - updater: unknown; - clearStagedPreview?: boolean; + newState: unknown; }>('lens/updateVisualizationState'); -export const updateLayer = createAction<{ +export const insertLayer = createAction<{ layerId: string; datasourceId: string; - updater: (state: unknown, layerId: string) => unknown; -}>('lens/updateLayer'); +}>('lens/insertLayer'); export const switchVisualization = createAction<{ suggestion: { @@ -133,6 +136,29 @@ export const initEmpty = createAction( return { payload: { layerId: generateId(), newState, initialContext } }; } ); +export const editVisualizationAction = createAction<{ + visualizationId: string; + event: LensEditEvent; +}>('lens/editVisualizationAction'); +export const removeLayers = createAction<{ + visualizationId: VisualizationState['activeId']; + layerIds: string[]; +}>('lens/removeLayers'); +export const removeOrClearLayer = createAction<{ + visualizationId: string; + layerId: string; + layerIds: string[]; +}>('lens/removeOrClearLayer'); +export const addLayer = createAction<{ + layerId: string; + layerType: LayerType; +}>('lens/addLayer'); + +export const setLayerDefaultDimension = createAction<{ + layerId: string; + columnId: string; + groupId: string; +}>('lens/setLayerDefaultDimension'); export const lensActions = { setState, @@ -141,7 +167,7 @@ export const lensActions = { updateState, updateDatasourceState, updateVisualizationState, - updateLayer, + insertLayer, switchVisualization, rollbackSuggestion, setToggleFullscreen, @@ -150,6 +176,11 @@ export const lensActions = { navigateAway, loadInitial, initEmpty, + editVisualizationAction, + removeLayers, + removeOrClearLayer, + addLayer, + setLayerDefaultDimension, }; export const makeLensReducer = (storeDeps: LensStoreDeps) => { @@ -175,14 +206,58 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }, [updateState.type]: ( state, - action: { + { + payload: { updater }, + }: { payload: { - subType: string; updater: (prevState: LensAppState) => LensAppState; }; } ) => { - return action.payload.updater(current(state) as LensAppState); + const newState = updater(current(state) as LensAppState); + return { + ...newState, + stagedPreview: undefined, + }; + }, + [removeOrClearLayer.type]: ( + state, + { + payload: { visualizationId, layerId, layerIds }, + }: { + payload: { + visualizationId: string; + layerId: string; + layerIds: string[]; + }; + } + ) => { + const activeVisualization = visualizationMap[visualizationId]; + const isOnlyLayer = + getRemoveOperation( + activeVisualization, + state.visualization.state, + layerId, + layerIds.length + ) === 'clear'; + + state.datasourceStates = mapValues( + state.datasourceStates, + (datasourceState, datasourceId) => { + const datasource = datasourceMap[datasourceId!]; + return { + ...datasourceState, + state: isOnlyLayer + ? datasource.clearLayer(datasourceState.state, layerId) + : datasource.removeLayer(datasourceState.state, layerId), + }; + } + ); + state.stagedPreview = undefined; + state.visualization.state = + isOnlyLayer || !activeVisualization.removeLayer + ? activeVisualization.clearLayer(state.visualization.state, layerId) + : activeVisualization.removeLayer(state.visualization.state, layerId); }, [updateDatasourceState.type]: ( state, @@ -218,8 +293,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }: { payload: { visualizationId: string; - updater: unknown | ((state: unknown) => unknown); - clearStagedPreview?: boolean; + newState: unknown; }; } ) => { @@ -236,37 +310,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { ...state, visualization: { ...state.visualization, - state: - typeof payload.updater === 'function' - ? payload.updater(current(state.visualization.state)) - : payload.updater, - }, - stagedPreview: payload.clearStagedPreview ? undefined : state.stagedPreview, - }; - }, - [updateLayer.type]: ( - state, - { - payload, - }: { - payload: { - layerId: string; - datasourceId: string; - updater: (state: unknown, layerId: string) => unknown; - }; - } - ) => { - return { - ...state, - datasourceStates: { - ...state.datasourceStates, - [payload.datasourceId]: { - ...state.datasourceStates[payload.datasourceId], - state: payload.updater( - current(state).datasourceStates[payload.datasourceId].state, - payload.layerId - ), - }, + state: payload.newState, }, }; }, @@ -433,5 +477,248 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { } return newState; }, + [editVisualizationAction.type]: ( + state, + { + payload, + }: { + payload: { + visualizationId: string; + event: LensEditEvent; + }; + } + ) => { + if (!state.visualization.activeId) { + throw new Error('Invariant: visualization state got updated without active visualization'); + } + // This is a safeguard that prevents us from accidentally updating the + // wrong visualization. This occurs in some cases due to the uncoordinated + // way we manage state across plugins. + if (state.visualization.activeId !== payload.visualizationId) { + return state; + } + const activeVisualization = visualizationMap[payload.visualizationId]; + if (activeVisualization?.onEditAction) { + state.visualization.state = activeVisualization.onEditAction( + state.visualization.state, + payload.event + ); + } + }, + [insertLayer.type]: ( + state, + { + payload, + }: { + payload: { + layerId: string; + datasourceId: string; + }; + } + ) => { + const updater = datasourceMap[payload.datasourceId].insertLayer; + return { + ...state, + datasourceStates: { + ...state.datasourceStates, + [payload.datasourceId]: { + ...state.datasourceStates[payload.datasourceId], + state: updater( + current(state).datasourceStates[payload.datasourceId].state, + payload.layerId + ), + }, + }, + }; + }, + [removeLayers.type]: ( + state, + { + payload: { visualizationId, layerIds }, + }: { + payload: { + visualizationId: VisualizationState['activeId']; + layerIds: string[]; + }; + } + ) => { + if (!state.visualization.activeId) { + throw new Error('Invariant: visualization state got updated without active visualization'); + } + + const activeVisualization = visualizationId && visualizationMap[visualizationId]; + + // This is a safeguard that prevents us from accidentally updating the + // wrong visualization. This occurs in some cases due to the uncoordinated + // way we manage state across plugins. + if ( + state.visualization.activeId === visualizationId && + activeVisualization && + activeVisualization.removeLayer && + state.visualization.state + ) { + const updater = layerIds.reduce( + (acc, layerId) => + activeVisualization.removeLayer ? activeVisualization.removeLayer(acc, layerId) : acc, + state.visualization.state + ); + + state.visualization.state = + typeof updater === 'function' ? updater(current(state.visualization.state)) : updater; + } + layerIds.forEach((layerId) => { + const [layerDatasourceId] = + Object.entries(datasourceMap).find(([datasourceId, datasource]) => { + return ( + state.datasourceStates[datasourceId] && + datasource.getLayers(state.datasourceStates[datasourceId].state).includes(layerId) + ); + }) ?? []; + if (layerDatasourceId) { + state.datasourceStates[layerDatasourceId].state = datasourceMap[ + layerDatasourceId + ].removeLayer(current(state).datasourceStates[layerDatasourceId].state, layerId); + } + }); + }, + + [addLayer.type]: ( + state, + { + payload: { layerId, layerType }, + }: { + payload: { + layerId: string; + layerType: LayerType; + }; + } + ) => { + if (!state.activeDatasourceId || !state.visualization.activeId) { + return state; + } + + const activeDatasource = datasourceMap[state.activeDatasourceId]; + const activeVisualization = visualizationMap[state.visualization.activeId]; + + const datasourceState = activeDatasource.insertLayer( + state.datasourceStates[state.activeDatasourceId].state, + layerId + ); + + const visualizationState = activeVisualization.appendLayer!( + state.visualization.state, + layerId, + layerType + ); + + const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ + datasourceState, + visualizationState, + framePublicAPI: { + // any better idea to avoid `as`? + activeData: state.activeData as TableInspectorAdapter, + datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), + }, + activeVisualization, + activeDatasource, + layerId, + layerType, + }); + + state.visualization.state = activeVisualizationState; + state.datasourceStates[state.activeDatasourceId].state = activeDatasourceState; + state.stagedPreview = undefined; + }, + [setLayerDefaultDimension.type]: ( + state, + { + payload: { layerId, columnId, groupId }, + }: { + payload: { + layerId: string; + columnId: string; + groupId: string; + }; + } + ) => { + if (!state.activeDatasourceId || !state.visualization.activeId) { + return state; + } + + const activeDatasource = datasourceMap[state.activeDatasourceId]; + const activeVisualization = visualizationMap[state.visualization.activeId]; + const layerType = getLayerType(activeVisualization, state.visualization.state, layerId); + const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ + datasourceState: state.datasourceStates[state.activeDatasourceId].state, + visualizationState: state.visualization.state, + framePublicAPI: { + // any better idea to avoid `as`? + activeData: state.activeData as TableInspectorAdapter, + datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), + }, + activeVisualization, + activeDatasource, + layerId, + layerType, + columnId, + groupId, + }); + + state.visualization.state = activeVisualizationState; + state.datasourceStates[state.activeDatasourceId].state = activeDatasourceState; + }, }); }; + +function addInitialValueIfAvailable({ + visualizationState, + datasourceState, + activeVisualization, + activeDatasource, + framePublicAPI, + layerType, + layerId, + columnId, + groupId, +}: { + framePublicAPI: FramePublicAPI; + visualizationState: unknown; + datasourceState: unknown; + activeDatasource: Datasource; + activeVisualization: Visualization; + layerId: string; + layerType: string; + columnId?: string; + groupId?: string; +}) { + const layerInfo = activeVisualization + .getSupportedLayers(visualizationState, framePublicAPI) + .find(({ type }) => type === layerType); + + if (layerInfo?.initialDimensions && activeDatasource?.initializeDimension) { + const info = groupId + ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) + : // pick the first available one if not passed + layerInfo.initialDimensions[0]; + + if (info) { + return { + activeDatasourceState: activeDatasource.initializeDimension(datasourceState, layerId, { + ...info, + columnId: columnId || info.columnId, + }), + activeVisualizationState: activeVisualization.setDimension({ + groupId: info.groupId, + layerId, + columnId: columnId || info.columnId, + prevState: visualizationState, + frame: framePublicAPI, + }), + }; + } + } + return { + activeDatasourceState: datasourceState, + activeVisualizationState: visualizationState, + }; +} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index e207f2938dd3cf..975e44f7039598 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -776,7 +776,7 @@ export interface LensBrushEvent { } // Use same technique as TriggerContext -interface LensEditContextMapping { +export interface LensEditContextMapping { [LENS_EDIT_SORT_ACTION]: LensSortActionData; [LENS_EDIT_RESIZE_ACTION]: LensResizeActionData; [LENS_TOGGLE_ACTION]: LensToggleActionData; diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 993be9a06a2d99..921cc8fb364a26 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -16,8 +16,8 @@ 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 } from './types'; -import type { DatasourceStates } from './state_management'; +import type { Datasource, DatasourceMap, Visualization } from './types'; +import type { DatasourceStates, VisualizationState } from './state_management'; export function getVisualizeGeoFieldMessage(fieldType: string) { return i18n.translate('xpack.lens.visualizeGeoFieldMessage', { @@ -94,3 +94,16 @@ export async function getIndexPatternsObjects( // return also the rejected ids in case we want to show something later on return { indexPatterns: fullfilled.map((response) => response.value), rejectedIds }; } + +export function getRemoveOperation( + activeVisualization: Visualization, + visualizationState: VisualizationState['state'], + layerId: string, + layerCount: number +) { + if (activeVisualization.getRemoveOperation) { + return activeVisualization.getRemoveOperation(visualizationState, layerId); + } + // fallback to generic count check + return layerCount === 1 ? 'clear' : 'remove'; +} diff --git a/x-pack/plugins/lens/public/visualization_container.scss b/x-pack/plugins/lens/public/visualization_container.scss index c3c15eff3819ac..9fc16a0afc3650 100644 --- a/x-pack/plugins/lens/public/visualization_container.scss +++ b/x-pack/plugins/lens/public/visualization_container.scss @@ -1,6 +1,6 @@ .lnsVisualizationContainer { @include euiScrollBar; - overflow: auto; + overflow: auto hidden; user-select: text; } diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index 6601167e1f83a6..e2566aa22ce9e2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -46,7 +46,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` + isHistogram && + (seriesType.includes('stacked') || !splitAccessor) && + (seriesType.includes('stacked') || + !seriesType.includes('bar') || + !chartHasMoreThanOneBarSeries) + ); - const gridLineStyle: RecursivePartial = shouldUseNewTimeAxis - ? { - visible: gridlinesVisibilitySettings?.x, - strokeWidth: 0.1, - stroke: darkMode ? 'white' : 'black', - } - : { - visible: gridlinesVisibilitySettings?.x, - strokeWidth: 2, - }; + const shouldUseNewTimeAxis = + isTimeViz && isHistogramModeEnabled && !useLegacyTimeAxis && !shouldRotate; + + const gridLineStyle = { + visible: gridlinesVisibilitySettings?.x, + strokeWidth: 1, + }; const xAxisStyle: RecursivePartial = shouldUseNewTimeAxis ? { + ...MULTILAYER_TIME_AXIS_STYLE, tickLabel: { + ...MULTILAYER_TIME_AXIS_STYLE.tickLabel, visible: Boolean(tickLabelsVisibilitySettings?.x), - rotation: 0, // rotation is disabled on new time axis - fontSize: 11, - padding: - referenceLinePaddings.bottom != null ? { inner: referenceLinePaddings.bottom } : 0, - alignment: { - vertical: Position.Bottom, - horizontal: Position.Left, - }, - offset: { - x: 1.5, - y: 0, - }, - }, - axisLine: { - stroke: darkMode ? 'lightgray' : 'darkgray', - strokeWidth: 1, }, tickLine: { - size: 12, - strokeWidth: 0.15, - stroke: darkMode ? 'white' : 'black', - padding: -10, + ...MULTILAYER_TIME_AXIS_STYLE.tickLine, visible: Boolean(tickLabelsVisibilitySettings?.x), }, axisTitle: { visible: axisTitlesVisibilitySettings.x, - padding: - !tickLabelsVisibilitySettings?.x && referenceLinePaddings.bottom != null - ? { inner: referenceLinePaddings.bottom } - : undefined, }, } : { @@ -715,6 +697,7 @@ export function XYChart({ tickFormat={(d) => axis.formatter?.convert(d) || ''} style={getYAxesStyle(axis.groupId as 'left' | 'right')} domain={getYAxisDomain(axis)} + ticks={5} /> ); })} diff --git a/x-pack/plugins/license_management/public/application/index.tsx b/x-pack/plugins/license_management/public/application/index.tsx index a289a40e72da8b..16b6ebb1afdf93 100644 --- a/x-pack/plugins/license_management/public/application/index.tsx +++ b/x-pack/plugins/license_management/public/application/index.tsx @@ -36,4 +36,4 @@ export const renderApp = (element: Element, dependencies: AppDependencies) => { }; }; -export { AppDependencies }; +export type { AppDependencies }; diff --git a/x-pack/plugins/license_management/public/index.ts b/x-pack/plugins/license_management/public/index.ts index d36a08a999f417..c63cab6f4ad090 100644 --- a/x-pack/plugins/license_management/public/index.ts +++ b/x-pack/plugins/license_management/public/index.ts @@ -9,5 +9,5 @@ import { PluginInitializerContext } from 'src/core/public'; import { LicenseManagementUIPlugin } from './plugin'; import './application/index.scss'; -export { LicenseManagementUIPluginSetup, LicenseManagementUIPluginStart } from './plugin'; +export type { LicenseManagementUIPluginSetup, LicenseManagementUIPluginStart } from './plugin'; export const plugin = (ctx: PluginInitializerContext) => new LicenseManagementUIPlugin(ctx); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index 1db463a47dbf0e..f147b560ad5d3a 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -14,6 +14,7 @@ import { createLicenseUpdate } from '../common/license_update'; import { License } from '../common/license'; import { mountExpiredBanner } from './expired_banner'; import { FeatureUsageService } from './services'; +import type { PublicLicenseJSON } from '../common/types'; export const licensingSessionStorageKey = 'xpack.licensing'; @@ -148,9 +149,9 @@ export class LicensingPlugin implements Plugin => { + private fetchLicense = async (core: CoreSetup): Promise => { try { - const response = await core.http.get({ + const response = await core.http.get({ path: this.infoEndpoint, asSystemRequest: true, }); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_export_details_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_export_details_schema.mock.ts new file mode 100644 index 00000000000000..91b20a49213d64 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/exception_export_details_schema.mock.ts @@ -0,0 +1,28 @@ +/* + * 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 { ExportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types'; + +export interface ExportExceptionDetailsMock { + listCount?: number; + missingListsCount?: number; + missingLists?: Array>; + itemCount?: number; + missingItemCount?: number; + missingItems?: Array>; +} + +export const getExceptionExportDetailsMock = ( + details?: ExportExceptionDetailsMock +): ExportExceptionDetails => ({ + exported_exception_list_count: details?.listCount ?? 0, + exported_exception_list_item_count: details?.itemCount ?? 0, + missing_exception_list_item_count: details?.missingItemCount ?? 0, + missing_exception_list_items: details?.missingItems ?? [], + missing_exception_lists: details?.missingLists ?? [], + missing_exception_lists_count: details?.missingListsCount ?? 0, +}); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts index 42c35ba1a5d7a7..eca17b4c835d63 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts @@ -16,6 +16,7 @@ import { import { DATE_NOW, DESCRIPTION, + DETECTION_TYPE, ELASTIC_USER, ENDPOINT_TYPE, IMMUTABLE, @@ -48,6 +49,26 @@ export const getExceptionListSchemaMock = (): ExceptionListSchema => ({ version: VERSION, }); +export const getDetectionsExceptionListSchemaMock = (): ExceptionListSchema => ({ + _version: _VERSION, + created_at: DATE_NOW, + created_by: USER, + description: DESCRIPTION, + id: '1', + immutable: IMMUTABLE, + list_id: 'exception_list_id', + meta: META, + name: 'Sample Exception List', + namespace_type: 'single', + os_types: ['linux'], + tags: ['user added string for a tag', 'malware'], + tie_breaker_id: TIE_BREAKER, + type: DETECTION_TYPE, + updated_at: DATE_NOW, + updated_by: 'user_name', + version: VERSION, +}); + export const getTrustedAppsListSchemaMock = (): ExceptionListSchema => { return { ...getExceptionListSchemaMock(), diff --git a/x-pack/plugins/lists/server/index.ts b/x-pack/plugins/lists/server/index.ts index 9f395cb0d94bc7..d8e13e86329c12 100644 --- a/x-pack/plugins/lists/server/index.ts +++ b/x-pack/plugins/lists/server/index.ts @@ -12,7 +12,7 @@ import { ListPlugin } from './plugin'; // exporting these since its required at top level in siem plugin export { ListClient } from './services/lists/list_client'; -export { +export type { CreateExceptionListItemOptions, UpdateExceptionListItemOptions, } from './services/exception_lists/exception_list_client_types'; diff --git a/x-pack/plugins/lists/server/scripts/check_env_variables.sh b/x-pack/plugins/lists/server/scripts/check_env_variables.sh index 4df0e42adf9f38..df2354ed8398a9 100755 --- a/x-pack/plugins/lists/server/scripts/check_env_variables.sh +++ b/x-pack/plugins/lists/server/scripts/check_env_variables.sh @@ -30,13 +30,3 @@ if [ -z "${KIBANA_URL}" ]; then echo "Set KIBANA_URL in your environment" exit 1 fi - -if [ -z "${TASK_MANAGER_INDEX}" ]; then - echo "Set TASK_MANAGER_INDEX in your environment" - exit 1 -fi - -if [ -z "${KIBANA_INDEX}" ]; then - echo "Set KIBANA_INDEX in your environment" - exit 1 -fi diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts index f5f6a4f1f2d5a0..a780080dabc83d 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts @@ -11,6 +11,7 @@ import { getFoundExceptionListSchemaMock } from '../../../common/schemas/respons import { getFoundExceptionListItemSchemaMock } from '../../../common/schemas/response/found_exception_list_item_schema.mock'; import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock'; import { + getDetectionsExceptionListSchemaMock, getExceptionListSchemaMock, getTrustedAppsListSchemaMock, } from '../../../common/schemas/response/exception_list_schema.mock'; @@ -31,10 +32,12 @@ export class ExceptionListClientMock extends ExceptionListClient { public createTrustedAppsList = jest.fn().mockResolvedValue(getTrustedAppsListSchemaMock()); public createEndpointList = jest.fn().mockResolvedValue(getExceptionListSchemaMock()); public exportExceptionListAndItems = jest.fn().mockResolvedValue({ - exportData: 'exportString', + exportData: `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}`, exportDetails: { - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, + exported_exception_list_count: 1, + exported_exception_list_item_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], missing_exception_lists: [], diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index 14de474974c119..d6edf83428587c 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -15,6 +15,7 @@ import type { ExceptionListItemTypeOrUndefined, ExceptionListType, ExceptionListTypeOrUndefined, + ExportExceptionDetails, FilterOrUndefined, Id, IdOrUndefined, @@ -229,12 +230,5 @@ export interface ExportExceptionListAndItemsOptions { export interface ExportExceptionListAndItemsReturn { exportData: string; - exportDetails: { - exported_exception_list_count: number; - exported_exception_list_item_count: number; - missing_exception_list_item_count: number; - missing_exception_list_items: string[]; - missing_exception_lists: string[]; - missing_exception_lists_count: number; - }; + exportDetails: ExportExceptionDetails; } diff --git a/x-pack/plugins/lists/server/services/exception_lists/export_exception_list_and_items.ts b/x-pack/plugins/lists/server/services/exception_lists/export_exception_list_and_items.ts index 46b3df4e5ac449..b071c72a9b1225 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/export_exception_list_and_items.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/export_exception_list_and_items.ts @@ -6,6 +6,7 @@ */ import type { + ExportExceptionDetails, IdOrUndefined, ListIdOrUndefined, NamespaceType, @@ -25,14 +26,7 @@ interface ExportExceptionListAndItemsOptions { export interface ExportExceptionListAndItemsReturn { exportData: string; - exportDetails: { - exported_exception_list_count: number; - exported_exception_list_item_count: number; - missing_exception_list_item_count: number; - missing_exception_list_items: string[]; - missing_exception_lists: string[]; - missing_exception_lists_count: number; - }; + exportDetails: ExportExceptionDetails; } export const exportExceptionListAndItems = async ({ diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 86d1a38b4939f5..5de21099c93404 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -175,7 +175,6 @@ export enum GRID_RESOLUTION { SUPER_FINE = 'SUPER_FINE', } -export const SUPER_FINE_ZOOM_DELTA = 7; // (2 ^ SUPER_FINE_ZOOM_DELTA) ^ 2 = number of cells in a given tile export const GEOTILE_GRID_AGG_NAME = 'gridSplit'; export const GEOCENTROID_AGG_NAME = 'gridCentroid'; diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts index 8f681cc9de70de..4d687969308bbe 100644 --- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts @@ -32,6 +32,13 @@ export type TileMetaFeature = Feature & { properties: { 'hits.total.relation': string; 'hits.total.value': number; + + // For _mvt requests with "aggs" property in request: aggregation statistics returned in the pattern outined below + // aggregations._count.min + // aggregations._count.max + // aggregations..min + // aggregations..max + [key: string]: number | string; }; }; diff --git a/x-pack/plugins/maps/common/elasticsearch_util/index.ts b/x-pack/plugins/maps/common/elasticsearch_util/index.ts index 6febb237cdda71..b5b20d70a7c764 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/index.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/index.ts @@ -9,4 +9,5 @@ export * from './es_agg_utils'; export * from './elasticsearch_geo_utils'; export * from './spatial_filter_utils'; export * from './types'; -export { isTotalHitsGreaterThan, TotalHits } from './total_hits'; +export type { TotalHits } from './total_hits'; +export { isTotalHitsGreaterThan } from './total_hits'; diff --git a/x-pack/plugins/maps/common/index.ts b/x-pack/plugins/maps/common/index.ts index 8374a4d0dbaa3e..517ed0bceff10f 100644 --- a/x-pack/plugins/maps/common/index.ts +++ b/x-pack/plugins/maps/common/index.ts @@ -19,7 +19,7 @@ export { SYMBOLIZE_AS_TYPES, } from './constants'; -export { +export type { EMSFileSourceDescriptor, ESTermSourceDescriptor, LayerDescriptor, diff --git a/x-pack/plugins/maps/public/_mixins.scss b/x-pack/plugins/maps/public/_mixins.scss index 914bc23c1163cf..135d90b3a4c56b 100644 --- a/x-pack/plugins/maps/public/_mixins.scss +++ b/x-pack/plugins/maps/public/_mixins.scss @@ -1,11 +1,4 @@ @mixin mapToolbarButtonGroupBorderRadius { - @include kbnThemeStyle($theme: 'v7') { - border-radius: $euiBorderRadius; - } - - @include kbnThemeStyle($theme: 'v8') { - border-radius: $euiBorderRadiusSmall; - } - + border-radius: $euiBorderRadiusSmall; overflow: hidden; } diff --git a/x-pack/plugins/maps/public/actions/index.ts b/x-pack/plugins/maps/public/actions/index.ts index 4c869698d5ac16..f4d6997333c6c1 100644 --- a/x-pack/plugins/maps/public/actions/index.ts +++ b/x-pack/plugins/maps/public/actions/index.ts @@ -10,9 +10,9 @@ export * from './ui_actions'; export * from './map_actions'; export * from './map_action_constants'; export * from './layer_actions'; +export type { DataRequestContext } from './data_request_actions'; export { cancelAllInFlightRequests, - DataRequestContext, fitToLayerExtent, fitToDataBounds, } from './data_request_actions'; diff --git a/x-pack/plugins/maps/public/api/index.ts b/x-pack/plugins/maps/public/api/index.ts index 9cf7577f448be3..0bbeca6f8160d2 100644 --- a/x-pack/plugins/maps/public/api/index.ts +++ b/x-pack/plugins/maps/public/api/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { MapsStartApi } from './start_api'; -export { MapsSetupApi } from './setup_api'; +export type { MapsStartApi } from './start_api'; +export type { MapsSetupApi } from './setup_api'; export { createLayerDescriptors } from './create_layer_descriptors'; export { suggestEMSTermJoinConfig } from './ems'; diff --git a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts index f5e2a467c5e629..d8346fa8f5283a 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts @@ -17,25 +17,48 @@ const defaultParams = { origin: FIELD_ORIGIN.SOURCE, }; -describe('supportsFieldMeta', () => { - test('Non-counting aggregations should support field meta', () => { +describe('supportsFieldMetaFromEs', () => { + test('Non-counting aggregations should support field meta from ES', () => { const avgMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.AVG }); - expect(avgMetric.supportsFieldMeta()).toBe(true); + expect(avgMetric.supportsFieldMetaFromEs()).toBe(true); const maxMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.MAX }); - expect(maxMetric.supportsFieldMeta()).toBe(true); + expect(maxMetric.supportsFieldMetaFromEs()).toBe(true); const minMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.MIN }); - expect(minMetric.supportsFieldMeta()).toBe(true); + expect(minMetric.supportsFieldMetaFromEs()).toBe(true); const termsMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.TERMS }); - expect(termsMetric.supportsFieldMeta()).toBe(true); + expect(termsMetric.supportsFieldMetaFromEs()).toBe(true); }); - test('Counting aggregations should not support field meta', () => { + test('Counting aggregations should not support field meta from ES', () => { const sumMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.SUM }); - expect(sumMetric.supportsFieldMeta()).toBe(false); + expect(sumMetric.supportsFieldMetaFromEs()).toBe(false); const uniqueCountMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.UNIQUE_COUNT, }); - expect(uniqueCountMetric.supportsFieldMeta()).toBe(false); + expect(uniqueCountMetric.supportsFieldMetaFromEs()).toBe(false); + }); +}); + +describe('supportsFieldMetaFromLocalData', () => { + test('number metrics should support field meta from local', () => { + const avgMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.AVG }); + expect(avgMetric.supportsFieldMetaFromLocalData()).toBe(true); + const maxMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.MAX }); + expect(maxMetric.supportsFieldMetaFromLocalData()).toBe(true); + const minMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.MIN }); + expect(minMetric.supportsFieldMetaFromLocalData()).toBe(true); + const sumMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.SUM }); + expect(sumMetric.supportsFieldMetaFromLocalData()).toBe(true); + const uniqueCountMetric = new AggField({ + ...defaultParams, + aggType: AGG_TYPE.UNIQUE_COUNT, + }); + expect(uniqueCountMetric.supportsFieldMetaFromLocalData()).toBe(true); + }); + + test('Non number metrics should not support field meta from local', () => { + const termMetric = new AggField({ ...defaultParams, aggType: AGG_TYPE.TERMS }); + expect(termMetric.supportsFieldMetaFromLocalData()).toBe(false); }); }); diff --git a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts index acfe6a9055eb66..ed8830a7c56b61 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts @@ -7,6 +7,7 @@ import { IndexPattern } from 'src/plugins/data/public'; import { AGG_TYPE } from '../../../../common/constants'; +import { TileMetaFeature } from '../../../../common/descriptor_types'; import { CountAggField } from './count_agg_field'; import { isMetricCountable } from '../../util/is_metric_countable'; import { CountAggFieldParams } from './agg_field_types'; @@ -30,6 +31,16 @@ export class AggField extends CountAggField { this._aggType = params.aggType; } + supportsFieldMetaFromEs(): boolean { + // count and sum aggregations are not within field bounds so they do not support field meta. + return !isMetricCountable(this._getAggType()); + } + + supportsFieldMetaFromLocalData(): boolean { + // Elasticsearch vector tile search API returns meta tiles with numeric aggregation metrics. + return this._getDataTypeSynchronous() === 'number'; + } + isValid(): boolean { return !!this._esDocField; } @@ -38,11 +49,6 @@ export class AggField extends CountAggField { return this._source.isMvt() ? this.getName() + '.value' : this.getName(); } - supportsFieldMeta(): boolean { - // count and sum aggregations are not within field bounds so they do not support field meta. - return !isMetricCountable(this._getAggType()); - } - canValueBeFormatted(): boolean { return this._getAggType() !== AGG_TYPE.UNIQUE_COUNT; } @@ -73,10 +79,14 @@ export class AggField extends CountAggField { ); } - async getDataType(): Promise { + _getDataTypeSynchronous(): string { return this._getAggType() === AGG_TYPE.TERMS ? 'string' : 'number'; } + async getDataType(): Promise { + return this._getDataTypeSynchronous(); + } + getBucketCount(): number { // terms aggregation increases the overall number of buckets per split bucket return this._getAggType() === AGG_TYPE.TERMS ? TERMS_AGG_SHARD_SIZE : 0; @@ -95,4 +105,17 @@ export class AggField extends CountAggField { async getCategoricalFieldMetaRequest(size: number): Promise { return this._esDocField ? await this._esDocField.getCategoricalFieldMetaRequest(size) : null; } + + pluckRangeFromTileMetaFeature(metaFeature: TileMetaFeature) { + const minField = `aggregations.${this.getName()}.min`; + const maxField = `aggregations.${this.getName()}.max`; + return metaFeature.properties && + typeof metaFeature.properties[minField] === 'number' && + typeof metaFeature.properties[maxField] === 'number' + ? { + min: metaFeature.properties[minField] as number, + max: metaFeature.properties[maxField] as number, + } + : null; + } } diff --git a/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts b/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts index e54c846883c205..4ab2f1b211a305 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts @@ -19,5 +19,4 @@ export interface CountAggFieldParams { label?: string; source: IESAggSource; origin: FIELD_ORIGIN; - canReadFromGeoJson?: boolean; } diff --git a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts index c9970123c80959..5a9e4476507e03 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts @@ -18,9 +18,9 @@ const defaultParams = { origin: FIELD_ORIGIN.SOURCE, }; -describe('supportsFieldMeta', () => { +describe('supportsFieldMetaFromEs', () => { test('Counting aggregations should not support field meta', () => { const countMetric = new CountAggField({ ...defaultParams }); - expect(countMetric.supportsFieldMeta()).toBe(false); + expect(countMetric.supportsFieldMetaFromEs()).toBe(false); }); }); diff --git a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts index b303dbc342bb26..7f38379c1075b3 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts @@ -9,6 +9,7 @@ import { IndexPattern } from 'src/plugins/data/public'; import { IESAggSource } from '../../sources/es_agg_source'; import { IVectorSource } from '../../sources/vector_source'; import { AGG_TYPE, FIELD_ORIGIN } from '../../../../common/constants'; +import { TileMetaFeature } from '../../../../common/descriptor_types'; import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property'; import { ESAggTooltipProperty } from '../../tooltips/es_agg_tooltip_property'; import { IESAggField, CountAggFieldParams } from './agg_field_types'; @@ -18,13 +19,20 @@ export class CountAggField implements IESAggField { protected readonly _source: IESAggSource; private readonly _origin: FIELD_ORIGIN; protected readonly _label?: string; - private readonly _canReadFromGeoJson: boolean; - constructor({ label, source, origin, canReadFromGeoJson = true }: CountAggFieldParams) { + constructor({ label, source, origin }: CountAggFieldParams) { this._source = source; this._origin = origin; this._label = label; - this._canReadFromGeoJson = canReadFromGeoJson; + } + + supportsFieldMetaFromEs(): boolean { + return false; + } + + supportsFieldMetaFromLocalData(): boolean { + // Elasticsearch vector tile search API returns meta tiles for aggregation metrics + return true; } _getAggType(): AGG_TYPE { @@ -79,10 +87,6 @@ export class CountAggField implements IESAggField { return null; } - supportsFieldMeta(): boolean { - return false; - } - getBucketCount() { return 0; } @@ -103,15 +107,20 @@ export class CountAggField implements IESAggField { return null; } - supportsAutoDomain(): boolean { - return true; - } - - canReadFromGeoJson(): boolean { - return this._canReadFromGeoJson; - } - isEqual(field: IESAggField) { return field.getName() === this.getName(); } + + pluckRangeFromTileMetaFeature(metaFeature: TileMetaFeature) { + const minField = `aggregations._count.min`; + const maxField = `aggregations._count.max`; + return metaFeature.properties && + typeof metaFeature.properties[minField] === 'number' && + typeof metaFeature.properties[maxField] === 'number' + ? { + min: metaFeature.properties[minField] as number, + max: metaFeature.properties[maxField] as number, + } + : null; + } } diff --git a/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.ts b/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.ts index 597cf2a0567d2f..7e43a2a63658cb 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.ts @@ -18,8 +18,7 @@ import { PercentileAggField } from './percentile_agg_field'; export function esAggFieldsFactory( aggDescriptor: AggDescriptor, source: IESAggSource, - origin: FIELD_ORIGIN, - canReadFromGeoJson: boolean = true + origin: FIELD_ORIGIN ): IESAggField[] { let aggField; if (aggDescriptor.type === AGG_TYPE.COUNT) { @@ -27,7 +26,6 @@ export function esAggFieldsFactory( label: aggDescriptor.label, source, origin, - canReadFromGeoJson, }); } else if (aggDescriptor.type === AGG_TYPE.PERCENTILE) { aggField = new PercentileAggField({ @@ -42,7 +40,6 @@ export function esAggFieldsFactory( : DEFAULT_PERCENTILE, source, origin, - canReadFromGeoJson, }); } else { aggField = new AggField({ @@ -54,14 +51,13 @@ export function esAggFieldsFactory( aggType: aggDescriptor.type, source, origin, - canReadFromGeoJson, }); } const aggFields: IESAggField[] = [aggField]; if ('field' in aggDescriptor && aggDescriptor.type === AGG_TYPE.TERMS) { - aggFields.push(new TopTermPercentageField(aggField, canReadFromGeoJson)); + aggFields.push(new TopTermPercentageField(aggField)); } return aggFields; diff --git a/x-pack/plugins/maps/public/classes/fields/agg/index.ts b/x-pack/plugins/maps/public/classes/fields/agg/index.ts index f8de2db85db911..bd0b5184fe1575 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/index.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/index.ts @@ -6,4 +6,4 @@ */ export { esAggFieldsFactory } from './es_agg_factory'; -export { IESAggField } from './agg_field_types'; +export type { IESAggField } from './agg_field_types'; diff --git a/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts index 141f6aea5d3012..4591b252de2790 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts @@ -31,7 +31,12 @@ export class PercentileAggField extends AggField implements IESAggField { this._percentile = params.percentile; } - supportsFieldMeta(): boolean { + supportsFieldMetaFromEs(): boolean { + return true; + } + + supportsFieldMetaFromLocalData(): boolean { + // Elasticsearch vector tile search API returns meta tiles for aggregation metrics return true; } diff --git a/x-pack/plugins/maps/public/classes/fields/agg/top_term_percentage_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/top_term_percentage_field.ts index 227084bfe0cad5..d0618f64a5e717 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/top_term_percentage_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/top_term_percentage_field.ts @@ -9,14 +9,22 @@ import { IESAggField } from './agg_field_types'; import { IVectorSource } from '../../sources/vector_source'; import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property'; import { TOP_TERM_PERCENTAGE_SUFFIX, FIELD_ORIGIN } from '../../../../common/constants'; +import { TileMetaFeature } from '../../../../common/descriptor_types'; export class TopTermPercentageField implements IESAggField { private readonly _topTermAggField: IESAggField; - private readonly _canReadFromGeoJson: boolean; - constructor(topTermAggField: IESAggField, canReadFromGeoJson: boolean = true) { + constructor(topTermAggField: IESAggField) { this._topTermAggField = topTermAggField; - this._canReadFromGeoJson = canReadFromGeoJson; + } + + supportsFieldMetaFromEs(): boolean { + return false; + } + + supportsFieldMetaFromLocalData(): boolean { + // Elasticsearch vector tile search API does not support top term metric + return false; } getSource(): IVectorSource { @@ -64,15 +72,6 @@ export class TopTermPercentageField implements IESAggField { getBucketCount(): number { return 0; } - - supportsAutoDomain(): boolean { - return this._canReadFromGeoJson; - } - - supportsFieldMeta(): boolean { - return false; - } - async getExtendedStatsFieldMetaRequest(): Promise { return null; } @@ -89,11 +88,11 @@ export class TopTermPercentageField implements IESAggField { return false; } - canReadFromGeoJson(): boolean { - return this._canReadFromGeoJson; - } - isEqual(field: IESAggField) { return field.getName() === this.getName(); } + + pluckRangeFromTileMetaFeature(metaFeature: TileMetaFeature) { + return null; + } } diff --git a/x-pack/plugins/maps/public/classes/fields/ems_file_field.ts b/x-pack/plugins/maps/public/classes/fields/ems_file_field.ts index 87119f32182de3..9463f364ad9531 100644 --- a/x-pack/plugins/maps/public/classes/fields/ems_file_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/ems_file_field.ts @@ -26,6 +26,14 @@ export class EMSFileField extends AbstractField implements IField { this._source = source; } + supportsFieldMetaFromEs(): boolean { + return false; + } + + supportsFieldMetaFromLocalData(): boolean { + return true; + } + getSource(): IVectorSource { return this._source; } diff --git a/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts b/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts index afddb34d2d0ec2..b3f68da99d5290 100644 --- a/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts @@ -16,22 +16,27 @@ import { IVectorSource } from '../sources/vector_source'; export class ESDocField extends AbstractField implements IField { private readonly _source: IESSource; - private readonly _canReadFromGeoJson: boolean; constructor({ fieldName, source, origin, - canReadFromGeoJson = true, }: { fieldName: string; source: IESSource; origin: FIELD_ORIGIN; - canReadFromGeoJson?: boolean; }) { super({ fieldName, origin }); this._source = source; - this._canReadFromGeoJson = canReadFromGeoJson; + } + + supportsFieldMetaFromEs(): boolean { + return true; + } + + supportsFieldMetaFromLocalData(): boolean { + // Elasticsearch vector tile search API does not return meta tiles for documents + return !this.getSource().isMvt(); } canValueBeFormatted(): boolean { @@ -73,14 +78,6 @@ export class ESDocField extends AbstractField implements IField { : super.getLabel(); } - supportsFieldMeta(): boolean { - return true; - } - - canReadFromGeoJson(): boolean { - return this._canReadFromGeoJson; - } - async getExtendedStatsFieldMetaRequest(): Promise { const indexPatternField = await this._getIndexPatternField(); diff --git a/x-pack/plugins/maps/public/classes/fields/field.ts b/x-pack/plugins/maps/public/classes/fields/field.ts index 014d75caf90b67..96d42a91319e15 100644 --- a/x-pack/plugins/maps/public/classes/fields/field.ts +++ b/x-pack/plugins/maps/public/classes/fields/field.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TileMetaFeature } from '../../../common/descriptor_types'; import { FIELD_ORIGIN } from '../../../common/constants'; import { IVectorSource } from '../sources/vector_source'; import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; @@ -22,20 +23,25 @@ export interface IField { isValid(): boolean; getExtendedStatsFieldMetaRequest(): Promise; getPercentilesFieldMetaRequest(percentiles: number[]): Promise; - getCategoricalFieldMetaRequest(size: number): Promise; + getCategoricalFieldMetaRequest(size: number): Promise; + + /* + * IField.supportsFieldMetaFromLocalData returns boolean indicating whether field value domain + * can be determined from local data + */ + supportsFieldMetaFromLocalData(): boolean; + + /* + * IField.supportsFieldMetaFromEs returns boolean indicating whether field value domain + * can be determined from Elasticsearch. + * When true, getExtendedStatsFieldMetaRequest, getPercentilesFieldMetaRequest, and getCategoricalFieldMetaRequest + * can not return null + */ + supportsFieldMetaFromEs(): boolean; - // Whether Maps-app can automatically determine the domain of the field-values - // if this is not the case (e.g. for .mvt tiled data), - // then styling properties that require the domain to be known cannot use this property. - supportsAutoDomain(): boolean; - - // Whether Maps-app can automatically determine the domain of the field-values - // _without_ having to retrieve the data as GeoJson - // e.g. for ES-sources, this would use the extended_stats API - supportsFieldMeta(): boolean; - - canReadFromGeoJson(): boolean; isEqual(field: IField): boolean; + + pluckRangeFromTileMetaFeature(metaFeature: TileMetaFeature): { min: number; max: number } | null; } export class AbstractField implements IField { @@ -47,6 +53,14 @@ export class AbstractField implements IField { this._origin = origin || FIELD_ORIGIN.SOURCE; } + supportsFieldMetaFromEs(): boolean { + throw new Error('must implement AbstractField#supportsFieldMetaFromEs'); + } + + supportsFieldMetaFromLocalData(): boolean { + throw new Error('must implement AbstractField#supportsFieldMetaFromLocalData'); + } + getName(): string { return this._fieldName; } @@ -64,7 +78,7 @@ export class AbstractField implements IField { } getSource(): IVectorSource { - throw new Error('must implement Field#getSource'); + throw new Error('must implement AbstractField#getSource'); } isValid(): boolean { @@ -88,10 +102,6 @@ export class AbstractField implements IField { return this._origin; } - supportsFieldMeta(): boolean { - return false; - } - async getExtendedStatsFieldMetaRequest(): Promise { return null; } @@ -104,15 +114,11 @@ export class AbstractField implements IField { return null; } - supportsAutoDomain(): boolean { - return true; - } - - canReadFromGeoJson(): boolean { - return true; - } - isEqual(field: IField) { return this._origin === field.getOrigin() && this._fieldName === field.getName(); } + + pluckRangeFromTileMetaFeature(metaFeature: TileMetaFeature) { + return null; + } } diff --git a/x-pack/plugins/maps/public/classes/fields/inline_field.ts b/x-pack/plugins/maps/public/classes/fields/inline_field.ts index 17892d122c5397..1c81d1399f24bc 100644 --- a/x-pack/plugins/maps/public/classes/fields/inline_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/inline_field.ts @@ -29,6 +29,14 @@ export class InlineField extends AbstractField implemen this._dataType = dataType; } + supportsFieldMetaFromEs(): boolean { + return false; + } + + supportsFieldMetaFromLocalData(): boolean { + return true; + } + getSource(): IVectorSource { return this._source; } diff --git a/x-pack/plugins/maps/public/classes/fields/mvt_field.ts b/x-pack/plugins/maps/public/classes/fields/mvt_field.ts index ed2955a1cc16f3..7eff8943f42c15 100644 --- a/x-pack/plugins/maps/public/classes/fields/mvt_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/mvt_field.ts @@ -30,6 +30,14 @@ export class MVTField extends AbstractField implements IField { this._type = type; } + supportsFieldMetaFromEs(): boolean { + return false; + } + + supportsFieldMetaFromLocalData(): boolean { + return false; + } + getMVTFieldDescriptor(): MVTFieldDescriptor { return { type: this._type, @@ -54,12 +62,4 @@ export class MVTField extends AbstractField implements IField { async getLabel(): Promise { return this.getName(); } - - supportsAutoDomain() { - return false; - } - - canReadFromGeoJson(): boolean { - return false; - } } diff --git a/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts index 033b0de025ee19..d285d3a36f66ae 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts @@ -13,7 +13,6 @@ import { getEMSSettings } from '../../kibana_services'; import { KibanaTilemapSource } from '../sources/kibana_tilemap_source'; import { TileLayer } from './tile_layer/tile_layer'; import { VectorTileLayer } from './vector_tile_layer/vector_tile_layer'; -// @ts-expect-error import { EMSTMSSource } from '../sources/ems_tms_source'; export function createBasemapLayerDescriptor(): LayerDescriptor | null { diff --git a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.test.ts b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.test.ts index 16e36fff03f867..43cc69687ffb71 100644 --- a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.test.ts @@ -8,7 +8,8 @@ import { ITileLayerArguments, TileLayer } from './tile_layer'; import { SOURCE_TYPES } from '../../../../common/constants'; import { XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; -import { ITMSSource, AbstractTMSSource } from '../../sources/tms_source'; +import { AbstractSource } from '../../sources/source'; +import { ITMSSource } from '../../sources/tms_source'; import { ILayer } from '../layer'; const sourceDescriptor: XYZTMSSourceDescriptor = { @@ -17,7 +18,7 @@ const sourceDescriptor: XYZTMSSourceDescriptor = { id: 'foobar', }; -class MockTileSource extends AbstractTMSSource implements ITMSSource { +class MockTileSource extends AbstractSource implements ITMSSource { readonly _descriptor: XYZTMSSourceDescriptor; constructor(descriptor: XYZTMSSourceDescriptor) { super(descriptor, {}); diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index 79aeab76e41854..4b881228f79b53 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -319,9 +319,9 @@ export class TiledVectorLayer extends VectorLayer { return; } + this._setMbLabelProperties(mbMap, sourceMeta.layerName); this._setMbPointsProperties(mbMap, sourceMeta.layerName); this._setMbLinePolygonProperties(mbMap, sourceMeta.layerName); - this._setMbLabelProperties(mbMap, sourceMeta.layerName); this._syncTooManyFeaturesProperties(mbMap); } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts index cb964f77613da1..2b14b78f929464 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts @@ -6,10 +6,5 @@ */ export { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; -export { - isVectorLayer, - IVectorLayer, - VectorLayer, - VectorLayerArguments, - NO_RESULTS_ICON_AND_TOOLTIPCONTENT, -} from './vector_layer'; +export type { IVectorLayer, VectorLayerArguments } from './vector_layer'; +export { isVectorLayer, VectorLayer, NO_RESULTS_ICON_AND_TOOLTIPCONTENT } from './vector_layer'; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 675bacb10cde99..434743ef7ac9e9 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -820,133 +820,76 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { mvtSourceLayer?: string, timesliceMaskConfig?: TimesliceMaskConfig ) { + const sourceId = this.getId(); + const labelLayerId = this._getMbLabelLayerId(); const pointLayerId = this._getMbPointLayerId(); const symbolLayerId = this._getMbSymbolLayerId(); const pointLayer = mbMap.getLayer(pointLayerId); const symbolLayer = mbMap.getLayer(symbolLayerId); - // Point layers symbolized as circles require 2 mapbox layers because - // "circle" layers do not support "text" style properties - // Point layers symbolized as icons only contain a single mapbox layer. + // + // Create marker layer + // "circle" layer type for points + // "symbol" layer type for icons + // let markerLayerId; - let textLayerId; if (this.getCurrentStyle().arePointsSymbolizedAsCircles()) { markerLayerId = pointLayerId; - textLayerId = this._getMbTextLayerId(); + if (!pointLayer) { + const mbLayer: MbLayer = { + id: pointLayerId, + type: 'circle', + source: sourceId, + paint: {}, + }; + + if (mvtSourceLayer) { + mbLayer['source-layer'] = mvtSourceLayer; + } + mbMap.addLayer(mbLayer, labelLayerId); + } if (symbolLayer) { mbMap.setLayoutProperty(symbolLayerId, 'visibility', 'none'); } - this._setMbCircleProperties(mbMap, mvtSourceLayer, timesliceMaskConfig); } else { markerLayerId = symbolLayerId; - textLayerId = symbolLayerId; + if (!symbolLayer) { + const mbLayer: MbLayer = { + id: symbolLayerId, + type: 'symbol', + source: sourceId, + }; + if (mvtSourceLayer) { + mbLayer['source-layer'] = mvtSourceLayer; + } + mbMap.addLayer(mbLayer, labelLayerId); + } if (pointLayer) { mbMap.setLayoutProperty(pointLayerId, 'visibility', 'none'); - mbMap.setLayoutProperty(this._getMbTextLayerId(), 'visibility', 'none'); } - this._setMbSymbolProperties(mbMap, mvtSourceLayer, timesliceMaskConfig); - } - - this.syncVisibilityWithMb(mbMap, markerLayerId); - mbMap.setLayerZoomRange(markerLayerId, this.getMinZoom(), this.getMaxZoom()); - if (markerLayerId !== textLayerId) { - this.syncVisibilityWithMb(mbMap, textLayerId); - mbMap.setLayerZoomRange(textLayerId, this.getMinZoom(), this.getMaxZoom()); - } - } - - _setMbCircleProperties( - mbMap: MbMap, - mvtSourceLayer?: string, - timesliceMaskConfig?: TimesliceMaskConfig - ) { - const sourceId = this.getId(); - const pointLayerId = this._getMbPointLayerId(); - const pointLayer = mbMap.getLayer(pointLayerId); - if (!pointLayer) { - const mbLayer: MbLayer = { - id: pointLayerId, - type: 'circle', - source: sourceId, - paint: {}, - }; - - if (mvtSourceLayer) { - mbLayer['source-layer'] = mvtSourceLayer; - } - mbMap.addLayer(mbLayer); - } - - const textLayerId = this._getMbTextLayerId(); - const textLayer = mbMap.getLayer(textLayerId); - if (!textLayer) { - const mbLayer: MbLayer = { - id: textLayerId, - type: 'symbol', - source: sourceId, - }; - if (mvtSourceLayer) { - mbLayer['source-layer'] = mvtSourceLayer; - } - mbMap.addLayer(mbLayer); } const filterExpr = getPointFilterExpression(this.hasJoins(), timesliceMaskConfig); - if (!_.isEqual(filterExpr, mbMap.getFilter(pointLayerId))) { - mbMap.setFilter(pointLayerId, filterExpr); - mbMap.setFilter(textLayerId, filterExpr); + if (!_.isEqual(filterExpr, mbMap.getFilter(markerLayerId))) { + mbMap.setFilter(markerLayerId, filterExpr); } - this.getCurrentStyle().setMBPaintPropertiesForPoints({ - alpha: this.getAlpha(), - mbMap, - pointLayerId, - }); - - this.getCurrentStyle().setMBPropertiesForLabelText({ - alpha: this.getAlpha(), - mbMap, - textLayerId, - }); - } - - _setMbSymbolProperties( - mbMap: MbMap, - mvtSourceLayer?: string, - timesliceMaskConfig?: TimesliceMaskConfig - ) { - const sourceId = this.getId(); - const symbolLayerId = this._getMbSymbolLayerId(); - const symbolLayer = mbMap.getLayer(symbolLayerId); - - if (!symbolLayer) { - const mbLayer: MbLayer = { - id: symbolLayerId, - type: 'symbol', - source: sourceId, - }; - if (mvtSourceLayer) { - mbLayer['source-layer'] = mvtSourceLayer; - } - mbMap.addLayer(mbLayer); - } - - const filterExpr = getPointFilterExpression(this.hasJoins(), timesliceMaskConfig); - if (!_.isEqual(filterExpr, mbMap.getFilter(symbolLayerId))) { - mbMap.setFilter(symbolLayerId, filterExpr); + if (this.getCurrentStyle().arePointsSymbolizedAsCircles()) { + this.getCurrentStyle().setMBPaintPropertiesForPoints({ + alpha: this.getAlpha(), + mbMap, + pointLayerId: markerLayerId, + }); + } else { + this.getCurrentStyle().setMBSymbolPropertiesForPoints({ + alpha: this.getAlpha(), + mbMap, + symbolLayerId: markerLayerId, + }); } - this.getCurrentStyle().setMBSymbolPropertiesForPoints({ - alpha: this.getAlpha(), - mbMap, - symbolLayerId, - }); - - this.getCurrentStyle().setMBPropertiesForLabelText({ - alpha: this.getAlpha(), - mbMap, - textLayerId: symbolLayerId, - }); + this.syncVisibilityWithMb(mbMap, markerLayerId); + mbMap.setLayerZoomRange(markerLayerId, this.getMinZoom(), this.getMaxZoom()); } _setMbLinePolygonProperties( @@ -955,6 +898,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { timesliceMaskConfig?: TimesliceMaskConfig ) { const sourceId = this.getId(); + const labelLayerId = this._getMbLabelLayerId(); const fillLayerId = this._getMbPolygonLayerId(); const lineLayerId = this._getMbLineLayerId(); @@ -969,7 +913,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { if (mvtSourceLayer) { mbLayer['source-layer'] = mvtSourceLayer; } - mbMap.addLayer(mbLayer); + mbMap.addLayer(mbLayer, labelLayerId); } if (!mbMap.getLayer(lineLayerId)) { const mbLayer: MbLayer = { @@ -981,7 +925,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { if (mvtSourceLayer) { mbLayer['source-layer'] = mvtSourceLayer; } - mbMap.addLayer(mbLayer); + mbMap.addLayer(mbLayer, labelLayerId); } this.getCurrentStyle().setMBPaintProperties({ @@ -1047,10 +991,9 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { _syncStylePropertiesWithMb(mbMap: MbMap, timeslice?: Timeslice) { const timesliceMaskConfig = this._getTimesliceMaskConfig(timeslice); + this._setMbLabelProperties(mbMap, undefined, timesliceMaskConfig); this._setMbPointsProperties(mbMap, undefined, timesliceMaskConfig); this._setMbLinePolygonProperties(mbMap, undefined, timesliceMaskConfig); - // label layers added after geometry layers to ensure they are on top - this._setMbLabelProperties(mbMap, undefined, timesliceMaskConfig); } _getTimesliceMaskConfig(timeslice?: Timeslice): TimesliceMaskConfig | undefined { @@ -1077,14 +1020,6 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this.makeMbLayerId('circle'); } - _getMbTextLayerId() { - return this.makeMbLayerId('text'); - } - - // _getMbTextLayerId is labels for Points and MultiPoints - // _getMbLabelLayerId is labels for not Points and MultiPoints - // _getMbLabelLayerId used to be called _getMbCentroidLayerId - // TODO merge textLayer and labelLayer into single layer _getMbLabelLayerId() { return this.makeMbLayerId('label'); } @@ -1104,7 +1039,6 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { getMbTooltipLayerIds() { return [ this._getMbPointLayerId(), - this._getMbTextLayerId(), this._getMbLabelLayerId(), this._getMbSymbolLayerId(), this._getMbLineLayerId(), diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts index c15a0e1cf2fb83..d094e53c59a922 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts @@ -8,7 +8,8 @@ import { ITileLayerArguments } from '../tile_layer/tile_layer'; import { SOURCE_TYPES } from '../../../../common/constants'; import { DataFilters, XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; -import { ITMSSource, AbstractTMSSource } from '../../sources/tms_source'; +import { AbstractSource } from '../../sources/source'; +import { ITMSSource } from '../../sources/tms_source'; import { ILayer } from '../layer'; import { VectorTileLayer } from './vector_tile_layer'; import { DataRequestContext } from '../../../actions'; @@ -19,7 +20,7 @@ const sourceDescriptor: XYZTMSSourceDescriptor = { id: 'mockSourceId', }; -class MockTileSource extends AbstractTMSSource implements ITMSSource { +class MockTileSource extends AbstractSource implements ITMSSource { readonly _descriptor: XYZTMSSourceDescriptor; constructor(descriptor: XYZTMSSourceDescriptor) { super(descriptor, {}); diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/index.ts b/x-pack/plugins/maps/public/classes/sources/ems_file_source/index.ts index 2d4d59c5c41598..6c9d7060e9a4b8 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/index.ts @@ -6,4 +6,5 @@ */ export { emsBoundariesLayerWizardConfig } from './ems_boundaries_layer_wizard'; -export { EMSFileSource, IEmsFileSource } from './ems_file_source'; +export type { IEmsFileSource } from './ems_file_source'; +export { EMSFileSource } from './ems_file_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/create_source_editor.tsx index 16e4986f4d8a65..569e0aee6df0fd 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/create_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/create_source_editor.tsx @@ -21,7 +21,7 @@ export class CreateSourceEditor extends Component { state: State = {}; componentDidMount() { - this._onTileSelect({ id: null, isAutoSelect: true }); + this._onTileSelect({ isAutoSelect: true }); } _onTileSelect = (config: EmsTmsSourceConfig) => { diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js deleted file mode 100644 index 833e4b0fff95ec..00000000000000 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js +++ /dev/null @@ -1,70 +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. - */ - -jest.mock('../../../kibana_services', () => { - return { - getEmsTileLayerId: () => { - return { - bright: 'road_map', - desaturated: 'road_map_desaturated', - dark: 'dark_map', - }; - }, - }; -}); - -jest.mock('../../../util', () => { - return { - getEmsTmsServices: () => { - class MockTMSService { - constructor(config) { - this._config = config; - } - getMarkdownAttribution() { - return this._config.attributionMarkdown; - } - getId() { - return this._config.id; - } - } - - return [ - new MockTMSService({ - id: 'road_map', - attributionMarkdown: '[foobar](http://foobar.org) | [foobaz](http://foobaz.org)', - }), - new MockTMSService({ - id: 'satellite', - attributionMarkdown: '[satellite](http://satellite.org)', - }), - ]; - }, - }; -}); - -import { EMSTMSSource } from './ems_tms_source'; - -describe('EMSTMSSource', () => { - it('should get attribution from markdown (tiles v2 legacy format)', async () => { - const emsTmsSource = new EMSTMSSource({ - id: 'road_map', - }); - - const attributionProvider = emsTmsSource.getAttributionProvider(); - const attributions = await attributionProvider(); - expect(attributions).toEqual([ - { - label: 'foobar', - url: 'http://foobar.org', - }, - { - label: 'foobaz', - url: 'http://foobaz.org', - }, - ]); - }); -}); diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.tsx similarity index 78% rename from x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js rename to x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.tsx index 2acb0578f6152e..da36b9bf0b7e68 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.tsx @@ -6,18 +6,21 @@ */ import React from 'react'; -import { AbstractTMSSource } from '../tms_source'; +import { Adapters } from 'src/plugins/inspector/public'; +import { i18n } from '@kbn/i18n'; +import { AbstractSource, SourceEditorArgs } from '../source'; +import { ITMSSource } from '../tms_source'; import { getEmsTmsServices } from '../../../util'; import { UpdateSourceEditor } from './update_source_editor'; -import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { SOURCE_TYPES } from '../../../../common/constants'; +import { EMSTMSSourceDescriptor } from '../../../../common/descriptor_types'; import { getEmsTileLayerId, getIsDarkMode, getEMSSettings } from '../../../kibana_services'; import { registerSource } from '../source_registry'; import { getEmsUnavailableMessage } from '../../../components/ems_unavailable_message'; import { LICENSED_FEATURES } from '../../../licensed_features'; -function getErrorInfo(emsTileLayerId) { +function getErrorInfo(emsTileLayerId: string) { return i18n.translate('xpack.maps.source.emsTile.unableToFindTileIdErrorMessage', { defaultMessage: `Unable to find EMS tile configuration for id: {id}. {info}`, values: { id: emsTileLayerId, info: getEmsUnavailableMessage() }, @@ -37,8 +40,8 @@ export function getSourceTitle() { } } -export class EMSTMSSource extends AbstractTMSSource { - static createDescriptor(descriptor) { +export class EMSTMSSource extends AbstractSource implements ITMSSource { + static createDescriptor(descriptor: Partial): EMSTMSSourceDescriptor { return { type: SOURCE_TYPES.EMS_TMS, id: descriptor.id, @@ -51,12 +54,15 @@ export class EMSTMSSource extends AbstractTMSSource { }; } - constructor(descriptor, inspectorAdapters) { - descriptor = EMSTMSSource.createDescriptor(descriptor); - super(descriptor, inspectorAdapters); + readonly _descriptor: EMSTMSSourceDescriptor; + + constructor(descriptor: Partial, inspectorAdapters?: Adapters) { + const emsTmsDescriptor = EMSTMSSource.createDescriptor(descriptor); + super(emsTmsDescriptor, inspectorAdapters); + this._descriptor = emsTmsDescriptor; } - renderSourceSettingsEditor({ onChange }) { + renderSourceSettingsEditor({ onChange }: SourceEditorArgs) { return ; } @@ -100,12 +106,12 @@ export class EMSTMSSource extends AbstractTMSSource { } catch (e) { throw new Error(`${getErrorInfo(emsTileLayerId)} - ${e.message}`); } - const tmsService = emsTMSServices.find((tmsService) => tmsService.getId() === emsTileLayerId); - if (tmsService) { - return tmsService; + const tmsService = emsTMSServices.find((service) => service.getId() === emsTileLayerId); + if (!tmsService) { + throw new Error(getErrorInfo(emsTileLayerId)); } - throw new Error(getErrorInfo(emsTileLayerId)); + return tmsService; } async getDisplayName() { @@ -120,15 +126,11 @@ export class EMSTMSSource extends AbstractTMSSource { getAttributionProvider() { return async () => { const emsTMSService = await this._getEMSTMSService(); - const markdown = emsTMSService.getMarkdownAttribution(); - if (!markdown) { - return []; - } - return this.convertMarkdownLinkToObjectArr(markdown); + return emsTMSService.getAttributions(); }; } - async getUrlTemplate() { + async getUrlTemplate(): Promise { const emsTMSService = await this._getEMSTMSService(); return await emsTMSService.getUrlTemplate(); } @@ -137,13 +139,13 @@ export class EMSTMSSource extends AbstractTMSSource { return 'ems/' + this.getTileLayerId(); } - async getVectorStyleSheetAndSpriteMeta(isRetina) { + async getVectorStyleSheetAndSpriteMeta(isRetina: boolean) { const emsTMSService = await this._getEMSTMSService(); const styleSheet = await emsTMSService.getVectorStyleSheet(); const spriteMeta = await emsTMSService.getSpriteSheetMeta(isRetina); return { vectorStyleSheet: styleSheet, - spriteMeta: spriteMeta, + spriteMeta, }; } diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/index.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/index.ts similarity index 100% rename from x-pack/plugins/maps/public/classes/sources/ems_tms_source/index.js rename to x-pack/plugins/maps/public/classes/sources/ems_tms_source/index.ts diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx index 60c047b13bb7ad..c2f86a2cdb1618 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx @@ -9,15 +9,13 @@ import React, { ChangeEvent, Component } from 'react'; import { EuiSelect, EuiSelectOption, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { EMSTMSSourceDescriptor } from '../../../../common/descriptor_types'; import { getEmsTmsServices } from '../../../util'; import { getEmsUnavailableMessage } from '../../../components/ems_unavailable_message'; const AUTO_SELECT = 'auto_select'; -export interface EmsTmsSourceConfig { - id: string | null; - isAutoSelect: boolean; -} +export type EmsTmsSourceConfig = Pick; interface Props { config?: EmsTmsSourceConfig; @@ -72,7 +70,7 @@ export class TileServiceSelect extends Component { const value = e.target.value; const isAutoSelect = value === AUTO_SELECT; this.props.onTileSelect({ - id: isAutoSelect ? null : value, + id: isAutoSelect ? undefined : value, isAutoSelect, }); }; diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/update_source_editor.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/update_source_editor.tsx similarity index 71% rename from x-pack/plugins/maps/public/classes/sources/ems_tms_source/update_source_editor.js rename to x-pack/plugins/maps/public/classes/sources/ems_tms_source/update_source_editor.tsx index 1fc9addd1f08e3..6e1d5d9660eea2 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/update_source_editor.tsx @@ -8,10 +8,16 @@ import React, { Fragment } from 'react'; import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TileServiceSelect } from './tile_service_select'; +import { EmsTmsSourceConfig, TileServiceSelect } from './tile_service_select'; +import { OnSourceChangeArgs } from '../source'; -export function UpdateSourceEditor({ onChange, config }) { - const _onTileSelect = ({ id, isAutoSelect }) => { +interface Props { + onChange: (...args: OnSourceChangeArgs[]) => Promise; + config: EmsTmsSourceConfig; +} + +export function UpdateSourceEditor({ onChange, config }: Props) { + const _onTileSelect = ({ id, isAutoSelect }: EmsTmsSourceConfig) => { onChange({ propName: 'id', value: id }); onChange({ propName: 'isAutoSelect', value: isAutoSelect }); }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts index dc9637c7a76376..419ba12ed9d26c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts @@ -30,7 +30,6 @@ export interface IESAggSource extends IESSource { export abstract class AbstractESAggSource extends AbstractESSource implements IESAggSource { private readonly _metricFields: IESAggField[]; - private readonly _canReadFromGeoJson: boolean; static createDescriptor( descriptor: Partial @@ -44,23 +43,13 @@ export abstract class AbstractESAggSource extends AbstractESSource implements IE }; } - constructor( - descriptor: AbstractESAggSourceDescriptor, - inspectorAdapters?: Adapters, - canReadFromGeoJson = true - ) { + constructor(descriptor: AbstractESAggSourceDescriptor, inspectorAdapters?: Adapters) { super(descriptor, inspectorAdapters); this._metricFields = []; - this._canReadFromGeoJson = canReadFromGeoJson; if (descriptor.metrics) { descriptor.metrics.forEach((aggDescriptor: AggDescriptor) => { this._metricFields.push( - ...esAggFieldsFactory( - aggDescriptor, - this, - this.getOriginForField(), - this._canReadFromGeoJson - ) + ...esAggFieldsFactory(aggDescriptor, this, this.getOriginForField()) ); }); } @@ -89,12 +78,7 @@ export abstract class AbstractESAggSource extends AbstractESSource implements IE const metrics = this._metricFields.filter((esAggField) => esAggField.isValid()); // Handle case where metrics is empty because older saved object state is empty array or there are no valid aggs. return metrics.length === 0 - ? esAggFieldsFactory( - { type: AGG_TYPE.COUNT }, - this, - this.getOriginForField(), - this._canReadFromGeoJson - ) + ? esAggFieldsFactory({ type: AGG_TYPE.COUNT }, this, this.getOriginForField()) : metrics; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index 777787d8213f35..88ccfafe6f28f3 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -83,11 +83,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle constructor(descriptor: Partial, inspectorAdapters?: Adapters) { const sourceDescriptor = ESGeoGridSource.createDescriptor(descriptor); - super( - sourceDescriptor, - inspectorAdapters, - descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE - ); + super(sourceDescriptor, inspectorAdapters); this._descriptor = sourceDescriptor; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx index b2201a545d5cb3..19a561caf094a1 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx @@ -85,7 +85,7 @@ export class ESGeoLineSource extends AbstractESAggSource { constructor(descriptor: Partial, inspectorAdapters?: Adapters) { const sourceDescriptor = ESGeoLineSource.createDescriptor(descriptor); - super(sourceDescriptor, inspectorAdapters, true); + super(sourceDescriptor, inspectorAdapters); this._descriptor = sourceDescriptor; } @@ -140,7 +140,6 @@ export class ESGeoLineSource extends AbstractESAggSource { fieldName: this._descriptor.splitField, source: this, origin: FIELD_ORIGIN.SOURCE, - canReadFromGeoJson: true, }); } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index aad377ef53649f..baee5b78f75f32 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -152,20 +152,4 @@ describe('ESSearchSource', () => { ); }); }); - - describe('getFields', () => { - it('default', () => { - const esSearchSource = new ESSearchSource(mockDescriptor); - const docField = esSearchSource.createField({ fieldName: 'prop1' }); - expect(docField.canReadFromGeoJson()).toBe(true); - }); - it('mvt', () => { - const esSearchSource = new ESSearchSource({ - ...mockDescriptor, - scalingType: SCALING_TYPES.MVT, - }); - const docField = esSearchSource.createField({ fieldName: 'prop1' }); - expect(docField.canReadFromGeoJson()).toBe(false); - }); - }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 31cc07d03549a4..488cafd07b6942 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -154,7 +154,6 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye fieldName, source: this, origin: FIELD_ORIGIN.SOURCE, - canReadFromGeoJson: this._descriptor.scalingType !== SCALING_TYPES.MVT, }); } @@ -461,14 +460,13 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye if (!(this.indexPattern && this.indexPattern.title)) { return []; } - let success; - let matchingIndexes; try { - ({ success, matchingIndexes } = await getMatchingIndexes(this.indexPattern.title)); + const { success, matchingIndexes } = await getMatchingIndexes(this.indexPattern.title); + return success ? matchingIndexes : []; } catch (e) { // Fail silently + return []; } - return success ? matchingIndexes : []; } async supportsFeatureEditing(): Promise { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts index 75217c0a29c082..54686f91c133d3 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts @@ -5,7 +5,8 @@ * 2.0. */ -export { createLayerDescriptor, CreateLayerDescriptorParams } from './create_layer_descriptor'; +export type { CreateLayerDescriptorParams } from './create_layer_descriptor'; +export { createLayerDescriptor } from './create_layer_descriptor'; export { ESSearchSource } from './es_search_source'; export { createDefaultLayerDescriptor, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts index af39019c2d14c2..c4e12ee177f679 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts @@ -42,7 +42,10 @@ export const deleteFeatureFromIndex = async (indexName: string, featureId: strin }; export const getMatchingIndexes = async (indexPattern: string) => { - return await getHttp().fetch({ + return await getHttp().fetch<{ + success: boolean; + matchingIndexes: string[]; + }>({ path: GET_MATCHING_INDEXES_PATH, method: 'GET', query: { indexPattern }, @@ -50,7 +53,10 @@ export const getMatchingIndexes = async (indexPattern: string) => { }; export const getIsDrawLayer = async (index: string) => { - return await getHttp().fetch({ + return await getHttp().fetch<{ + success: boolean; + isDrawingIndex: boolean; + }>({ path: CHECK_IS_DRAWING_INDEX, method: 'GET', query: { index }, diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/extract_attributions.test.ts b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/extract_attributions.test.ts new file mode 100644 index 00000000000000..f8f7618b9cfc0f --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/extract_attributions.test.ts @@ -0,0 +1,28 @@ +/* + * 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 { extractAttributions } from './extract_attributions'; + +test('Should extract attributions from markdown', () => { + const markdown = + '[OpenStreetMap contributors](https://www.openstreetmap.org/copyright)|[OpenMapTiles](https://openmaptiles.org)|[Elastic Maps Service](https://www.elastic.co/elastic-maps-service)'; + const attributions = extractAttributions(markdown); + expect(attributions).toEqual([ + { + label: 'OpenStreetMap contributors', + url: 'https://www.openstreetmap.org/copyright', + }, + { + label: 'OpenMapTiles', + url: 'https://openmaptiles.org', + }, + { + label: 'Elastic Maps Service', + url: 'https://www.elastic.co/elastic-maps-service', + }, + ]); +}); diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/extract_attributions.ts b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/extract_attributions.ts new file mode 100644 index 00000000000000..86268f5c8c5cfb --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/extract_attributions.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 { Attribution } from '../../../../common/descriptor_types'; + +export function extractAttributions(markdown: string): Attribution[] { + const attributions: Attribution[] = []; + markdown.split('|').forEach((attribution: string) => { + attribution = attribution.trim(); + // this assumes attribution is plain markdown link + const extractLink = /\[(.*)\]\((.*)\)/; + const result = extractLink.exec(attribution); + if (result && result?.length >= 3 && result[1] && result[2]) { + attributions.push({ + label: result[1], + url: result[2], + }); + } + }); + return attributions; +} diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js index cceb3ddd1fcc0c..db0b5359ca56e2 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js @@ -5,19 +5,20 @@ * 2.0. */ -import { AbstractTMSSource } from '../tms_source'; +import { AbstractSource } from '../source'; import { getKibanaTileMap } from '../../../util'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import _ from 'lodash'; import { SOURCE_TYPES } from '../../../../common/constants'; import { registerSource } from '../source_registry'; +import { extractAttributions } from './extract_attributions'; export const sourceTitle = i18n.translate('xpack.maps.source.kbnTMSTitle', { defaultMessage: 'Configured Tile Map Service', }); -export class KibanaTilemapSource extends AbstractTMSSource { +export class KibanaTilemapSource extends AbstractSource { static type = SOURCE_TYPES.KIBANA_TILEMAP; static createDescriptor() { @@ -57,8 +58,7 @@ export class KibanaTilemapSource extends AbstractTMSSource { return async () => { const tilemap = getKibanaTileMap(); const markdown = _.get(tilemap, 'options.attribution', ''); - const objArr = this.convertMarkdownLinkToObjectArr(markdown); - return objArr; + return extractAttributions(markdown); }; } diff --git a/x-pack/plugins/maps/public/classes/sources/term_join_source/index.ts b/x-pack/plugins/maps/public/classes/sources/term_join_source/index.ts index a40533d98be87e..78f7705104f733 100644 --- a/x-pack/plugins/maps/public/classes/sources/term_join_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/term_join_source/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { ITermJoinSource } from './term_join_source'; +export type { ITermJoinSource } from './term_join_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/index.ts b/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/index.ts index 30177751a8d555..c60cedba61c83d 100644 --- a/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { ITiledSingleLayerVectorSource } from './tiled_single_layer_vector_source'; +export type { ITiledSingleLayerVectorSource } from './tiled_single_layer_vector_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/tms_source/index.ts b/x-pack/plugins/maps/public/classes/sources/tms_source/index.ts index cc416cc94027b5..b3d88670469b15 100644 --- a/x-pack/plugins/maps/public/classes/sources/tms_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/tms_source/index.ts @@ -5,4 +5,8 @@ * 2.0. */ -export * from './tms_source'; +import { ISource } from '../source'; + +export interface ITMSSource extends ISource { + getUrlTemplate(): Promise; +} diff --git a/x-pack/plugins/maps/public/classes/sources/tms_source/tms_source.d.ts b/x-pack/plugins/maps/public/classes/sources/tms_source/tms_source.d.ts deleted file mode 100644 index edd335fd5507b1..00000000000000 --- a/x-pack/plugins/maps/public/classes/sources/tms_source/tms_source.d.ts +++ /dev/null @@ -1,16 +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 { AbstractSource, ISource } from '../source'; - -export interface ITMSSource extends ISource { - getUrlTemplate(): Promise; -} - -export class AbstractTMSSource extends AbstractSource implements ITMSSource { - getUrlTemplate(): Promise; -} diff --git a/x-pack/plugins/maps/public/classes/sources/tms_source/tms_source.js b/x-pack/plugins/maps/public/classes/sources/tms_source/tms_source.js deleted file mode 100644 index 9b45e77468db62..00000000000000 --- a/x-pack/plugins/maps/public/classes/sources/tms_source/tms_source.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AbstractSource } from '../source'; - -export class AbstractTMSSource extends AbstractSource { - async getUrlTemplate() { - throw new Error('Should implement TMSSource#getUrlTemplate'); - } - - convertMarkdownLinkToObjectArr(markdown) { - return markdown.split('|').map((attribution) => { - attribution = attribution.trim(); - //this assumes attribution is plain markdown link - const extractLink = /\[(.*)\]\((.*)\)/; - const result = extractLink.exec(attribution); - return { - label: result ? result[1] : null, - url: result ? result[2] : null, - }; - }); - } -} diff --git a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js index 0077602c9e00b7..b884785d348aa1 100644 --- a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js +++ b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js @@ -5,7 +5,7 @@ * 2.0. */ -import { AbstractTMSSource } from '../tms_source'; +import { AbstractSource } from '../source'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters'; import { WmsClient } from './wms_client'; @@ -16,7 +16,7 @@ export const sourceTitle = i18n.translate('xpack.maps.source.wmsTitle', { defaultMessage: 'Web Map Service', }); -export class WMSSource extends AbstractTMSSource { +export class WMSSource extends AbstractSource { static type = SOURCE_TYPES.WMS; static createDescriptor({ serviceUrl, layers, styles }) { diff --git a/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_source.ts b/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_source.ts index 4dd81b77668f41..01f77e4a45c387 100644 --- a/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_source.ts @@ -9,16 +9,16 @@ import { i18n } from '@kbn/i18n'; import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters'; import { SOURCE_TYPES } from '../../../../common/constants'; import { registerSource } from '../source_registry'; -import { AbstractTMSSource } from '../tms_source'; +import { ITMSSource } from '../tms_source'; import { XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; -import { ImmutableSourceProperty } from '../source'; +import { AbstractSource, ImmutableSourceProperty } from '../source'; import { XYZTMSSourceConfig } from './xyz_tms_editor'; export const sourceTitle = i18n.translate('xpack.maps.source.ems_xyzTitle', { defaultMessage: 'Tile Map Service', }); -export class XYZTMSSource extends AbstractTMSSource { +export class XYZTMSSource extends AbstractSource implements ITMSSource { static type = SOURCE_TYPES.EMS_XYZ; readonly _descriptor: XYZTMSSourceDescriptor; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/data_mapping/categorical_data_mapping_popover.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/data_mapping/categorical_data_mapping_popover.tsx index 3fb36364bee4ce..ee2cabb7c0794c 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/data_mapping/categorical_data_mapping_popover.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/data_mapping/categorical_data_mapping_popover.tsx @@ -15,6 +15,7 @@ import { FieldMetaOptions } from '../../../../../../common/descriptor_types'; interface Props { fieldMetaOptions: FieldMetaOptions; onChange: (updatedOptions: DynamicOptions) => void; + supportsFieldMetaFromLocalData: boolean; } export function CategoricalDataMappingPopover(props: Props) { @@ -38,6 +39,7 @@ export function CategoricalDataMappingPopover(props: Props{' '} { onChange: (updatedOptions: DynamicOptions) => void; dataMappingFunction: DATA_MAPPING_FUNCTION; supportedDataMappingFunctions: DATA_MAPPING_FUNCTION[]; + supportsFieldMetaFromLocalData: boolean; } export function OrdinalDataMappingPopover(props: Props) { @@ -167,6 +168,7 @@ export function OrdinalDataMappingPopover(props: Props{' '} ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx index 4ec4948170c9c9..b89f4ee0b2aa0c 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx @@ -28,7 +28,11 @@ jest.mock('../../../../kibana_services', () => { }; }); -class MockField extends AbstractField {} +class MockField extends AbstractField { + supportsFieldMetaFromLocalData(): boolean { + return true; + } +} function createLayerMock(numFields: number, supportedShapeTypes: VECTOR_SHAPE_TYPE[]) { const fields: IField[] = []; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap index 5b43c5fb955601..58af4d009e43af 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap @@ -16,6 +16,7 @@ exports[`renderDataMappingPopover Should render OrdinalDataMappingPopover 1`] = "PERCENTILES", ] } + supportsFieldMetaFromLocalData={true} /> `; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx index 9aaa71a11f8f18..e0ccb80273c9d9 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx @@ -18,6 +18,7 @@ import { Feature, Point } from 'geojson'; import { DynamicColorProperty } from './dynamic_color_property'; import { COLOR_MAP_TYPE, + FIELD_ORIGIN, RawValue, DATA_MAPPING_FUNCTION, VECTOR_STYLES, @@ -291,17 +292,27 @@ describe('supportsFieldMeta', () => { expect(styleProp.supportsFieldMeta()).toEqual(true); }); - test('should not support fieldMeta when field does not support fieldMeta', () => { - const field = Object.create(mockField); - field.supportsFieldMeta = function () { - return false; - }; - - const dynamicStyleOptions = { + test('should not support fieldMeta when field does not support fieldMeta from ES', () => { + const field = { + supportsFieldMetaFromEs() { + return false; + }, + } as unknown as IField; + const layer = {} as unknown as IVectorLayer; + const options = { type: COLOR_MAP_TYPE.ORDINAL, - fieldMetaOptions, + fieldMetaOptions: { isEnabled: true }, }; - const styleProp = makeProperty(dynamicStyleOptions, undefined, field); + + const styleProp = new DynamicColorProperty( + options, + VECTOR_STYLES.LINE_COLOR, + field, + layer, + () => { + return (value: RawValue) => value + '_format'; + } + ); expect(styleProp.supportsFieldMeta()).toEqual(false); }); @@ -382,12 +393,50 @@ describe('get mapbox color expression (via internal _getMbColor)', () => { expect(colorProperty._getMbColor()).toBeNull(); }); test('should return mapbox expression for color ramp', async () => { - const dynamicStyleOptions = { + const field = { + getMbFieldName: () => { + return 'foobar'; + }, + getName: () => { + return 'foobar'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMetaFromEs: () => { + return true; + }, + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; + }, + } as unknown as IField; + const options = { type: COLOR_MAP_TYPE.ORDINAL, color: 'Blues', - fieldMetaOptions, + fieldMetaOptions: { isEnabled: true }, }; - const colorProperty = makeProperty(dynamicStyleOptions); + + const colorProperty = new DynamicColorProperty( + options, + VECTOR_STYLES.LINE_COLOR, + field, + {} as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + } + ); + colorProperty.getRangeFieldMeta = () => { + return { + min: 0, + max: 100, + delta: 100, + }; + }; + expect(colorProperty._getMbColor()).toEqual([ 'interpolate', ['linear'], @@ -445,17 +494,40 @@ describe('get mapbox color expression (via internal _getMbColor)', () => { expect(colorProperty._getMbColor()).toBeNull(); }); - test('should use `feature-state` by default', async () => { - const dynamicStyleOptions = { + test('should use `feature-state` for geojson source', async () => { + const field = { + getMbFieldName: () => { + return 'foobar'; + }, + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; + }, + } as unknown as IField; + const layer = {} as unknown as IVectorLayer; + const options = { type: COLOR_MAP_TYPE.ORDINAL, useCustomColorRamp: true, customColorRamp: [ { stop: 10, color: '#f7faff' }, { stop: 100, color: '#072f6b' }, ], - fieldMetaOptions, + fieldMetaOptions: { isEnabled: true }, }; - const colorProperty = makeProperty(dynamicStyleOptions); + + const colorProperty = new DynamicColorProperty( + options, + VECTOR_STYLES.LINE_COLOR, + field, + layer, + () => { + return (value: RawValue) => value + '_format'; + } + ); + expect(colorProperty._getMbColor()).toEqual([ 'step', [ @@ -476,21 +548,40 @@ describe('get mapbox color expression (via internal _getMbColor)', () => { ]); }); - test('should use `get` when source cannot return raw geojson', async () => { - const field = Object.create(mockField); - field.canReadFromGeoJson = function () { - return false; - }; - const dynamicStyleOptions = { + test('should use `get` for MVT source', async () => { + const field = { + getMbFieldName: () => { + return 'foobar'; + }, + getSource: () => { + return { + isMvt: () => { + return true; + }, + }; + }, + } as unknown as IField; + const layer = {} as unknown as IVectorLayer; + const options = { type: COLOR_MAP_TYPE.ORDINAL, useCustomColorRamp: true, customColorRamp: [ { stop: 10, color: '#f7faff' }, { stop: 100, color: '#072f6b' }, ], - fieldMetaOptions, + fieldMetaOptions: { isEnabled: true }, }; - const colorProperty = makeProperty(dynamicStyleOptions, undefined, field); + + const colorProperty = new DynamicColorProperty( + options, + VECTOR_STYLES.LINE_COLOR, + field, + layer, + () => { + return (value: RawValue) => value + '_format'; + } + ); + expect(colorProperty._getMbColor()).toEqual([ 'step', [ diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx index bff053fc469a01..cfb5d54720ce7c 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx @@ -101,7 +101,7 @@ export class DynamicColorProperty extends DynamicStyleProperty ({ import React from 'react'; import { shallow } from 'enzyme'; -// @ts-ignore import { DynamicSizeProperty } from './dynamic_size_property'; -import { RawValue, VECTOR_STYLES } from '../../../../../common/constants'; +import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../common/constants'; import { IField } from '../../../fields/field'; import type { Map as MbMap } from '@kbn/mapbox-gl'; -import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; -import { mockField, MockLayer, MockStyle } from './test_helpers/test_util'; import { IVectorLayer } from '../../../layers/vector_layer'; export class MockMbMap { @@ -38,31 +35,40 @@ export class MockMbMap { } } -const makeProperty = ( - options: SizeDynamicOptions, - mockStyle: MockStyle, - field: IField = mockField -) => { - return new DynamicSizeProperty( - options, - VECTOR_STYLES.ICON_SIZE, - field, - new MockLayer(mockStyle) as unknown as IVectorLayer, - () => { - return (value: RawValue) => value + '_format'; - }, - false - ); -}; - -const fieldMetaOptions = { isEnabled: true }; - describe('renderLegendDetailRow', () => { test('Should render as range', async () => { - const sizeProp = makeProperty( - { minSize: 0, maxSize: 10, fieldMetaOptions }, - new MockStyle({ min: 0, max: 100 }) + const field = { + getLabel: async () => { + return 'foobar_label'; + }, + getName: () => { + return 'foodbar'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMetaFromEs: () => { + return true; + }, + } as unknown as IField; + const sizeProp = new DynamicSizeProperty( + { minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } }, + VECTOR_STYLES.ICON_SIZE, + field, + {} as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + }, + false ); + sizeProp.getRangeFieldMeta = () => { + return { + min: 0, + max: 100, + delta: 100, + }; + }; + const legendRow = sizeProp.renderLegendDetailRow(); const component = shallow(legendRow); @@ -76,10 +82,47 @@ describe('renderLegendDetailRow', () => { describe('syncSize', () => { test('Should sync with circle-radius prop', async () => { - const sizeProp = makeProperty( - { minSize: 8, maxSize: 32, fieldMetaOptions }, - new MockStyle({ min: 0, max: 100 }) + const field = { + isValid: () => { + return true; + }, + getName: () => { + return 'foodbar'; + }, + getMbFieldName: () => { + return 'foobar'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; + }, + supportsFieldMetaFromEs: () => { + return true; + }, + } as unknown as IField; + const sizeProp = new DynamicSizeProperty( + { minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } }, + VECTOR_STYLES.ICON_SIZE, + field, + {} as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + }, + false ); + sizeProp.getRangeFieldMeta = () => { + return { + min: 0, + max: 100, + delta: 100, + }; + }; const mockMbMap = new MockMbMap() as unknown as MbMap; sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); @@ -112,10 +155,47 @@ describe('syncSize', () => { }); test('Should truncate interpolate expression to max when no delta', async () => { - const sizeProp = makeProperty( - { minSize: 8, maxSize: 32, fieldMetaOptions }, - new MockStyle({ min: 100, max: 100 }) + const field = { + isValid: () => { + return true; + }, + getName: () => { + return 'foobar'; + }, + getMbFieldName: () => { + return 'foobar'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; + }, + supportsFieldMetaFromEs: () => { + return true; + }, + } as unknown as IField; + const sizeProp = new DynamicSizeProperty( + { minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } }, + VECTOR_STYLES.ICON_SIZE, + field, + {} as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + }, + false ); + sizeProp.getRangeFieldMeta = () => { + return { + min: 100, + max: 100, + delta: 0, + }; + }; const mockMbMap = new MockMbMap() as unknown as MbMap; sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index 54e3744472fa14..577fe60aa2e139 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -260,7 +260,7 @@ export class DynamicStyleProperty } supportsFieldMeta() { - return this.isComplete() && !!this._field && this._field.supportsFieldMeta(); + return this.isComplete() && !!this._field && this._field.supportsFieldMetaFromEs(); } async getFieldMetaRequest() { @@ -287,7 +287,7 @@ export class DynamicStyleProperty } supportsMbFeatureState() { - return !!this._field && this._field.canReadFromGeoJson(); + return !!this._field && !this._field.getSource().isMvt(); } getMbLookupFunction(): MB_LOOKUP_FUNCTION { @@ -309,24 +309,17 @@ export class DynamicStyleProperty pluckOrdinalStyleMetaFromTileMetaFeatures( metaFeatures: TileMetaFeature[] ): RangeFieldMeta | null { - if (!this.isOrdinal()) { + if (!this._field || !this.isOrdinal()) { return null; } - const mbFieldName = this.getMbFieldName(); let min = Infinity; let max = -Infinity; for (let i = 0; i < metaFeatures.length; i++) { - const fieldMeta = metaFeatures[i].properties; - const minField = `aggregations.${mbFieldName}.min`; - const maxField = `aggregations.${mbFieldName}.max`; - if ( - fieldMeta && - typeof fieldMeta[minField] === 'number' && - typeof fieldMeta[maxField] === 'number' - ) { - min = Math.min(fieldMeta[minField] as number, min); - max = Math.max(fieldMeta[maxField] as number, max); + const range = this._field.pluckRangeFromTileMetaFeature(metaFeatures[i]); + if (range) { + min = Math.min(range.min, min); + max = Math.max(range.max, max); } } @@ -466,13 +459,14 @@ export class DynamicStyleProperty } renderDataMappingPopover(onChange: (updatedOptions: Partial) => void) { - if (!this.supportsFieldMeta()) { + if (!this._field || !this.supportsFieldMeta()) { return null; } return this.isCategorical() ? ( fieldMetaOptions={this.getFieldMetaOptions()} onChange={onChange} + supportsFieldMetaFromLocalData={this._field.supportsFieldMetaFromLocalData()} /> ) : ( @@ -481,6 +475,7 @@ export class DynamicStyleProperty onChange={onChange} dataMappingFunction={this.getDataMappingFunction()} supportedDataMappingFunctions={this._getSupportedDataMappingFunctions()} + supportsFieldMetaFromLocalData={this._field.supportsFieldMetaFromLocalData()} /> ); } @@ -502,7 +497,7 @@ export class DynamicStyleProperty // They just re-use the original property-name targetName = this._field.getName(); } else { - if (this._field.canReadFromGeoJson() && this._field.supportsAutoDomain()) { + if (!this._field.getSource().isMvt() && this._field.supportsFieldMetaFromLocalData()) { // Geojson-sources can support rewrite // e.g. field-formatters will create duplicate field targetName = getComputedFieldName(this.getStyleName(), this._field.getName()); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx index 2b90292ccc75a3..1dce8934e325d1 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx @@ -18,7 +18,7 @@ import { DynamicTextProperty } from './dynamic_text_property'; import { RawValue, VECTOR_STYLES } from '../../../../../common/constants'; import { IField } from '../../../fields/field'; import type { Map as MbMap } from '@kbn/mapbox-gl'; -import { mockField, MockLayer, MockStyle } from './test_helpers/test_util'; +import { MockLayer, MockStyle } from './test_helpers/test_util'; import { IVectorLayer } from '../../../layers/vector_layer'; export class MockMbMap { @@ -49,22 +49,37 @@ export class MockMbMap { } } -const makeProperty = (mockStyle: MockStyle, field: IField | null) => { - return new DynamicTextProperty( - {}, - VECTOR_STYLES.LABEL_TEXT, - field, - new MockLayer(mockStyle) as unknown as IVectorLayer, - () => { - return (value: RawValue) => value + '_format'; - } - ); -}; - describe('syncTextFieldWithMb', () => { describe('with field', () => { - test('Should set', async () => { - const dynamicTextProperty = makeProperty(new MockStyle({ min: 0, max: 100 }), mockField); + test('Should set text-field', async () => { + const field = { + isValid: () => { + return true; + }, + getName: () => { + return 'foobar'; + }, + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; + }, + supportsFieldMetaFromLocalData: () => { + return true; + }, + } as unknown as IField; + const dynamicTextProperty = new DynamicTextProperty( + {}, + VECTOR_STYLES.LABEL_TEXT, + field, + new MockLayer(new MockStyle({ min: 0, max: 100 })) as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + } + ); + const mockMbMap = new MockMbMap() as unknown as MbMap; dynamicTextProperty.syncTextFieldWithMb('foobar', mockMbMap); @@ -77,8 +92,17 @@ describe('syncTextFieldWithMb', () => { }); describe('without field', () => { - test('Should clear', async () => { - const dynamicTextProperty = makeProperty(new MockStyle({ min: 0, max: 100 }), null); + test('Should clear text-field', async () => { + const dynamicTextProperty = new DynamicTextProperty( + {}, + VECTOR_STYLES.LABEL_TEXT, + null, + new MockLayer(new MockStyle({ min: 0, max: 100 })) as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + } + ); + const mockMbMap = new MockMbMap([ 'foobar', ['coalesce', ['get', '__kbn__dynamic__foobar__labelText'], ''], @@ -90,14 +114,22 @@ describe('syncTextFieldWithMb', () => { expect(mockMbMap.getPaintPropertyCalls()).toEqual([['foobar', undefined]]); }); - test('Should not clear when already cleared', async () => { + test('Should not set or clear text-field', async () => { // This verifies a weird edge-case in mapbox-gl, where setting the `text-field` layout-property to null causes tiles to be invalidated. // This triggers a refetch of the tile during panning and zooming // This affects vector-tile rendering in tiled_vector_layers with custom vector_styles // It does _not_ affect EMS, since that does not have a code-path where a `text-field` need to be resynced. // Do not remove this logic without verifying that mapbox-gl does not re-issue tile-requests for previously requested tiles - const dynamicTextProperty = makeProperty(new MockStyle({ min: 0, max: 100 }), null); + const dynamicTextProperty = new DynamicTextProperty( + {}, + VECTOR_STYLES.LABEL_TEXT, + null, + new MockLayer(new MockStyle({ min: 0, max: 100 })) as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + } + ); const mockMbMap = new MockMbMap(undefined) as unknown as MbMap; dynamicTextProperty.syncTextFieldWithMb('foobar', mockMbMap); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/test_helpers/test_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/test_helpers/test_util.ts index ff2811f2b3df0c..9d6560ecb8888b 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/test_helpers/test_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/test_helpers/test_util.ts @@ -19,22 +19,22 @@ import { IStyle } from '../../../style'; export class MockField extends AbstractField { private readonly _dataType: string; - private readonly _supportsAutoDomain: boolean; + private readonly _supportsFieldMetaFromLocalData: boolean; constructor({ fieldName, origin = FIELD_ORIGIN.SOURCE, dataType = 'string', - supportsAutoDomain = true, + supportsFieldMetaFromLocalData = true, }: { fieldName: string; origin?: FIELD_ORIGIN; dataType?: string; - supportsAutoDomain?: boolean; + supportsFieldMetaFromLocalData?: boolean; }) { super({ fieldName, origin }); this._dataType = dataType; - this._supportsAutoDomain = supportsAutoDomain; + this._supportsFieldMetaFromLocalData = supportsFieldMetaFromLocalData; } async getLabel(): Promise { @@ -45,11 +45,11 @@ export class MockField extends AbstractField { return this._dataType; } - supportsAutoDomain(): boolean { - return this._supportsAutoDomain; + supportsFieldMetaFromLocalData(): boolean { + return this._supportsFieldMetaFromLocalData; } - supportsFieldMeta(): boolean { + supportsFieldMetaFromEs(): boolean { return true; } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts index 4407620f09ce5e..2b9d37fdba78b2 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts @@ -7,45 +7,76 @@ import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../common/constants'; import { createStyleFieldsHelper, StyleFieldsHelper } from './style_fields_helper'; -import { AbstractField, IField } from '../../fields/field'; - -class MockField extends AbstractField { - private readonly _dataType: string; - private readonly _supportsAutoDomain: boolean; - constructor({ dataType, supportsAutoDomain }: { dataType: string; supportsAutoDomain: boolean }) { - super({ fieldName: 'foobar_' + dataType, origin: FIELD_ORIGIN.SOURCE }); - this._dataType = dataType; - this._supportsAutoDomain = supportsAutoDomain; - } - async getDataType() { - return this._dataType; - } - - supportsAutoDomain(): boolean { - return this._supportsAutoDomain; - } -} +import { IField } from '../../fields/field'; describe('StyleFieldHelper', () => { describe('isFieldDataTypeCompatibleWithStyleType', () => { - async function createHelper(supportsAutoDomain: boolean): Promise<{ + async function createHelper(supportsFieldMetaFromLocalData: boolean): Promise<{ styleFieldHelper: StyleFieldsHelper; stringField: IField; numberField: IField; dateField: IField; }> { - const stringField = new MockField({ - dataType: 'string', - supportsAutoDomain, - }); - const numberField = new MockField({ - dataType: 'number', - supportsAutoDomain, - }); - const dateField = new MockField({ - dataType: 'date', - supportsAutoDomain, - }); + const stringField = { + getDataType: async () => { + return 'string'; + }, + getLabel: async () => { + return 'foobar_string_label'; + }, + getName: () => { + return 'foobar_string'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMetaFromLocalData: () => { + return supportsFieldMetaFromLocalData; + }, + supportsFieldMetaFromEs: () => { + return false; + }, + } as unknown as IField; + const numberField = { + getDataType: async () => { + return 'number'; + }, + getLabel: async () => { + return 'foobar_number_label'; + }, + getName: () => { + return 'foobar_number'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMetaFromLocalData: () => { + return supportsFieldMetaFromLocalData; + }, + supportsFieldMetaFromEs: () => { + return false; + }, + } as unknown as IField; + const dateField = { + getDataType: async () => { + return 'date'; + }, + getLabel: async () => { + return 'foobar_date_label'; + }, + getName: () => { + return 'foobar_date'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMetaFromLocalData: () => { + return supportsFieldMetaFromLocalData; + }, + supportsFieldMetaFromEs: () => { + return false; + }, + } as unknown as IField; return { styleFieldHelper: await createStyleFieldsHelper([stringField, numberField, dateField]), stringField, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts index 125d6bd9cb3c2c..9a1cb67c7bfd9e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts @@ -28,7 +28,7 @@ export async function createStyleFieldsHelper(fields: IField[]): Promise { const vectorStyle = new VectorStyle({ properties }, new MockSource()); const nextFields = [ - new MockField({ - fieldName: previousFieldName, - dataType: 'number', - supportsAutoDomain: false, - }), + { + getDataType: async () => { + return 'number'; + }, + getLabel: async () => { + return previousFieldName + '_label'; + }, + getName: () => { + return previousFieldName; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + // ordinal field must support auto domain + supportsFieldMetaFromLocalData: () => { + return false; + }, + supportsFieldMetaFromEs: () => { + return false; + }, + }, ]; const { hasChanges, nextStyleDescriptor } = await vectorStyle.getDescriptorWithUpdatedStyleProps(nextFields, previousFields, mapColors); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index 058ee0db08d359..0f17cfb18446a9 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -865,8 +865,6 @@ export class VectorStyle implements IVectorStyle { mbMap: MbMap; textLayerId: string; }) { - mbMap.setLayoutProperty(textLayerId, 'icon-allow-overlap', true); - mbMap.setLayoutProperty(textLayerId, 'text-allow-overlap', true); this._labelStyleProperty.syncTextFieldWithMb(textLayerId, mbMap); this._labelColorStyleProperty.syncLabelColorWithMb(textLayerId, mbMap, alpha); this._labelSizeStyleProperty.syncLabelSizeWithMb(textLayerId, mbMap); @@ -885,6 +883,7 @@ export class VectorStyle implements IVectorStyle { }) { mbMap.setLayoutProperty(symbolLayerId, 'icon-ignore-placement', true); mbMap.setPaintProperty(symbolLayerId, 'icon-opacity', alpha); + mbMap.setLayoutProperty(symbolLayerId, 'icon-allow-overlap', true); this._iconStyleProperty.syncIconWithMb( symbolLayerId, diff --git a/x-pack/plugins/maps/public/classes/util/geo_tile_utils.ts b/x-pack/plugins/maps/public/classes/util/geo_tile_utils.ts index 6e82d3b5095654..36c7d9d6c4a113 100644 --- a/x-pack/plugins/maps/public/classes/util/geo_tile_utils.ts +++ b/x-pack/plugins/maps/public/classes/util/geo_tile_utils.ts @@ -89,24 +89,6 @@ export function tile2lat(y: number, z: number): number { return tileToLatitude(y, tileCount); } -export function tileToESBbox(x: number, y: number, z: number): ESBounds { - const wLon = tile2long(x, z); - const sLat = tile2lat(y + 1, z); - const eLon = tile2long(x + 1, z); - const nLat = tile2lat(y, z); - - return { - top_left: { - lon: wLon, - lat: nLat, - }, - bottom_right: { - lon: eLon, - lat: sLat, - }, - }; -} - export function tileToLatitude(y: number, tileCount: number) { const radians = Math.atan(sinh(Math.PI - (2 * Math.PI * y) / tileCount)); const lat = (180 / Math.PI) * radians; diff --git a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts index 544b2697cab439..ed6c01ac32dfab 100644 --- a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts +++ b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts @@ -54,7 +54,7 @@ export function getFillFilterExpression( ): unknown[] { return getFilterExpression( [ - EXCLUDE_CENTROID_FEATURES, + // explicit EXCLUDE_CENTROID_FEATURES filter not needed. Centroids are points and are filtered out by geometry narrowing [ 'any', ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], @@ -72,7 +72,7 @@ export function getLineFilterExpression( ): unknown[] { return getFilterExpression( [ - EXCLUDE_CENTROID_FEATURES, + // explicit EXCLUDE_CENTROID_FEATURES filter not needed. Centroids are points and are filtered out by geometry narrowing [ 'any', ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], @@ -86,19 +86,18 @@ export function getLineFilterExpression( ); } +const IS_POINT_FEATURE = [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], +]; + export function getPointFilterExpression( hasJoins: boolean, timesliceMaskConfig?: TimesliceMaskConfig ): unknown[] { return getFilterExpression( - [ - EXCLUDE_CENTROID_FEATURES, - [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], - ], - ], + [EXCLUDE_CENTROID_FEATURES, IS_POINT_FEATURE], hasJoins, timesliceMaskConfig ); @@ -111,9 +110,11 @@ export function getLabelFilterExpression( ): unknown[] { const filters: unknown[] = []; - // centroids added for geojson sources only if (isSourceGeoJson) { - filters.push(['==', ['get', KBN_IS_CENTROID_FEATURE], true]); + // Centroid feature added to GeoJSON feature collection for LINE_STRING, MULTI_LINE_STRING, POLYGON, MULTI_POLYGON, and GEOMETRY_COLLECTION geometries + // For GeoJSON sources, show label for centroid features or point/multi-point features only. + // no explicit isCentroidFeature filter is needed, centroids are points and are included in the geometry filter. + filters.push(IS_POINT_FEATURE); } return getFilterExpression(filters, hasJoins, timesliceMaskConfig); diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/index.ts b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/index.ts index 7971017a3d52b6..570c1ba8fb896c 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/index.ts +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/index.ts @@ -31,4 +31,4 @@ function mapDispatchToProps(dispatch: ThunkDispatch = ({ coreStart, deps, appMountParams }) => { const pageDeps = { history: appMountParams.history, - indexPatterns: deps.data.indexPatterns, + dataViewsContract: deps.data.dataViews, config: coreStart.uiSettings!, setBreadcrumbs: coreStart.chrome!.setBreadcrumbs, redirectToMlAccessDeniedPage, diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js b/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js index 1f2236ad3e6a7a..0059bec2929d02 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js @@ -12,6 +12,7 @@ import React, { Component } from 'react'; import { EuiLink, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { blurButtonOnClick } from '../../util/component_utils'; /* * Component for rendering a list of record influencers inside a cell in the anomalies table. @@ -59,13 +60,13 @@ export class InfluencersCell extends Component { + onClick={blurButtonOnClick(() => { influencerFilter( influencer.influencerFieldName, influencer.influencerFieldValue, '+' - ) - } + ); + })} iconType="plusInCircle" aria-label={i18n.translate( 'xpack.ml.anomaliesTable.influencersCell.addFilterAriaLabel', @@ -86,13 +87,13 @@ export class InfluencersCell extends Component { + onClick={blurButtonOnClick(() => { influencerFilter( influencer.influencerFieldName, influencer.influencerFieldValue, '-' - ) - } + ); + })} iconType="minusInCircle" aria-label={i18n.translate( 'xpack.ml.anomaliesTable.influencersCell.removeFilterAriaLabel', diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js index 18fc10e69bc052..b0561893eb19f9 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -28,7 +28,7 @@ import { ml } from '../../services/ml_api_service'; import { mlJobService } from '../../services/job_service'; import { getUrlForRecord, openCustomUrlWindow } from '../../util/custom_url_utils'; import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils'; -import { getIndexPatternIdFromName } from '../../util/index_utils'; +import { getDataViewIdFromName } from '../../util/index_utils'; import { replaceStringTokens } from '../../util/string_utils'; import { ML_APP_LOCATOR, ML_PAGES } from '../../../../common/constants/locator'; /* @@ -258,18 +258,18 @@ class LinksMenuUI extends Component { }; const createAndOpenUrl = (index, categorizationFieldType) => { - // Find the ID of the data view with a title attribute which matches the - // index configured in the datafeed. If a Kibana data view has not been created - // for this index, then the user will see a warning message on the Discover tab advising - // them that no matching data view has been configured. - const indexPatternId = getIndexPatternIdFromName(index) || index; - // Get the definition of the category and use the terms or regex to view the // matching events in the Kibana Discover tab depending on whether the // categorization field is of mapping type text (preferred) or keyword. ml.results .getCategoryDefinition(record.job_id, categoryId) - .then((resp) => { + .then(async (resp) => { + // Find the ID of the data view with a title attribute which matches the + // index configured in the datafeed. If a Kibana data view has not been created + // for this index, then the user will see a warning message on the Discover tab advising + // them that no matching data view has been configured. + const dataViewId = (await getDataViewIdFromName(index)) ?? index; + let query = null; // Build query using categorization regex (if keyword type) or terms (if text type). // Check for terms or regex in case categoryId represents an anomaly from the absence of the @@ -313,7 +313,7 @@ class LinksMenuUI extends Component { }); const appStateProps = { - index: indexPatternId, + index: dataViewId, filters: [], }; if (query !== null) { diff --git a/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts b/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts index 37495d0ef739e9..e72a835cb1f63a 100644 --- a/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts +++ b/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts @@ -6,6 +6,7 @@ */ export { ColorRangeLegend } from './color_range_legend'; +export type { EuiThemeType } from './use_color_range'; export { colorRangeOptions, colorRangeScaleOptions, @@ -13,5 +14,4 @@ export { COLOR_RANGE, COLOR_RANGE_SCALE, useCurrentEuiTheme, - EuiThemeType, } from './use_color_range'; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts index 481ff432e0156a..b2bd1ff228923d 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts @@ -19,7 +19,7 @@ export { export { getFieldType } from './use_column_chart'; export { useDataGrid } from './use_data_grid'; export { DataGrid } from './data_grid'; -export { +export type { DataGridItem, EsSorting, RenderCellValue, diff --git a/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.tsx b/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.tsx index a79c8a63b3bc6d..f4a3b6dbf69c4a 100644 --- a/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.tsx +++ b/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.tsx @@ -13,6 +13,7 @@ import { i18n } from '@kbn/i18n'; import { EMPTY_FIELD_VALUE_LABEL } from '../../timeseriesexplorer/components/entity_control/entity_control'; import { MLCATEGORY } from '../../../../common/constants/field_types'; import { ENTITY_FIELD_OPERATIONS } from '../../../../common/util/anomaly_utils'; +import { blurButtonOnClick } from '../../util/component_utils'; export type EntityCellFilter = ( entityName: string, @@ -41,7 +42,9 @@ function getAddFilter({ entityName, entityValue, filter }: EntityCellProps) { filter(entityName, entityValue, ENTITY_FIELD_OPERATIONS.ADD)} + onClick={blurButtonOnClick(() => { + filter(entityName, entityValue, ENTITY_FIELD_OPERATIONS.ADD); + })} iconType="plusInCircle" aria-label={i18n.translate('xpack.ml.anomaliesTable.entityCell.addFilterAriaLabel', { defaultMessage: 'Add filter', @@ -66,7 +69,9 @@ function getRemoveFilter({ entityName, entityValue, filter }: EntityCellProps) { filter(entityName, entityValue, ENTITY_FIELD_OPERATIONS.REMOVE)} + onClick={blurButtonOnClick(() => { + filter(entityName, entityValue, ENTITY_FIELD_OPERATIONS.REMOVE); + })} iconType="minusInCircle" aria-label={i18n.translate('xpack.ml.anomaliesTable.entityCell.removeFilterAriaLabel', { defaultMessage: 'Remove filter', diff --git a/x-pack/plugins/ml/public/application/components/entity_cell/index.ts b/x-pack/plugins/ml/public/application/components/entity_cell/index.ts index d29e2adf66bfe8..e0d57bce48514d 100644 --- a/x-pack/plugins/ml/public/application/components/entity_cell/index.ts +++ b/x-pack/plugins/ml/public/application/components/entity_cell/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { EntityCell, EntityCellFilter } from './entity_cell'; +export type { EntityCellFilter } from './entity_cell'; +export { EntityCell } from './entity_cell'; diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx index afa4acdd8583d8..d04f8f7b648f5d 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx @@ -21,7 +21,7 @@ jest.mock('./full_time_range_selector_service', () => ({ })); describe('FullTimeRangeSelector', () => { - const indexPattern = { + const dataView = { id: '0844fc70-5ab5-11e9-935e-836737467b0f', fields: [], title: 'test-data-view', @@ -34,7 +34,7 @@ describe('FullTimeRangeSelector', () => { }; const requiredProps = { - indexPattern, + dataView, query, }; diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx index 4ab35a7c03f5a2..846c79468b823e 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx @@ -14,7 +14,7 @@ import type { DataView } from '../../../../../../../src/plugins/data_views/publi import { setFullTimeRange } from './full_time_range_selector_service'; interface Props { - indexPattern: DataView; + dataView: DataView; query: Query; disabled: boolean; callback?: (a: any) => void; @@ -22,7 +22,7 @@ interface Props { // Component for rendering a button which automatically sets the range of the time filter // to the time range of data in the index(es) mapped to the supplied Kibana index pattern or query. -export const FullTimeRangeSelector: FC = ({ indexPattern, query, disabled, callback }) => { +export const FullTimeRangeSelector: FC = ({ dataView, query, disabled, callback }) => { // wrapper around setFullTimeRange to allow for the calling of the optional callBack prop async function setRange(i: DataView, q: Query) { const fullTimeRange = await setFullTimeRange(i, q); @@ -33,14 +33,14 @@ export const FullTimeRangeSelector: FC = ({ indexPattern, query, disabled return ( setRange(indexPattern, query)} + onClick={() => setRange(dataView, query)} data-test-subj="mlButtonUseFullData" > diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/index.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/index.tsx index c79df59ee3f69f..14ad77e2adc3ad 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/index.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/index.tsx @@ -6,4 +6,5 @@ */ export { FullTimeRangeSelector } from './full_time_range_selector'; -export { getTimeFilterRange, TimeRange } from './full_time_range_selector_service'; +export type { TimeRange } from './full_time_range_selector_service'; +export { getTimeFilterRange } from './full_time_range_selector_service'; diff --git a/x-pack/plugins/ml/public/application/components/multi_select_picker/index.ts b/x-pack/plugins/ml/public/application/components/multi_select_picker/index.ts index 9d32228e1c4bc9..a42ce68d9aa10c 100644 --- a/x-pack/plugins/ml/public/application/components/multi_select_picker/index.ts +++ b/x-pack/plugins/ml/public/application/components/multi_select_picker/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { MultiSelectPicker, Option } from './multi_select_picker'; +export type { Option } from './multi_select_picker'; +export { MultiSelectPicker } from './multi_select_picker'; diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/index.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/index.ts index 216b0d8d5e9920..7f7642732b2e20 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/index.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/index.ts @@ -6,4 +6,5 @@ */ export { useScatterplotFieldOptions } from './use_scatterplot_field_options'; -export { ScatterplotMatrix, ScatterplotMatrixProps } from './scatterplot_matrix'; +export type { ScatterplotMatrixProps } from './scatterplot_matrix'; +export { ScatterplotMatrix } from './scatterplot_matrix'; diff --git a/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx b/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx index 7be72b84302334..4cc182988778d1 100644 --- a/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx +++ b/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx @@ -16,7 +16,6 @@ import { EuiRangeProps, } from '@elastic/eui'; import { ANOMALY_THRESHOLD } from '../../../../common'; -import './styles.scss'; export interface SeveritySelectorProps { value: number | undefined; @@ -29,23 +28,23 @@ export const SeverityControl: FC = React.memo(({ value, o const levels: EuiRangeProps['levels'] = [ { min: ANOMALY_THRESHOLD.LOW, - max: ANOMALY_THRESHOLD.MINOR - 1, - color: 'success', + max: ANOMALY_THRESHOLD.MINOR, + color: '#8BC8FB', }, { min: ANOMALY_THRESHOLD.MINOR, - max: ANOMALY_THRESHOLD.MAJOR - 1, - color: 'primary', + max: ANOMALY_THRESHOLD.MAJOR, + color: '#FDEC25', }, { min: ANOMALY_THRESHOLD.MAJOR, max: ANOMALY_THRESHOLD.CRITICAL, - color: 'warning', + color: '#FBA740', }, { min: ANOMALY_THRESHOLD.CRITICAL, max: MAX_ANOMALY_SCORE, - color: 'danger', + color: '#FE5050', }, ]; diff --git a/x-pack/plugins/ml/public/application/components/severity_control/styles.scss b/x-pack/plugins/ml/public/application/components/severity_control/styles.scss deleted file mode 100644 index 9a5fa8f2b160a9..00000000000000 --- a/x-pack/plugins/ml/public/application/components/severity_control/styles.scss +++ /dev/null @@ -1,18 +0,0 @@ -// Color overrides are required (https://github.com/elastic/eui/issues/4467) - -.mlSeverityControl { - .euiRangeLevel-- { - &success { - background-color: #8BC8FB; - } - &primary { - background-color: #FDEC25; - } - &warning { - background-color: #FBA740; - } - &danger { - background-color: #FE5050; - } - } -} diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/index.ts b/x-pack/plugins/ml/public/application/components/stats_bar/index.ts index c3c44477e7f919..e1638c90c9e49f 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/index.ts +++ b/x-pack/plugins/ml/public/application/components/stats_bar/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { StatsBar, AnalyticStatsBarStats, JobStatsBarStats, ModelsBarStats } from './stats_bar'; +export type { AnalyticStatsBarStats, JobStatsBarStats, ModelsBarStats } from './stats_bar'; +export { StatsBar } from './stats_bar'; diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/index.ts b/x-pack/plugins/ml/public/application/contexts/kibana/index.ts index 7331ebe34e915a..0873d395f0b79b 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/index.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/index.ts @@ -5,8 +5,10 @@ * 2.0. */ -export { useMlKibana, StartServices, MlKibanaReactContextValue } from './kibana_context'; -export { useNavigateToPath, NavigateToPath } from './use_navigate_to_path'; +export type { StartServices, MlKibanaReactContextValue } from './kibana_context'; +export { useMlKibana } from './kibana_context'; +export type { NavigateToPath } from './use_navigate_to_path'; +export { useNavigateToPath } from './use_navigate_to_path'; export { useUiSettings } from './use_ui_settings_context'; export { useTimefilter } from './use_timefilter'; export { useNotifications } from './use_notifications_context'; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view.ts similarity index 92% rename from x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts rename to x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view.ts index 93f92002c4bfdc..0d3c0993c880b9 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view.ts @@ -7,7 +7,7 @@ import type { DataView } from '../../../../../../../../src/plugins/data_views/public'; -export const indexPatternMock = { +export const dataViewMock = { id: 'the-index-pattern-id', title: 'the-index-pattern-title', fields: [], diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view_contract.ts similarity index 92% rename from x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts rename to x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view_contract.ts index 571ce8ac3f4232..61a466b6496ef9 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view_contract.ts @@ -7,7 +7,7 @@ import type { DataViewsContract } from '../../../../../../../../src/plugins/data_views/public'; -export const indexPatternsMock = new (class { +export const dataViewsContractMock = new (class { fieldFormats = []; config = {}; savedObjectsClient = {}; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts index 7045e08947f35b..642bc4baee712a 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { indexPatternMock } from './index_pattern'; -import { indexPatternsMock } from './index_patterns'; +import { dataViewMock } from './data_view'; +import { dataViewsContractMock } from './data_view_contract'; import { kibanaConfigMock } from './kibana_config'; import { savedSearchMock } from './saved_search'; @@ -15,8 +15,8 @@ export const kibanaContextValueMock = { query: 'the-query-string', language: 'the-query-language', }, - currentIndexPattern: indexPatternMock, + currentDataView: dataViewMock, currentSavedSearch: savedSearchMock, - indexPatterns: indexPatternsMock, + dataViewsContract: dataViewsContractMock, kibanaConfig: kibanaConfigMock, }; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/index.ts b/x-pack/plugins/ml/public/application/contexts/ml/index.ts index 20d5dca4e6c18e..a5ca1ded4f1aea 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/index.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { MlContext, MlContextValue, SavedSearchQuery } from './ml_context'; +export type { MlContextValue, SavedSearchQuery } from './ml_context'; +export { MlContext } from './ml_context'; export { useMlContext } from './use_ml_context'; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts index cd7059b5302f23..d707f56b10a340 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts @@ -12,25 +12,17 @@ import { MlServicesContext } from '../../app'; export interface MlContextValue { combinedQuery: any; - currentIndexPattern: DataView; // TODO this should be IndexPattern or null + currentDataView: DataView; // TODO this should be DataView or null currentSavedSearch: SavedSearchSavedObject | null; - indexPatterns: DataViewsContract; + dataViewsContract: DataViewsContract; kibanaConfig: any; // IUiSettingsClient; kibanaVersion: string; } export type SavedSearchQuery = object; -// This context provides dependencies which can be injected -// via angularjs only (like services, currentIndexPattern etc.). -// Because we cannot just import these dependencies, the default value -// for the context is just {} and of type `Partial` -// for the angularjs based dependencies. Therefore, the -// actual dependencies are set like we did previously with KibanaContext -// in the wrapping angularjs directive. In the custom hook we check if -// the dependencies are present with error reporting if they weren't -// added properly. That's why in tests, these custom hooks must not -// be mocked, instead ` needs +// In tests, these custom hooks must not be mocked, +// instead ` needs // to be used. This guarantees that we have both properly set up // TypeScript support and runtime checks for these dependencies. // Multiple custom hooks can be created to access subsets of diff --git a/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts b/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts index 4a8b77bdb2fde2..69c36ee09d7450 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts @@ -12,9 +12,9 @@ import { MlContext } from './ml_context'; export const useCurrentIndexPattern = () => { const context = useContext(MlContext); - if (context.currentIndexPattern === undefined) { - throw new Error('currentIndexPattern is undefined'); + if (context.currentDataView === undefined) { + throw new Error('currentDataView is undefined'); } - return context.currentIndexPattern; + return context.currentDataView; }; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts b/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts index 3d81c46684bef5..1a07cb0338855f 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts @@ -14,9 +14,9 @@ export const useMlContext = () => { if ( context.combinedQuery === undefined || - context.currentIndexPattern === undefined || + context.currentDataView === undefined || context.currentSavedSearch === undefined || - context.indexPatterns === undefined || + context.dataViewsContract === undefined || context.kibanaConfig === undefined ) { throw new Error('required attribute is undefined'); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts index 54372b5f6afc7d..2fb0daa1ed45e4 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts @@ -5,6 +5,13 @@ * 2.0. */ +export type { + UpdateDataFrameAnalyticsConfig, + IndexPattern, + RegressionEvaluateResponse, + Eval, + SearchQuery, +} from './analytics'; export { getAnalysisType, getDependentVar, @@ -13,38 +20,26 @@ export { isOutlierAnalysis, refreshAnalyticsList$, useRefreshAnalyticsList, - UpdateDataFrameAnalyticsConfig, - IndexPattern, REFRESH_ANALYTICS_LIST_STATE, OUTLIER_ANALYSIS_METHOD, - RegressionEvaluateResponse, getValuesFromResponse, loadEvalData, loadDocsCount, - Eval, getPredictedFieldName, INDEX_STATUS, SEARCH_SIZE, defaultSearchQuery, - SearchQuery, ANALYSIS_CONFIG_TYPE, } from './analytics'; -export { - getDefaultFieldsFromJobCaps, - sortExplorationResultsFields, - EsId, - EsDoc, - EsDocSource, - EsFieldName, - MAX_COLUMNS, -} from './fields'; +export type { EsId, EsDoc, EsDocSource, EsFieldName } from './fields'; +export { getDefaultFieldsFromJobCaps, sortExplorationResultsFields, MAX_COLUMNS } from './fields'; export { getIndexData } from './get_index_data'; export { getIndexFields } from './get_index_fields'; export { getScatterplotMatrixLegendType } from './get_scatterplot_matrix_legend_type'; export { useResultsViewConfig } from './use_results_view_config'; -export { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics'; -export { DataFrameAnalyticsId } from '../../../../common/types/data_frame_analytics'; -export { IndexName } from '../../../../common/types/data_frame_analytics'; +export type { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics'; +export type { DataFrameAnalyticsId } from '../../../../common/types/data_frame_analytics'; +export type { IndexName } from '../../../../common/types/data_frame_analytics'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts index ac638fe1f41a07..43dff4142023d0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts @@ -13,7 +13,7 @@ import type { DataView } from '../../../../../../../src/plugins/data_views/publi import { extractErrorMessage } from '../../../../common/util/errors'; -import { getIndexPatternIdFromName } from '../../util/index_utils'; +import { getDataViewIdFromName } from '../../util/index_utils'; import { ml } from '../../services/ml_api_service'; import { newJobCapsServiceAnalytics } from '../../services/new_job_capabilities/new_job_capabilities_service_analytics'; import { useMlContext } from '../../contexts/ml'; @@ -98,36 +98,36 @@ export const useResultsViewConfig = (jobId: string) => { const destIndex = Array.isArray(jobConfigUpdate.dest.index) ? jobConfigUpdate.dest.index[0] : jobConfigUpdate.dest.index; - const destIndexPatternId = getIndexPatternIdFromName(destIndex) || destIndex; - let indexP: DataView | undefined; + const destDataViewId = (await getDataViewIdFromName(destIndex)) ?? destIndex; + let dataView: DataView | undefined; try { - indexP = await mlContext.indexPatterns.get(destIndexPatternId); + dataView = await mlContext.dataViewsContract.get(destDataViewId); // Force refreshing the fields list here because a user directly coming // from the job creation wizard might land on the page without the // data view being fully initialized because it was created // before the analytics job populated the destination index. - await mlContext.indexPatterns.refreshFields(indexP); + await mlContext.dataViewsContract.refreshFields(dataView); } catch (e) { - indexP = undefined; + dataView = undefined; } - if (indexP === undefined) { + if (dataView === undefined) { setNeedsDestIndexPattern(true); const sourceIndex = jobConfigUpdate.source.index[0]; - const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; + const sourceDataViewId = (await getDataViewIdFromName(sourceIndex)) ?? sourceIndex; try { - indexP = await mlContext.indexPatterns.get(sourceIndexPatternId); + dataView = await mlContext.dataViewsContract.get(sourceDataViewId); } catch (e) { - indexP = undefined; + dataView = undefined; } } - if (indexP !== undefined) { - await newJobCapsServiceAnalytics.initializeFromIndexPattern(indexP); + if (dataView !== undefined) { + await newJobCapsServiceAnalytics.initializeFromDataVIew(dataView); setJobConfig(analyticsConfigs.data_frame_analytics[0]); - setIndexPattern(indexP); + setIndexPattern(dataView); setIsInitialized(true); setIsLoadingJobConfig(false); } else { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx index 9dd4c5c42cca76..8b7109d87a866a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx @@ -7,6 +7,7 @@ import React, { FC, Fragment, useEffect, useState } from 'react'; import { EuiCallOut, EuiFormRow, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import { isEqual } from 'lodash'; // @ts-ignore no declaration import { LEFT_ALIGNMENT, CENTER_ALIGNMENT, SortableProperties } from '@elastic/eui/lib/services'; import { i18n } from '@kbn/i18n'; @@ -90,167 +91,182 @@ export const AnalysisFieldsTable: FC<{ tableItems: FieldSelectionItem[]; unsupportedFieldsError?: string; setUnsupportedFieldsError: React.Dispatch>; -}> = ({ - dependentVariable, - includes, - setFormState, - minimumFieldsRequiredMessage, - setMinimumFieldsRequiredMessage, - tableItems, - unsupportedFieldsError, - setUnsupportedFieldsError, -}) => { - const [sortableProperties, setSortableProperties] = useState(); - const [currentPaginationData, setCurrentPaginationData] = useState<{ - pageIndex: number; - itemsPerPage: number; - }>({ pageIndex: 0, itemsPerPage: 5 }); +}> = React.memo( + ({ + dependentVariable, + includes, + setFormState, + minimumFieldsRequiredMessage, + setMinimumFieldsRequiredMessage, + tableItems, + unsupportedFieldsError, + setUnsupportedFieldsError, + }) => { + const [sortableProperties, setSortableProperties] = useState(); + const [currentPaginationData, setCurrentPaginationData] = useState<{ + pageIndex: number; + itemsPerPage: number; + }>({ pageIndex: 0, itemsPerPage: 5 }); - useEffect(() => { - if (includes.length === 0 && tableItems.length > 0) { - const includedFields: string[] = []; - tableItems.forEach((field) => { - if (field.is_included === true) { - includedFields.push(field.name); - } - }); - setFormState({ includes: includedFields }); - } else if (includes.length > 0) { - setFormState({ includes }); - } - setMinimumFieldsRequiredMessage(undefined); - }, [tableItems]); - - useEffect(() => { - let sortablePropertyItems = []; - const defaultSortProperty = 'name'; - - sortablePropertyItems = [ - { - name: 'name', - getValue: (item: any) => item.name.toLowerCase(), - isAscending: true, - }, - { - name: 'is_included', - getValue: (item: any) => item.is_included, - isAscending: true, - }, - { - name: 'is_required', - getValue: (item: any) => item.is_required, - isAscending: true, - }, - ]; - const sortableProps = new SortableProperties(sortablePropertyItems, defaultSortProperty); + useEffect(() => { + if (includes.length === 0 && tableItems.length > 0) { + const includedFields: string[] = []; + tableItems.forEach((field) => { + if (field.is_included === true) { + includedFields.push(field.name); + } + }); + setFormState({ includes: includedFields }); + } else if (includes.length > 0) { + setFormState({ + includes: + dependentVariable && includes.includes(dependentVariable) + ? includes + : [...includes, dependentVariable], + }); + } + setMinimumFieldsRequiredMessage(undefined); + }, [tableItems]); - setSortableProperties(sortableProps); - }, []); + useEffect(() => { + let sortablePropertyItems = []; + const defaultSortProperty = 'name'; - const filters = [ - { - type: 'field_value_toggle_group', - field: 'is_included', - items: [ + sortablePropertyItems = [ { - value: true, - name: i18n.translate('xpack.ml.dataframe.analytics.create.isIncludedOption', { - defaultMessage: 'Is included', - }), + name: 'name', + getValue: (item: any) => item.name.toLowerCase(), + isAscending: true, }, { - value: false, - name: i18n.translate('xpack.ml.dataframe.analytics.create.isNotIncludedOption', { - defaultMessage: 'Is not included', - }), + name: 'is_included', + getValue: (item: any) => item.is_included, + isAscending: true, }, - ], - }, - ]; + { + name: 'is_required', + getValue: (item: any) => item.is_required, + isAscending: true, + }, + ]; + const sortableProps = new SortableProperties(sortablePropertyItems, defaultSortProperty); - return ( - - - - - {tableItems.length > 0 && minimumFieldsRequiredMessage === undefined && ( - - {i18n.translate('xpack.ml.dataframe.analytics.create.includedFieldsCount', { - defaultMessage: - '{numFields, plural, one {# field} other {# fields}} included in the analysis', - values: { numFields: includes.length }, - })} - - )} - {tableItems.length === 0 && ( - + - - - )} - {tableItems.length > 0 && ( - - { - // dependent variable must always be in includes - if ( - dependentVariable !== undefined && - dependentVariable !== '' && - selection.length === 0 - ) { - selection = [dependentVariable]; - } - // If includes is empty show minimum fields required message and don't update form yet - if (selection.length === 0) { - setMinimumFieldsRequiredMessage(minimumFieldsMessage); - setUnsupportedFieldsError(undefined); - } else { - setMinimumFieldsRequiredMessage(undefined); - setFormState({ includes: selection }); - } - }} - selectedIds={includes} - setCurrentPaginationData={setCurrentPaginationData} - singleSelection={false} - sortableProperties={sortableProperties} - tableItemId={'name'} - /> - - )} - - - ); -}; + + + {tableItems.length > 0 && minimumFieldsRequiredMessage === undefined && ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.includedFieldsCount', { + defaultMessage: + '{numFields, plural, one {# field} other {# fields}} included in the analysis', + values: { numFields: includes.length }, + })} + + )} + {tableItems.length === 0 && ( + + + + )} + {tableItems.length > 0 && ( + + { + // dependent variable must always be in includes + if ( + dependentVariable !== undefined && + dependentVariable !== '' && + selection.length === 0 + ) { + selection = [dependentVariable]; + } + // If includes is empty show minimum fields required message and don't update form yet + if (selection.length === 0) { + setMinimumFieldsRequiredMessage(minimumFieldsMessage); + setUnsupportedFieldsError(undefined); + } else { + setMinimumFieldsRequiredMessage(undefined); + setFormState({ includes: selection }); + } + }} + selectedIds={includes} + setCurrentPaginationData={setCurrentPaginationData} + singleSelection={false} + sortableProperties={sortableProperties} + tableItemId={'name'} + /> + + )} + + + ); + }, + (prevProps, nextProps) => { + return ( + prevProps.dependentVariable === nextProps.dependentVariable && + isEqual(prevProps.includes, nextProps.includes) && + isEqual(prevProps.tableItems, nextProps.tableItems) && + prevProps.unsupportedFieldsError === nextProps.unsupportedFieldsError + ); + } +); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx index 53270bf60ae8ec..2c812226bfabbf 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx @@ -31,7 +31,7 @@ interface Props { export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) => { const mlContext = useMlContext(); - const { currentIndexPattern } = mlContext; + const { currentDataView } = mlContext; const { form, isJobCreated } = state; const { dependentVariable, includes, jobConfigQueryString, jobType, trainingPercent } = form; @@ -43,7 +43,7 @@ export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) = title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.sourceIndex', { defaultMessage: 'Source index', }), - description: currentIndexPattern.title || UNSET_CONFIG_ITEM, + description: currentDataView.title || UNSET_CONFIG_ITEM, }, { title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.Query', { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index df42c5b8eb1ca2..21090ce671d02a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -88,7 +88,6 @@ function getRuntimeDepVarOptions(jobType: AnalyticsJobType, runtimeMappings: Run if (isRuntimeField(field) && shouldAddAsDepVarOption(id, field.type, jobType)) { runtimeOptions.push({ label: id, - key: `runtime_mapping_${id}`, }); } }); @@ -102,7 +101,7 @@ export const ConfigurationStepForm: FC = ({ setCurrentStep, }) => { const mlContext = useMlContext(); - const { currentSavedSearch, currentIndexPattern } = mlContext; + const { currentSavedSearch, currentDataView } = mlContext; const { savedSearchQuery, savedSearchQueryStr } = useSavedSearch(); const [fieldOptionsFetchFail, setFieldOptionsFetchFail] = useState(false); @@ -168,7 +167,7 @@ export const ConfigurationStepForm: FC = ({ }; const indexData = useIndexData( - currentIndexPattern, + currentDataView, getIndexDataQuery(savedSearchQuery, jobConfigQuery), toastNotifications, runtimeMappings @@ -197,7 +196,7 @@ export const ConfigurationStepForm: FC = ({ setMaxDistinctValuesError(undefined); try { - if (currentIndexPattern !== undefined) { + if (currentDataView !== undefined) { const depVarOptions = []; let depVarUpdate = formState.dependentVariable; // Get fields and filter for supported types for job type @@ -335,7 +334,7 @@ export const ConfigurationStepForm: FC = ({ }, 300); useEffect(() => { - setFormState({ sourceIndex: currentIndexPattern.title }); + setFormState({ sourceIndex: currentDataView.title }); }, []); const indexPatternFieldsTableItems = useMemo(() => { @@ -357,7 +356,7 @@ export const ConfigurationStepForm: FC = ({ useEffect(() => { if (isJobTypeWithDepVar) { - const indexPatternRuntimeFields = getCombinedRuntimeMappings(currentIndexPattern); + const indexPatternRuntimeFields = getCombinedRuntimeMappings(currentDataView); let runtimeOptions; if (indexPatternRuntimeFields) { @@ -499,14 +498,14 @@ export const ConfigurationStepForm: FC = ({ fields: includesTableItems .filter((d) => d.feature_type === 'numerical' && d.is_included) .map((d) => d.name), - index: currentIndexPattern.title, + index: currentDataView.title, legendType: getScatterplotMatrixLegendType(jobType), searchQuery: jobConfigQuery, runtimeMappings, - indexPattern: currentIndexPattern, + indexPattern: currentDataView, }), [ - currentIndexPattern.title, + currentDataView.title, dependentVariable, includesTableItems, isJobTypeWithDepVar, @@ -549,7 +548,7 @@ export const ConfigurationStepForm: FC = ({ fullWidth > @@ -569,7 +568,7 @@ export const ConfigurationStepForm: FC = ({ {savedSearchQuery !== null ? currentSavedSearch?.attributes.title - : currentIndexPattern.title} + : currentDataView.title} } @@ -587,7 +586,7 @@ export const ConfigurationStepForm: FC = ({ helpText={ dependentVariableOptions.length === 0 && dependentVariableFetchFail === false && - currentIndexPattern && + currentDataView && i18n.translate( 'xpack.ml.dataframe.analytics.create.dependentVariableOptionsNoNumericalFields', { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts index ad23c018afbbb8..c4611a1740913f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts @@ -33,7 +33,7 @@ export function useSavedSearch() { const [savedSearchQueryStr, setSavedSearchQueryStr] = useState(undefined); const mlContext = useMlContext(); - const { currentSavedSearch, currentIndexPattern, kibanaConfig } = mlContext; + const { currentSavedSearch, currentDataView, kibanaConfig } = mlContext; const getQueryData = () => { let qry: estypes.QueryDslQueryContainer = {}; @@ -46,7 +46,7 @@ export function useSavedSearch() { if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) { const ast = fromKueryExpression(qryString); - qry = toElasticsearchQuery(ast, currentIndexPattern); + qry = toElasticsearchQuery(ast, currentDataView); } else { qry = luceneStringToDsl(qryString); decorateQuery(qry, kibanaConfig.get('query:queryString:options')); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx index 95881fc3289768..e24d8e89d464ea 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx @@ -95,7 +95,7 @@ export const RuntimeMappings: FC = ({ actions, state }) => { } = useXJsonMode(runtimeMappings || ''); const mlContext = useMlContext(); - const { currentIndexPattern } = mlContext; + const { currentDataView } = mlContext; const applyChanges = () => { const removeRuntimeMappings = advancedRuntimeMappingsConfig === ''; @@ -133,10 +133,7 @@ export const RuntimeMappings: FC = ({ actions, state }) => { }; useEffect(function getInitialRuntimeMappings() { - const combinedRuntimeMappings = getCombinedRuntimeMappings( - currentIndexPattern, - runtimeMappings - ); + const combinedRuntimeMappings = getCombinedRuntimeMappings(currentDataView, runtimeMappings); const prettySourceConfig = JSON.stringify(combinedRuntimeMappings, null, 2); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx index 42a56d6327a600..8b136cfcd36376 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx @@ -57,7 +57,7 @@ export const Page: FC = ({ jobId }) => { ]); const mlContext = useMlContext(); - const { currentIndexPattern } = mlContext; + const { currentDataView } = mlContext; const createAnalyticsForm = useCreateAnalyticsForm(); const { state } = createAnalyticsForm; @@ -69,7 +69,7 @@ export const Page: FC = ({ jobId }) => { useEffect(() => { initiateWizard(); - if (currentIndexPattern) { + if (currentDataView) { (async function () { if (jobId !== undefined) { const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId, true); @@ -192,7 +192,7 @@ export const Page: FC = ({ jobId }) => {

diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/index.ts index c8ea83aa489ce1..8979a350294a56 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/index.ts @@ -5,11 +5,8 @@ * 2.0. */ -export { - ExpandableSection, - ExpandableSectionProps, - HEADER_ITEMS_LOADING, -} from './expandable_section'; +export type { ExpandableSectionProps } from './expandable_section'; +export { ExpandableSection, HEADER_ITEMS_LOADING } from './expandable_section'; export { ExpandableSectionAnalytics } from './expandable_section_analytics'; export { ExpandableSectionResults } from './expandable_section_results'; export { ExpandableSectionSplom } from './expandable_section_splom'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts index c91301942daa81..75d9c1e89affcf 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { EvaluatePanelProps, ExplorationPageWrapper } from './exploration_page_wrapper'; +export type { EvaluatePanelProps } from './exploration_page_wrapper'; +export { ExplorationPageWrapper } from './exploration_page_wrapper'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts index 12d5b4be9ac6c7..ba26d84d6a475e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts @@ -5,9 +5,6 @@ * 2.0. */ -export { - extractCloningConfig, - isAdvancedConfig, - CloneDataFrameAnalyticsConfig, -} from './clone_action_name'; +export type { CloneDataFrameAnalyticsConfig } from './clone_action_name'; +export { extractCloningConfig, isAdvancedConfig } from './clone_action_name'; export { useCloneAction } from './use_clone_action'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index 1be9186a8468ce..78e5a80b4cc4cd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -10,7 +10,7 @@ import { EuiTableActionsColumnType, Query, Ast } from '@elastic/eui'; import { DATA_FRAME_TASK_STATE } from '../../../../../../../common/constants/data_frame_analytics'; import { DataFrameTaskStateType } from '../../../../../../../common/types/data_frame_analytics'; export { DATA_FRAME_TASK_STATE }; -export { DataFrameTaskStateType }; +export type { DataFrameTaskStateType }; import { DataFrameAnalyticsId, DataFrameAnalyticsConfig } from '../../../../common'; import { @@ -18,7 +18,7 @@ import { DataFrameAnalyticsStats, } from '../../../../../../../common/types/data_frame_analytics'; -export { DataFrameAnalyticsStats } from '../../../../../../../common/types/data_frame_analytics'; +export type { DataFrameAnalyticsStats } from '../../../../../../../common/types/data_frame_analytics'; export enum DATA_FRAME_MODE { BATCH = 'batch', diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx index 48760f412725fc..c9bf469bd1f406 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx @@ -10,10 +10,7 @@ import { render, fireEvent, waitFor, screen } from '@testing-library/react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; -import { - getIndexPatternAndSavedSearch, - IndexPatternAndSavedSearch, -} from '../../../../../util/index_utils'; +import { getDataViewAndSavedSearch, DataViewAndSavedSearch } from '../../../../../util/index_utils'; import { SourceSelection } from './source_selection'; @@ -83,27 +80,25 @@ jest.mock('../../../../../contexts/kibana', () => ({ jest.mock('../../../../../util/index_utils', () => { return { - getIndexPatternAndSavedSearch: jest.fn( - async (id: string): Promise => { - return { - indexPattern: { - // @ts-expect-error fields should not be empty - fields: [], - title: - id === 'the-remote-saved-search-id' - ? 'my_remote_cluster:index-pattern-title' - : 'index-pattern-title', - }, - savedSearch: null, - }; - } - ), + getDataViewAndSavedSearch: jest.fn(async (id: string): Promise => { + return { + dataView: { + // @ts-expect-error fields should not be empty + fields: [], + title: + id === 'the-remote-saved-search-id' + ? 'my_remote_cluster:index-pattern-title' + : 'index-pattern-title', + }, + savedSearch: null, + }; + }), isCcsIndexPattern: (a: string) => a.includes(':'), }; }); const mockOnClose = jest.fn(); -const mockGetDataViewAndSavedSearch = getIndexPatternAndSavedSearch as jest.Mock; +const mockGetDataViewAndSavedSearch = getDataViewAndSavedSearch as jest.Mock; describe('Data Frame Analytics: ', () => { afterEach(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx index c2f17a45c1e066..e0922d7e12653d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx @@ -25,7 +25,7 @@ import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana'; import { getNestedProperty } from '../../../../../util/object_utils'; -import { getIndexPatternAndSavedSearch, isCcsIndexPattern } from '../../../../../util/index_utils'; +import { getDataViewAndSavedSearch, isCcsIndexPattern } from '../../../../../util/index_utils'; const fixedPageSize: number = 8; @@ -57,8 +57,8 @@ export const SourceSelection: FC = ({ onClose }) => { if (type === 'index-pattern') { dataViewName = getNestedProperty(savedObject, 'attributes.title'); } else if (type === 'search') { - const indexPatternAndSavedSearch = await getIndexPatternAndSavedSearch(id); - dataViewName = indexPatternAndSavedSearch.indexPattern?.title ?? ''; + const dataViewAndSavedSearch = await getDataViewAndSavedSearch(id); + dataViewName = dataViewAndSavedSearch.dataView?.title ?? ''; } if (isCcsIndexPattern(dataViewName)) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts index f07b06ada93cf5..848b8f76ab14c1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts @@ -6,9 +6,9 @@ */ export { DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES } from './state'; -export { +export type { AnalyticsCreationStep, - useCreateAnalyticsForm, CreateAnalyticsFormProps, CreateAnalyticsStepProps, } from './use_create_analytics_form'; +export { useCreateAnalyticsForm } from './use_create_analytics_form'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 9137ce42a465c0..c49653c5a75c5d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -144,7 +144,7 @@ export const validateNumTopFeatureImportanceValues = ( }; export const validateAdvancedEditor = (state: State): State => { - const { jobIdEmpty, jobIdValid, jobIdExists, jobType, createIndexPattern, includes } = state.form; + const { jobIdEmpty, jobIdValid, jobIdExists, jobType, createIndexPattern } = state.form; const { jobConfig } = state; state.advancedEditorMessages = []; @@ -160,6 +160,8 @@ export const validateAdvancedEditor = (state: State): State => { const destinationIndexPatternTitleExists = state.indexPatternsMap[destinationIndexName] !== undefined; + const analyzedFields = jobConfig?.analyzed_fields?.includes || []; + const resultsFieldEmptyString = typeof jobConfig?.dest?.results_field === 'string' && jobConfig?.dest?.results_field.trim() === ''; @@ -189,12 +191,10 @@ export const validateAdvancedEditor = (state: State): State => { ) { const dependentVariableName = getDependentVar(jobConfig.analysis) || ''; dependentVariableEmpty = dependentVariableName === ''; - if ( !dependentVariableEmpty && - includes !== undefined && - includes.length > 0 && - !includes.includes(dependentVariableName) + analyzedFields.length > 0 && + !analyzedFields.includes(dependentVariableName) ) { includesValid = false; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index 18abc9be270be3..88b0774e107e2e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -127,7 +127,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const dataViewName = destinationIndex; try { - await mlContext.indexPatterns.createAndSave( + await mlContext.dataViewsContract.createAndSave( { title: dataViewName, }, @@ -179,7 +179,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { try { // Set the existing data view names. const indexPatternsMap: SourceIndexMap = {}; - const savedObjects = (await mlContext.indexPatterns.getCache()) || []; + const savedObjects = (await mlContext.dataViewsContract.getCache()) || []; savedObjects.forEach((obj) => { const title = obj?.attributes?.title; if (title !== undefined) { @@ -201,7 +201,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { }; const initiateWizard = async () => { - await mlContext.indexPatterns.clearCache(); + await mlContext.dataViewsContract.clearCache(); await prepareFormValidation(); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx index 1b961c05b2f308..b2879e2ffde542 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx @@ -33,7 +33,7 @@ import { JOB_MAP_NODE_TYPES } from '../../../../../../common/constants/data_fram import { ML_PAGES } from '../../../../../../common/constants/locator'; import { checkPermission } from '../../../../capabilities/check_capabilities'; import { useMlLocator, useNotifications, useNavigateToPath } from '../../../../contexts/kibana'; -import { getIndexPatternIdFromName } from '../../../../util/index_utils'; +import { getDataViewIdFromName } from '../../../../util/index_utils'; import { useNavigateToWizardWithClonedJob } from '../../analytics_management/components/action_clone/clone_action_name'; import { useDeleteAction, @@ -112,12 +112,12 @@ export const Controls: FC = React.memo( const nodeType = selectedNode?.data('type'); const onCreateJobClick = useCallback(async () => { - const indexId = getIndexPatternIdFromName(nodeLabel); + const dataViewId = await getDataViewIdFromName(nodeLabel); - if (indexId) { + if (dataViewId !== null) { const path = await mlLocator.getUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB, - pageState: { index: indexId }, + pageState: { index: dataViewId }, }); await navigateToPath(path); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx index 5fe519f25efb69..90b152453cecbe 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx @@ -30,7 +30,7 @@ export const FileDataVisualizerPage: FC = () => { docLinks, dataVisualizer, data: { - indexPatterns: { get: getIndexPattern }, + dataViews: { get: getDataView }, }, }, } = useMlKibana(); @@ -60,7 +60,7 @@ export const FileDataVisualizerPage: FC = () => { }, canDisplay: async ({ indexPatternId }) => { try { - const { timeFieldName } = await getIndexPattern(indexPatternId); + const { timeFieldName } = await getDataView(indexPatternId); return ( isFullLicense() && timeFieldName !== undefined && @@ -89,7 +89,7 @@ export const FileDataVisualizerPage: FC = () => { }, }); }, - canDisplay: async () => true, + canDisplay: async ({ indexPatternId }) => indexPatternId !== '', }, ], [] diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 0faf5b775d23eb..7ae2712a2edd82 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -29,7 +29,7 @@ export const IndexDataVisualizerPage: FC = () => { docLinks, dataVisualizer, data: { - indexPatterns: { get: getIndexPattern }, + dataViews: { get: getDataView }, }, }, } = useMlKibana(); @@ -74,7 +74,7 @@ export const IndexDataVisualizerPage: FC = () => { }, canDisplay: async ({ indexPatternId }) => { try { - const { timeFieldName } = await getIndexPattern(indexPatternId); + const { timeFieldName } = await getDataView(indexPatternId); return ( isFullLicense() && timeFieldName !== undefined && diff --git a/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts b/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts index 46d47bb5547055..ed244cbd894bab 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts @@ -17,8 +17,7 @@ import { createJobs } from '../explorer_utils'; export function jobSelectionActionCreator(selectedJobIds: string[]) { return from(mlFieldFormatService.populateFormats(selectedJobIds)).pipe( map((resp) => { - if (resp.err) { - console.log('Error populating field formats:', resp.err); // eslint-disable-line no-console + if (resp.error) { return null; } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx index 2ede9d380f3bf9..66f4052a6952fc 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx @@ -12,6 +12,7 @@ import { ENTITY_FIELD_OPERATIONS, EntityFieldOperation, } from '../../../../../../../common/util/anomaly_utils'; +import { blurButtonOnClick } from '../../../../../util/component_utils'; import './_entity_filter.scss'; interface EntityFilterProps { @@ -41,13 +42,13 @@ export const EntityFilter: FC = ({ + onClick={blurButtonOnClick(() => { onFilter({ influencerFieldName, influencerFieldValue, action: ENTITY_FIELD_OPERATIONS.ADD, - }) - } + }); + })} iconType="plusInCircle" aria-label={i18n.translate('xpack.ml.entityFilter.addFilterAriaLabel', { defaultMessage: 'Add filter for {influencerFieldName} {influencerFieldValue}', @@ -66,13 +67,13 @@ export const EntityFilter: FC = ({ + onClick={blurButtonOnClick(() => { onFilter({ influencerFieldName, influencerFieldValue, action: ENTITY_FIELD_OPERATIONS.REMOVE, - }) - } + }); + })} iconType="minusInCircle" aria-label={i18n.translate('xpack.ml.entityFilter.removeFilterAriaLabel', { defaultMessage: 'Remove filter for {influencerFieldName} {influencerFieldValue}', diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/index.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/index.ts index 91ae73628fd77b..74b6c88fba8d4f 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/index.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/index.ts @@ -7,4 +7,5 @@ export { getIndexPattern } from './get_index_pattern'; export { explorerReducer } from './reducer'; -export { getExplorerDefaultState, ExplorerState } from './state'; +export type { ExplorerState } from './state'; +export { getExplorerDefaultState } from './state'; diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/index.ts b/x-pack/plugins/ml/public/application/explorer/reducers/index.ts index 6652bbf132d4e4..db44d1864daa1d 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/index.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/index.ts @@ -5,9 +5,5 @@ * 2.0. */ -export { - explorerReducer, - getExplorerDefaultState, - getIndexPattern, - ExplorerState, -} from './explorer_reducer'; +export type { ExplorerState } from './explorer_reducer'; +export { explorerReducer, getExplorerDefaultState, getIndexPattern } from './explorer_reducer'; diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx index 409f6a5911bb70..8fb4c43d772073 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { CustomUrlEditor } from './editor'; import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import { CustomUrlSettings } from './utils'; -import { DataView } from '../../../../../../../../src/plugins/data_views/common'; +import { DataViewListItem } from '../../../../../../../../src/plugins/data_views/common'; function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: UrlConfig) => void) { const savedCustomUrls = [ @@ -47,10 +47,10 @@ function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: Url { id: 'dash2', title: 'Dashboard 2' }, ]; - const indexPatterns = [ + const dataViewListItems = [ { id: 'pattern1', title: 'Data view 1' }, { id: 'pattern2', title: 'Data view 2' }, - ] as DataView[]; + ] as DataViewListItem[]; const queryEntityFieldNames = ['airline']; @@ -59,7 +59,7 @@ function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: Url setEditCustomUrl: setEditCustomUrlFn, savedCustomUrls, dashboards, - indexPatterns, + dataViewListItems, queryEntityFieldNames, }; diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx index c769e2548370c3..b140c950d52ef6 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx @@ -29,7 +29,7 @@ import { isValidLabel } from '../../../util/custom_url_utils'; import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import { UrlConfig } from '../../../../../common/types/custom_urls'; -import { DataView } from '../../../../../../../../src/plugins/data_views/common'; +import { DataViewListItem } from '../../../../../../../../src/plugins/data_views/common'; function getLinkToOptions() { return [ @@ -59,7 +59,7 @@ interface CustomUrlEditorProps { setEditCustomUrl: (url: any) => void; savedCustomUrls: UrlConfig[]; dashboards: any[]; - indexPatterns: DataView[]; + dataViewListItems: DataViewListItem[]; queryEntityFieldNames: string[]; } @@ -71,7 +71,7 @@ export const CustomUrlEditor: FC = ({ setEditCustomUrl, savedCustomUrls, dashboards, - indexPatterns, + dataViewListItems, queryEntityFieldNames, }) => { if (customUrl === undefined) { @@ -164,8 +164,8 @@ export const CustomUrlEditor: FC = ({ return { value: dashboard.id, text: dashboard.title }; }); - const indexPatternOptions = indexPatterns.map((indexPattern) => { - return { value: indexPattern.id, text: indexPattern.title }; + const dataViewOptions = dataViewListItems.map(({ id, title }) => { + return { value: id, text: title }; }); const entityOptions = queryEntityFieldNames.map((fieldName) => ({ label: fieldName })); @@ -274,7 +274,7 @@ export const CustomUrlEditor: FC = ({ display="rowCompressed" > 0) { urlType = URL_TYPE.KIBANA_DASHBOARD; kibanaSettings.dashboardId = dashboards[0].id; - } else if (indexPatterns !== undefined && indexPatterns.length > 0) { + } else if (dataViews !== undefined && dataViews.length > 0) { urlType = URL_TYPE.KIBANA_DISCOVER; } @@ -40,15 +39,15 @@ export function getNewCustomUrlDefaults(job, dashboards, indexPatterns) { // which matches the indices configured in the job datafeed. const datafeedConfig = job.datafeed_config; if ( - indexPatterns !== undefined && - indexPatterns.length > 0 && + dataViews !== undefined && + dataViews.length > 0 && datafeedConfig !== undefined && datafeedConfig.indices !== undefined && datafeedConfig.indices.length > 0 ) { - const defaultIndexPatternId = - getIndexPatternIdFromName(datafeedConfig.indices.join()) ?? indexPatterns[0].id; - kibanaSettings.discoverIndexPatternId = defaultIndexPatternId; + const indicesName = datafeedConfig.indices.join(); + const defaultDataViewId = dataViews.find((dv) => dv.title === indicesName)?.id; + kibanaSettings.discoverIndexPatternId = defaultDataViewId; } return { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts index 32e99e3e433e0b..9e9f112cee25d0 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { DataView } from 'src/plugins/data_views/common'; +import type { DataViewListItem } from 'src/plugins/data_views/common'; export function loadSavedDashboards(maxNumber: number): Promise; -export function loadIndexPatterns(maxNumber: number): Promise; +export function loadDataViewListItems(): Promise; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js index f32800d6b7b7ff..3a94cf6c673f35 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js @@ -102,20 +102,9 @@ export function loadSavedDashboards(maxNumber) { }); } -export function loadIndexPatterns(maxNumber) { - // Loads the list of Kibana data views, as used in editing custom URLs. - return new Promise((resolve, reject) => { - const dataViewsContract = getDataViews(); - dataViewsContract - .find('*', maxNumber) - .then((dataViews) => { - const sortedDataViews = dataViews.sort((a, b) => a.title.localeCompare(b.title)); - resolve(sortedDataViews); - }) - .catch((resp) => { - reject(resp); - }); - }); +export async function loadDataViewListItems() { + const dataViewsContract = getDataViews(); + return (await dataViewsContract.getIdsWithTitle()).sort((a, b) => a.title.localeCompare(b.title)); } function extractDescription(job, newJobData) { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index 45f059690c3a93..035836373adf6e 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -33,15 +33,14 @@ import { CustomUrlSettings, } from '../../../../components/custom_url_editor/utils'; import { withKibana } from '../../../../../../../../../../src/plugins/kibana_react/public'; -import { loadSavedDashboards, loadIndexPatterns } from '../edit_utils'; +import { loadSavedDashboards, loadDataViewListItems } from '../edit_utils'; import { openCustomUrlWindow } from '../../../../../util/custom_url_utils'; import { Job } from '../../../../../../../common/types/anomaly_detection_jobs'; import { UrlConfig } from '../../../../../../../common/types/custom_urls'; -import { DataView } from '../../../../../../../../../../src/plugins/data_views/common'; +import { DataViewListItem } from '../../../../../../../../../../src/plugins/data_views/common'; import { MlKibanaReactContextValue } from '../../../../../contexts/kibana'; const MAX_NUMBER_DASHBOARDS = 1000; -const MAX_NUMBER_INDEX_PATTERNS = 1000; interface CustomUrlsProps { job: Job; @@ -54,7 +53,7 @@ interface CustomUrlsProps { interface CustomUrlsState { customUrls: UrlConfig[]; dashboards: any[]; - indexPatterns: DataView[]; + dataViewListItems: DataViewListItem[]; queryEntityFieldNames: string[]; editorOpen: boolean; editorSettings?: CustomUrlSettings; @@ -67,7 +66,7 @@ class CustomUrlsUI extends Component { this.state = { customUrls: [], dashboards: [], - indexPatterns: [], + dataViewListItems: [], queryEntityFieldNames: [], editorOpen: false, }; @@ -100,9 +99,9 @@ class CustomUrlsUI extends Component { ); }); - loadIndexPatterns(MAX_NUMBER_INDEX_PATTERNS) - .then((indexPatterns) => { - this.setState({ indexPatterns }); + loadDataViewListItems() + .then((dataViewListItems) => { + this.setState({ dataViewListItems }); }) .catch((resp) => { // eslint-disable-next-line no-console @@ -121,11 +120,11 @@ class CustomUrlsUI extends Component { editNewCustomUrl = () => { // Opens the editor for configuring a new custom URL. this.setState((prevState) => { - const { dashboards, indexPatterns } = prevState; + const { dashboards, dataViewListItems } = prevState; return { editorOpen: true, - editorSettings: getNewCustomUrlDefaults(this.props.job, dashboards, indexPatterns), + editorSettings: getNewCustomUrlDefaults(this.props.job, dashboards, dataViewListItems), }; }); }; @@ -209,7 +208,7 @@ class CustomUrlsUI extends Component { editorOpen, editorSettings, dashboards, - indexPatterns, + dataViewListItems, queryEntityFieldNames, } = this.state; @@ -220,7 +219,7 @@ class CustomUrlsUI extends Component { setEditCustomUrl={this.setEditCustomUrl} savedCustomUrls={customUrls} dashboards={dashboards} - indexPatterns={indexPatterns} + dataViewListItems={dataViewListItems} queryEntityFieldNames={queryEntityFieldNames} /> ); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js index 1124052f863671..64bc7f4a517c28 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js @@ -7,7 +7,6 @@ import { checkPermission } from '../../../../capabilities/check_capabilities'; import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes'; -import { getIndexPatternNames } from '../../../../util/index_utils'; import { JOB_ACTION } from '../../../../../../common/constants/job_actions'; import { @@ -19,7 +18,6 @@ import { isClosable, isResettable, } from '../utils'; -import { getToastNotifications } from '../../../../util/dependency_cache'; import { i18n } from '@kbn/i18n'; export function actionsMenuContent( @@ -136,21 +134,7 @@ export function actionsMenuContent( return isJobBlocked(item) === false && canCreateJob; }, onClick: (item) => { - const indexPatternNames = getIndexPatternNames(); - const indexPatternTitle = item.datafeedIndices.join(','); - const jobIndicesAvailable = indexPatternNames.includes(indexPatternTitle); - - if (!jobIndicesAvailable) { - getToastNotifications().addDanger( - i18n.translate('xpack.ml.jobsList.managementActions.noSourceDataViewForClone', { - defaultMessage: - 'Unable to clone the anomaly detection job {jobId}. No data view exists for index {indexPatternTitle}.', - values: { jobId: item.id, indexPatternTitle }, - }) - ); - } else { - cloneJob(item.id); - } + cloneJob(item.id); closeMenu(true); }, 'data-test-subj': 'mlActionButtonCloneJob', diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 414d920237e8c7..fe09ed45f1274a 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -16,6 +16,7 @@ import { import { getToastNotifications } from '../../../util/dependency_cache'; import { ml } from '../../../services/ml_api_service'; import { stringMatch } from '../../../util/string_utils'; +import { getDataViewNames } from '../../../util/index_utils'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states'; import { JOB_ACTION } from '../../../../../common/constants/job_actions'; import { parseInterval } from '../../../../../common/util/parse_interval'; @@ -217,6 +218,14 @@ export async function cloneJob(jobId) { loadJobForCloning(jobId), loadFullJob(jobId, false), ]); + + const dataViewNames = await getDataViewNames(); + const jobIndicesAvailable = dataViewNames.includes(datafeed.indices.join(',')); + + if (jobIndicesAvailable === false) { + return; + } + if (cloneableJob !== undefined && originalJob?.custom_settings?.created_by !== undefined) { // if the job is from a wizards, i.e. contains a created_by property // use tempJobCloningObjects to temporarily store the job diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/index.ts index ee05f3777a5150..f4932f4536add2 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ChartLoader, LineChartData, LineChartPoint } from './chart_loader'; +export type { LineChartData, LineChartPoint } from './chart_loader'; +export { ChartLoader } from './chart_loader'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/components/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/components/index.ts index f8de9af407285a..cabbdf302f87f5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/components/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/components/index.ts @@ -6,4 +6,5 @@ */ export { JobGroupsInput } from './job_groups_input'; -export { TimeRangePicker, TimeRange } from './time_range_picker'; +export type { TimeRange } from './time_range_picker'; +export { TimeRangePicker } from './time_range_picker'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts index ec4e4c0e0c7eca..ccc26a3e0b6ae5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts @@ -12,8 +12,8 @@ export { PopulationJobCreator } from './population_job_creator'; export { AdvancedJobCreator } from './advanced_job_creator'; export { CategorizationJobCreator } from './categorization_job_creator'; export { RareJobCreator } from './rare_job_creator'; +export type { JobCreatorType } from './type_guards'; export { - JobCreatorType, isSingleMetricJobCreator, isMultiMetricJobCreator, isPopulationJobCreator, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/index.ts index 72460af14623d8..2178086de5b919 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { JobRunner, ProgressSubscriber } from './job_runner'; +export type { ProgressSubscriber } from './job_runner'; +export { JobRunner } from './job_runner'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/index.ts index 054fcbab0c7a89..71de7aa3724f1b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { JobValidator, Validation, BasicValidations, ValidationSummary } from './job_validator'; +export type { Validation, BasicValidations, ValidationSummary } from './job_validator'; +export { JobValidator } from './job_validator'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts index 19c950439fdda9..e13a74e76f5bab 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { ResultsLoader, Results, ModelItem, Anomaly } from './results_loader'; +export type { Results, ModelItem, Anomaly } from './results_loader'; +export { ResultsLoader } from './results_loader'; export { CategorizationExamplesLoader } from './categorization_examples_loader'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx index c402ee4bf97994..e56ff02b9e6da7 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view.tsx @@ -130,7 +130,7 @@ export const ChangeDataViewModal: FC = ({ onClose }) => { @@ -140,7 +140,7 @@ export const ChangeDataViewModal: FC = ({ onClose }) => { <> @@ -162,7 +162,7 @@ export const ChangeDataViewModal: FC = ({ onClose }) => { name: i18n.translate( 'xpack.ml.newJob.wizard.datafeedStep.dataView.step1.dataView', { - defaultMessage: 'Index pattern', + defaultMessage: 'Data view', } ), }, @@ -188,7 +188,7 @@ export const ChangeDataViewModal: FC = ({ onClose }) => { ) : ( @@ -244,14 +244,14 @@ const ValidationMessage: FC<{ title={i18n.translate( 'xpack.ml.newJob.wizard.datafeedStep.dataView.validation.noDetectors.title', { - defaultMessage: 'Index pattern valid', + defaultMessage: 'Data view valid', } )} color="primary" > ); @@ -263,14 +263,14 @@ const ValidationMessage: FC<{ title={i18n.translate( 'xpack.ml.newJob.wizard.datafeedStep.dataView.validation.valid.title', { - defaultMessage: 'Index pattern valid', + defaultMessage: 'Data view valid', } )} color="primary" > ); @@ -280,14 +280,14 @@ const ValidationMessage: FC<{ title={i18n.translate( 'xpack.ml.newJob.wizard.datafeedStep.dataView.validation.possiblyInvalid.title', { - defaultMessage: 'Index pattern possibly invalid', + defaultMessage: 'Data view possibly invalid', } )} color="warning" > @@ -299,14 +299,14 @@ const ValidationMessage: FC<{ title={i18n.translate( 'xpack.ml.newJob.wizard.datafeedStep.dataView.validation.invalid.title', { - defaultMessage: 'Index pattern invalid', + defaultMessage: 'Data view invalid', } )} color="danger" > diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view_button.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view_button.tsx index dc9af26236d8c4..a83782b9c14326 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view_button.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/change_data_view_button.tsx @@ -27,7 +27,7 @@ export const ChangeDataView: FC<{ isDisabled: boolean }> = ({ isDisabled }) => { > diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/description.tsx index 2632660738a587..2eeab08229348e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/data_view/description.tsx @@ -12,7 +12,7 @@ import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.datafeedStep.dataView.title', { - defaultMessage: 'Index pattern', + defaultMessage: 'Data view', }); return ( { description={ } > diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/index.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/index.tsx index 7758ec28f4bbfe..75a331ce5d0ebb 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/index.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/index.tsx @@ -5,4 +5,5 @@ * 2.0. */ -export { AdvancedDetectorModal, ModalPayload } from './advanced_detector_modal'; +export type { ModalPayload } from './advanced_detector_modal'; +export { AdvancedDetectorModal } from './advanced_detector_modal'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/index.ts index eefa14d9032424..eecbacfaecccc7 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { AggSelect, DropDownLabel, DropDownOption, DropDownProps, createLabel } from './agg_select'; +export type { DropDownLabel, DropDownOption, DropDownProps } from './agg_select'; +export { AggSelect, createLabel } from './agg_select'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index 88ed17cba00035..eb9c4bf7557077 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -38,10 +38,10 @@ export function useEstimateBucketSpan() { end: jobCreator.end, }, fields: jobCreator.fields.map((f) => (f.id === EVENT_RATE_FIELD_ID ? null : f.id)), - index: mlContext.currentIndexPattern.title, + index: mlContext.currentDataView.title, query: mlContext.combinedQuery, splitField: undefined, - timeField: mlContext.currentIndexPattern.timeFieldName, + timeField: mlContext.currentDataView.timeFieldName, runtimeMappings: jobCreator.runtimeMappings ?? undefined, indicesOptions: jobCreator.datafeedConfig.indices_options, }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx index 4c13130ae4ce34..9924bf1674f790 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx @@ -104,7 +104,7 @@ export const TimeRangeStep: FC = ({ setCurrentStep, isCurrentStep }) { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } - return `jobs/new_job/${page}?index=${indexPatternId}&_g=()`; + const [dv] = await dataViewsContract?.find(datafeed.indices.join(',')); + if (!dv) { + return null; + } + return dv.id ?? dv.title; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx index 15ca0b82695ffb..5e621589772808 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx @@ -45,9 +45,9 @@ export const Page: FC = () => { const [recognizerResultsCount, setRecognizerResultsCount] = useState(0); - const { currentSavedSearch, currentIndexPattern } = mlContext; + const { currentSavedSearch, currentDataView } = mlContext; - const isTimeBasedIndex = timeBasedIndexCheck(currentIndexPattern); + const isTimeBasedIndex = timeBasedIndexCheck(currentDataView); const indexWarningTitle = !isTimeBasedIndex && isSavedSearchSavedObject(currentSavedSearch) ? i18n.translate( @@ -57,13 +57,13 @@ export const Page: FC = () => { '{savedSearchTitle} uses data view {dataViewName} which is not time based', values: { savedSearchTitle: currentSavedSearch.attributes.title as string, - dataViewName: currentIndexPattern.title, + dataViewName: currentDataView.title, }, } ) : i18n.translate('xpack.ml.newJob.wizard.jobType.dataViewNotTimeBasedMessage', { defaultMessage: 'Data view {dataViewName} is not time based', - values: { dataViewName: currentIndexPattern.title }, + values: { dataViewName: currentDataView.title }, }); const pageTitleLabel = isSavedSearchSavedObject(currentSavedSearch) @@ -73,7 +73,7 @@ export const Page: FC = () => { }) : i18n.translate('xpack.ml.newJob.wizard.jobType.dataViewPageTitleLabel', { defaultMessage: 'data view {dataViewName}', - values: { dataViewName: currentIndexPattern.title }, + values: { dataViewName: currentDataView.title }, }); const recognizerResults = { @@ -85,13 +85,13 @@ export const Page: FC = () => { const getUrlParams = () => { return !isSavedSearchSavedObject(currentSavedSearch) - ? `?index=${currentIndexPattern.id}` + ? `?index=${currentDataView.id}` : `?savedSearchId=${currentSavedSearch.id}`; }; const addSelectionToRecentlyAccessed = async () => { const title = !isSavedSearchSavedObject(currentSavedSearch) - ? currentIndexPattern.title + ? currentDataView.title : (currentSavedSearch.attributes.title as string); const mlLocator = share.url.locators.get(ML_APP_LOCATOR)!; @@ -101,7 +101,7 @@ export const Page: FC = () => { pageState: { ...(currentSavedSearch?.id ? { savedSearchId: currentSavedSearch.id } - : { index: currentIndexPattern.id }), + : { index: currentDataView.id }), }, }, { absolute: true } @@ -270,7 +270,7 @@ export const Page: FC = () => { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx index f708ea83f1d0d3..52e3c55afc15aa 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx @@ -55,7 +55,7 @@ export interface PageProps { export const Page: FC = ({ existingJobsAndGroups, jobType }) => { const mlContext = useMlContext(); const jobCreator = jobCreatorFactory(jobType)( - mlContext.currentIndexPattern, + mlContext.currentDataView, mlContext.currentSavedSearch, mlContext.combinedQuery ); @@ -184,7 +184,7 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { chartInterval.setMaxBars(MAX_BARS); chartInterval.setInterval('auto'); - const chartLoader = new ChartLoader(mlContext.currentIndexPattern, mlContext.combinedQuery); + const chartLoader = new ChartLoader(mlContext.currentDataView, mlContext.combinedQuery); const jobValidator = new JobValidator(jobCreator); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx index c24fb2521ea580..b2e5a4ee1c22f2 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx @@ -40,10 +40,10 @@ export const WizardSteps: FC = ({ currentStep, setCurrentStep }) => { defaultMessage: 'New job from saved search {title}', values: { title: mlContext.currentSavedSearch.attributes.title as string }, }); - } else if (mlContext.currentIndexPattern.id !== undefined) { + } else if (mlContext.currentDataView.id !== undefined) { return i18n.translate('xpack.ml.newJob.wizard.stepComponentWrapper.summaryTitleDataView', { defaultMessage: 'New job from data view {dataViewName}', - values: { dataViewName: mlContext.currentIndexPattern.title }, + values: { dataViewName: mlContext.currentDataView.title }, }); } return ''; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx index 8dc0ca0ddbdc47..c996f50ea5018a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx @@ -53,7 +53,7 @@ export const JobSettingsForm: FC = ({ jobs, }) => { const { from, to } = getTimeFilterRange(); - const { currentIndexPattern: indexPattern } = useMlContext(); + const { currentDataView: dataView } = useMlContext(); const jobPrefixValidator = composeValidators( patternValidator(/^([a-z0-9]+[a-z0-9\-_]*)?$/), @@ -181,7 +181,7 @@ export const JobSettingsForm: FC = ({ } checked={formState.useFullIndexData} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index dbf9bce64b114e..7603855d47e5cb 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -92,7 +92,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const { currentSavedSearch: savedSearch, - currentIndexPattern: indexPattern, + currentDataView: dataView, combinedQuery, } = useMlContext(); const pageTitle = @@ -103,7 +103,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { }) : i18n.translate('xpack.ml.newJob.recognize.dataViewPageTitle', { defaultMessage: 'data view {dataViewName}', - values: { dataViewName: indexPattern.title }, + values: { dataViewName: dataView.title }, }); const displayQueryWarning = savedSearch !== null; const tempQuery = savedSearch === null ? undefined : combinedQuery; @@ -135,10 +135,10 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { timeRange: TimeRange ): Promise => { if (useFullIndexData) { - const runtimeMappings = indexPattern.getComputedFields().runtimeFields as RuntimeMappings; + const runtimeMappings = dataView.getComputedFields().runtimeFields as RuntimeMappings; const { start, end } = await ml.getTimeFieldRange({ - index: indexPattern.title, - timeFieldName: indexPattern.timeFieldName, + index: dataView.title, + timeFieldName: dataView.timeFieldName, query: combinedQuery, ...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}), }); @@ -178,7 +178,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { moduleId, prefix: resultJobPrefix, query: tempQuery, - indexPatternName: indexPattern.title, + indexPatternName: dataView.title, useDedicatedIndex, startDatafeed: startDatafeedAfterSave, ...(jobOverridesPayload !== null ? { jobOverrides: jobOverridesPayload } : {}), diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts index b4e581d46bcc39..6dca394c0227dd 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts @@ -19,7 +19,7 @@ import { CreateLinkWithUserDefaults } from '../../../components/custom_hooks/use */ export function checkViewOrCreateJobs( moduleId: string, - indexPatternId: string, + dataViewId: string, createLinkWithUserDefaults: CreateLinkWithUserDefaults, navigateToPath: NavigateToPath ): Promise { @@ -36,7 +36,7 @@ export function checkViewOrCreateJobs( await navigateToPath(url); reject(); } else { - await navigateToPath(`/jobs/new_job/recognize?id=${moduleId}&index=${indexPatternId}`); + await navigateToPath(`/jobs/new_job/recognize?id=${moduleId}&index=${dataViewId}`); reject(); } }) diff --git a/x-pack/plugins/ml/public/application/routing/index.ts b/x-pack/plugins/ml/public/application/routing/index.ts index f4b076db7ab378..1aac673b5411cd 100644 --- a/x-pack/plugins/ml/public/application/routing/index.ts +++ b/x-pack/plugins/ml/public/application/routing/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { MlRouter, MlRoute } from './router'; +export type { MlRoute } from './router'; +export { MlRouter } from './router'; diff --git a/x-pack/plugins/ml/public/application/routing/resolvers.ts b/x-pack/plugins/ml/public/application/routing/resolvers.ts index 3479005809efba..d95dfc9d2784f6 100644 --- a/x-pack/plugins/ml/public/application/routing/resolvers.ts +++ b/x-pack/plugins/ml/public/application/routing/resolvers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { loadIndexPatterns, loadSavedSearches } from '../util/index_utils'; +import { cacheDataViewsContract, loadSavedSearches } from '../util/index_utils'; import { checkFullLicense } from '../license'; import { checkGetJobsCapabilitiesResolver } from '../capabilities/check_capabilities'; import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes'; @@ -21,18 +21,18 @@ export interface ResolverResults { } interface BasicResolverDependencies { - indexPatterns: DataViewsContract; + dataViewsContract: DataViewsContract; redirectToMlAccessDeniedPage: () => Promise; } export const basicResolvers = ({ - indexPatterns, + dataViewsContract, redirectToMlAccessDeniedPage, }: BasicResolverDependencies): Resolvers => ({ checkFullLicense, getMlNodeCount, loadMlServerInfo, - loadIndexPatterns: () => loadIndexPatterns(indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(dataViewsContract), checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), loadSavedSearches, }); diff --git a/x-pack/plugins/ml/public/application/routing/router.tsx b/x-pack/plugins/ml/public/application/routing/router.tsx index 847dcc1ae11078..5f220d86cd6ad1 100644 --- a/x-pack/plugins/ml/public/application/routing/router.tsx +++ b/x-pack/plugins/ml/public/application/routing/router.tsx @@ -43,7 +43,7 @@ export interface PageProps { interface PageDependencies { config: IUiSettingsClient; history: AppMountParameters['history']; - indexPatterns: DataViewsContract; + dataViewsContract: DataViewsContract; setBreadcrumbs: ChromeStart['setBreadcrumbs']; redirectToMlAccessDeniedPage: () => Promise; } diff --git a/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx b/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx index afb36b89732f14..10b2d1438c32b4 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx @@ -27,7 +27,7 @@ export const accessDeniedRouteFactory = (): MlRoute => ({ }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, {}); + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {}); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx index ab57c264683ca4..e550eaa338b08b 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx @@ -44,10 +44,10 @@ const PageWrapper: FC = ({ location, deps }) => { sort: false, }); - const { context } = useResolver(index, savedSearchId, deps.config, { + const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, { ...basicResolvers(deps), analyticsFields: () => - loadNewJobCapabilities(index, savedSearchId, deps.indexPatterns, DATA_FRAME_ANALYTICS), + loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, DATA_FRAME_ANALYTICS), }); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx index 98603f69f382f7..49a756fd12ced5 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx @@ -39,7 +39,13 @@ export const analyticsJobExplorationRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); const [globalState] = useUrlState('_g'); diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx index d6a70105a71b42..2e55a9e85fb6eb 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx @@ -35,7 +35,13 @@ export const analyticsJobsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx index 77e7d3b3740ef7..29bf616041624b 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx @@ -35,7 +35,13 @@ export const analyticsMapRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx index b2466c74667472..4db8d9a0c72f39 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx @@ -32,7 +32,7 @@ export const selectorRouteFactory = ( const PageWrapper: FC = ({ location, deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkBasicLicense, checkFindFileStructurePrivilege: () => checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage), diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx index 5b16bf8352b27b..9e48940a29b568 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx @@ -16,7 +16,7 @@ import { FileDataVisualizerPage } from '../../../datavisualizer/file_based'; import { checkBasicLicense } from '../../../license'; import { checkFindFileStructurePrivilegeResolver } from '../../../capabilities/check_capabilities'; -import { loadIndexPatterns } from '../../../util/index_utils'; +import { cacheDataViewsContract } from '../../../util/index_utils'; import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; @@ -41,9 +41,9 @@ export const fileBasedRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkBasicLicense, - loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), checkFindFileStructurePrivilege: () => checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage), }); diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx index 04543a28ab3e6b..dde1a3a7685532 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx @@ -18,7 +18,7 @@ import { IndexDataVisualizerPage as Page } from '../../../datavisualizer/index_b import { checkBasicLicense } from '../../../license'; import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities'; -import { loadIndexPatterns } from '../../../util/index_utils'; +import { cacheDataViewsContract } from '../../../util/index_utils'; import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; export const indexBasedRouteFactory = ( @@ -43,9 +43,9 @@ const PageWrapper: FC = ({ location, deps }) => { const { redirectToMlAccessDeniedPage } = deps; const { index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context } = useResolver(index, savedSearchId, deps.config, { + const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, { checkBasicLicense, - loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), }); diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 49e7857eee082c..e50dc301f970b5 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -57,11 +57,17 @@ export const explorerRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context, results } = useResolver(undefined, undefined, deps.config, { - ...basicResolvers(deps), - jobs: mlJobService.loadJobsWrapper, - jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), - }); + const { context, results } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + jobs: mlJobService.loadJobsWrapper, + jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), + } + ); const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx index ecaa0a9e42e3f6..52cdfc5c42b2d0 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx @@ -39,7 +39,13 @@ export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: st }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); const timefilter = useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true }); const [globalState, setGlobalState] = useUrlState('_g'); diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx index 53057cb16c1320..e92bac32debcbd 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx @@ -17,7 +17,7 @@ import { basicResolvers } from '../../resolvers'; import { Page, preConfiguredJobRedirect } from '../../../jobs/new_job/pages/index_or_search'; import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; import { checkBasicLicense } from '../../../license'; -import { loadIndexPatterns } from '../../../util/index_utils'; +import { cacheDataViewsContract } from '../../../util/index_utils'; import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities'; enum MODE { @@ -86,11 +86,11 @@ const PageWrapper: FC = ({ nextStepPath, deps, mode }) = const newJobResolvers = { ...basicResolvers(deps), preConfiguredJobRedirect: () => - preConfiguredJobRedirect(deps.indexPatterns, basePath.get(), navigateToUrl), + preConfiguredJobRedirect(deps.dataViewsContract, basePath.get(), navigateToUrl), }; const dataVizResolvers = { checkBasicLicense, - loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), }; @@ -98,6 +98,7 @@ const PageWrapper: FC = ({ nextStepPath, deps, mode }) = undefined, undefined, deps.config, + deps.dataViewsContract, mode === MODE.NEW_JOB ? newJobResolvers : dataVizResolvers ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx index 235a91ea737911..cdd2b890f086ed 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx @@ -35,7 +35,13 @@ export const jobTypeRouteFactory = (navigateToPath: NavigateToPath, basePath: st const PageWrapper: FC = ({ location, deps }) => { const { index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context } = useResolver(index, savedSearchId, deps.config, basicResolvers(deps)); + const { context } = useResolver( + index, + savedSearchId, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx index 125d7cbb0f84a9..7e7da6c79a858f 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx @@ -49,10 +49,16 @@ export const checkViewOrCreateRouteFactory = (): MlRoute => ({ const PageWrapper: FC = ({ location, deps }) => { const { id, index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context, results } = useResolver(index, savedSearchId, deps.config, { - ...basicResolvers(deps), - existingJobsAndGroups: mlJobService.getJobAndGroupIds, - }); + const { context, results } = useResolver( + index, + savedSearchId, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + existingJobsAndGroups: mlJobService.getJobAndGroupIds, + } + ); return ( @@ -62,7 +68,7 @@ const PageWrapper: FC = ({ location, deps }) => { }; const CheckViewOrCreateWrapper: FC = ({ location, deps }) => { - const { id: moduleId, index: indexPatternId }: Record = parse(location.search, { + const { id: moduleId, index: dataViewId }: Record = parse(location.search, { sort: false, }); const { createLinkWithUserDefaults } = useCreateADLinks(); @@ -70,9 +76,9 @@ const CheckViewOrCreateWrapper: FC = ({ location, deps }) => { const navigateToPath = useNavigateToPath(); // the single resolver checkViewOrCreateJobs redirects only. so will always reject - useResolver(undefined, undefined, deps.config, { + useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkViewOrCreateJobs: () => - checkViewOrCreateJobs(moduleId, indexPatternId, createLinkWithUserDefaults, navigateToPath), + checkViewOrCreateJobs(moduleId, dataViewId, createLinkWithUserDefaults, navigateToPath), }); return null; }; diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx index d95fbcaba9f67f..7953d15a55b1e0 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx @@ -153,13 +153,19 @@ const PageWrapper: FC = ({ location, jobType, deps }) => { ); const { index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context, results } = useResolver(index, savedSearchId, deps.config, { - ...basicResolvers(deps), - privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage), - jobCaps: () => - loadNewJobCapabilities(index, savedSearchId, deps.indexPatterns, ANOMALY_DETECTOR), - existingJobsAndGroups: mlJobService.getJobAndGroupIds, - }); + const { context, results } = useResolver( + index, + savedSearchId, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage), + jobCaps: () => + loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, ANOMALY_DETECTOR), + existingJobsAndGroups: mlJobService.getJobAndGroupIds, + } + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/overview.tsx b/x-pack/plugins/ml/public/application/routing/routes/overview.tsx index 9f21609db35ca3..dd3fc70a6425e4 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/overview.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/overview.tsx @@ -44,7 +44,7 @@ export const overviewRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx index d8a59ebed9de69..08949d5d514b2b 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx @@ -38,7 +38,7 @@ export const calendarListRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx index 18c4d43b564d7b..05f9f92479f45c 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx @@ -83,7 +83,7 @@ const PageWrapper: FC = ({ location, mode, deps }) => { ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE ); - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage), diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx index d28569ff8aaa40..8e956ecf59d5d5 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx @@ -39,7 +39,7 @@ export const filterListRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx index e12a33b48439cf..cf320632106242 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx @@ -85,7 +85,7 @@ const PageWrapper: FC = ({ location, mode, deps }) => { ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE ); - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage), diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx index 51b8e8e8376330..5e0e8dbc801e7f 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx @@ -37,7 +37,7 @@ export const settingsRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index 8c704ef4240a05..91697b8a89bd79 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -65,11 +65,17 @@ export const timeSeriesExplorerRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context, results } = useResolver(undefined, undefined, deps.config, { - ...basicResolvers(deps), - jobs: mlJobService.loadJobsWrapper, - jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), - }); + const { context, results } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + jobs: mlJobService.loadJobsWrapper, + jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), + } + ); const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx index 9367a58372484e..646df84aee5e70 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx @@ -35,7 +35,13 @@ export const modelsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx index bd88527af1a8df..c0bd22e657bb03 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx @@ -35,7 +35,13 @@ export const nodesListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts index 4c5c8c7b21ddd9..98deb8df7b60d5 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts @@ -11,6 +11,7 @@ import { IUiSettingsClient } from 'kibana/public'; import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; import { useNotifications } from '../contexts/kibana'; +import type { DataViewsContract } from '../../../../../../src/plugins/data_views/public'; import { useResolver } from './use_resolver'; @@ -44,9 +45,9 @@ describe('useResolver', () => { jest.useRealTimers(); }); - it('should accept undefined as indexPatternId and savedSearchId.', async () => { + it('should accept undefined as dataViewId and savedSearchId.', async () => { const { result, waitForNextUpdate } = renderHook(() => - useResolver(undefined, undefined, {} as IUiSettingsClient, {}) + useResolver(undefined, undefined, {} as IUiSettingsClient, {} as DataViewsContract, {}) ); await act(async () => { @@ -64,9 +65,9 @@ describe('useResolver', () => { ], }, }, - currentIndexPattern: null, + currentDataView: null, currentSavedSearch: null, - indexPatterns: null, + dataViewsContract: {}, kibanaConfig: {}, }, results: {}, @@ -75,8 +76,10 @@ describe('useResolver', () => { expect(redirectToJobsManagementPage).toHaveBeenCalledTimes(0); }); - it('should add an error toast and redirect if indexPatternId is an empty string.', async () => { - const { result } = renderHook(() => useResolver('', undefined, {} as IUiSettingsClient, {})); + it('should add an error toast and redirect if dataViewId is an empty string.', async () => { + const { result } = renderHook(() => + useResolver('', undefined, {} as IUiSettingsClient, {} as DataViewsContract, {}) + ); await act(async () => {}); diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.ts index 62450106d0dfbe..59d64f412cc804 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.ts @@ -9,10 +9,9 @@ import { useEffect, useState } from 'react'; import { IUiSettingsClient } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { - getIndexPatternById, - getIndexPatternsContract, - getIndexPatternAndSavedSearch, - IndexPatternAndSavedSearch, + getDataViewById, + getDataViewAndSavedSearch, + DataViewAndSavedSearch, } from '../util/index_utils'; import { createSearchItems } from '../jobs/new_job/utils/new_job_utils'; import { ResolverResults, Resolvers } from './resolvers'; @@ -20,19 +19,21 @@ import { MlContextValue } from '../contexts/ml'; import { useNotifications } from '../contexts/kibana'; import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; import { ML_PAGES } from '../../../common/constants/locator'; +import type { DataViewsContract } from '../../../../../../src/plugins/data_views/public'; /** * Hook to resolve route specific requirements - * @param indexPatternId optional Kibana index pattern id, used for wizards + * @param dataViewId optional Kibana data view id, used for wizards * @param savedSearchId optional Kibana saved search id, used for wizards * @param config Kibana UI Settings * @param resolvers an array of resolvers to be executed for the route * @return { context, results } returns the ML context and resolver results */ export const useResolver = ( - indexPatternId: string | undefined, + dataViewId: string | undefined, savedSearchId: string | undefined, config: IUiSettingsClient, + dataViewsContract: DataViewsContract, resolvers: Resolvers ): { context: MlContextValue; results: ResolverResults } => { const notifications = useNotifications(); @@ -63,38 +64,38 @@ export const useResolver = ( } try { - if (indexPatternId === '') { + if (dataViewId === '') { throw new Error( i18n.translate('xpack.ml.useResolver.errorIndexPatternIdEmptyString', { - defaultMessage: 'indexPatternId must not be empty string.', + defaultMessage: 'dataViewId must not be empty string.', }) ); } - let indexPatternAndSavedSearch: IndexPatternAndSavedSearch = { + let dataViewAndSavedSearch: DataViewAndSavedSearch = { savedSearch: null, - indexPattern: null, + dataView: null, }; if (savedSearchId !== undefined) { - indexPatternAndSavedSearch = await getIndexPatternAndSavedSearch(savedSearchId); - } else if (indexPatternId !== undefined) { - indexPatternAndSavedSearch.indexPattern = await getIndexPatternById(indexPatternId); + dataViewAndSavedSearch = await getDataViewAndSavedSearch(savedSearchId); + } else if (dataViewId !== undefined) { + dataViewAndSavedSearch.dataView = await getDataViewById(dataViewId); } - const { savedSearch, indexPattern } = indexPatternAndSavedSearch; + const { savedSearch, dataView } = dataViewAndSavedSearch; const { combinedQuery } = createSearchItems( config, - indexPattern !== null ? indexPattern : undefined, + dataView !== null ? dataView : undefined, savedSearch ); setContext({ combinedQuery, - currentIndexPattern: indexPattern, + currentDataView: dataView, currentSavedSearch: savedSearch, - indexPatterns: getIndexPatternsContract(), + dataViewsContract, kibanaConfig: config, }); } catch (error) { diff --git a/x-pack/plugins/ml/public/application/services/annotations_service.tsx b/x-pack/plugins/ml/public/application/services/annotations_service.tsx index a84cd7ee000f2c..f3d50115f02062 100644 --- a/x-pack/plugins/ml/public/application/services/annotations_service.tsx +++ b/x-pack/plugins/ml/public/application/services/annotations_service.tsx @@ -17,7 +17,7 @@ export type AnnotationState = Annotation | null; /* This observable offers a way to share state between components that don't have a direct parent -> * -> child relationship. - It's also useful in mixed angularjs/React environments. + It's also useful in mixed React environments. For example, we want to trigger the flyout for editing annotations from both the timeseries_chart and the annotations_table. Since we don't want two flyout instances, @@ -75,8 +75,7 @@ export const annotation$ = new BehaviorSubject(null); /* This observable provides a way to trigger a reload of annotations based on a given event. - Instead of passing around callbacks or deeply nested props, it can be imported for both - angularjs controllers/directives and React components. + Instead of passing around callbacks or deeply nested props, it can be imported in React components. */ export const annotationsRefresh$ = new BehaviorSubject(Date.now()); export const annotationsRefreshed = () => annotationsRefresh$.next(Date.now()); diff --git a/x-pack/plugins/ml/public/application/services/field_format_service.ts b/x-pack/plugins/ml/public/application/services/field_format_service.ts index d95975156a99d9..ddcb447430a456 100644 --- a/x-pack/plugins/ml/public/application/services/field_format_service.ts +++ b/x-pack/plugins/ml/public/application/services/field_format_service.ts @@ -6,7 +6,7 @@ */ import { mlFunctionToESAggregation } from '../../../common/util/job_utils'; -import { getIndexPatternById, getIndexPatternIdFromName } from '../util/index_utils'; +import { getDataViewById, getDataViewIdFromName } from '../util/index_utils'; import { mlJobService } from './job_service'; import type { DataView } from '../../../../../../src/plugins/data_views/public'; @@ -25,35 +25,40 @@ class FieldFormatService { // configured in the datafeed of each job. // Builds a map of Kibana FieldFormats (plugins/data/common/field_formats) // against detector index by job ID. - populateFormats(jobIds: string[]): Promise { - return new Promise((resolve, reject) => { - // Populate a map of data view IDs against job ID, by finding the ID of the data - // view with a title attribute which matches the indices configured in the datafeed. - // If a Kibana data view has not been created - // for this index, then no custom field formatting will occur. - jobIds.forEach((jobId) => { - const jobObj = mlJobService.getJob(jobId); - const datafeedIndices = jobObj.datafeed_config.indices; - const id = getIndexPatternIdFromName(datafeedIndices.length ? datafeedIndices[0] : ''); - if (id !== null) { - this.indexPatternIdsByJob[jobId] = id; - } - }); + async populateFormats(jobIds: string[]): Promise { + // Populate a map of data view IDs against job ID, by finding the ID of the data + // view with a title attribute which matches the indices configured in the datafeed. + // If a Kibana data view has not been created + // for this index, then no custom field formatting will occur. + ( + await Promise.all( + jobIds.map(async (jobId) => { + const jobObj = mlJobService.getJob(jobId); + return { + jobId, + dataViewId: await getDataViewIdFromName(jobObj.datafeed_config.indices.join(',')), + }; + }) + ) + ).forEach(({ jobId, dataViewId }) => { + if (dataViewId !== null) { + this.indexPatternIdsByJob[jobId] = dataViewId; + } + }); - const promises = jobIds.map((jobId) => Promise.all([this.getFormatsForJob(jobId)])); + const promises = jobIds.map((jobId) => Promise.all([this.getFormatsForJob(jobId)])); - Promise.all(promises) - .then((fmtsByJobByDetector) => { - fmtsByJobByDetector.forEach((formatsByDetector, i) => { - this.formatsByJob[jobIds[i]] = formatsByDetector[0]; - }); + try { + const fmtsByJobByDetector = await Promise.all(promises); + fmtsByJobByDetector.forEach((formatsByDetector, i) => { + this.formatsByJob[jobIds[i]] = formatsByDetector[0]; + }); - resolve(this.formatsByJob); - }) - .catch((err) => { - reject({ formats: {}, err }); - }); - }); + return this.formatsByJob; + } catch (error) { + console.log('Error populating field formats:', error); // eslint-disable-line no-console + return { formats: {}, error }; + } } // Return the FieldFormat to use for formatting values from @@ -87,13 +92,13 @@ class FieldFormatService { const detectors = jobObj.analysis_config.detectors || []; const formatsByDetector: any[] = []; - const indexPatternId = this.indexPatternIdsByJob[jobId]; - if (indexPatternId !== undefined) { + const dataViewId = this.indexPatternIdsByJob[jobId]; + if (dataViewId !== undefined) { // Load the full data view configuration to obtain the formats of each field. - getIndexPatternById(indexPatternId) - .then((indexPatternData) => { + getDataViewById(dataViewId) + .then((dataView) => { // Store the FieldFormat for each job by detector_index. - const fieldList = indexPatternData.fields; + const fieldList = dataView.fields; detectors.forEach((dtr) => { const esAgg = mlFunctionToESAggregation(dtr.function); // distinct_count detectors should fall back to the default @@ -101,8 +106,7 @@ class FieldFormatService { if (dtr.field_name !== undefined && esAgg !== 'cardinality') { const field = fieldList.getByName(dtr.field_name); if (field !== undefined) { - formatsByDetector[dtr.detector_index!] = - indexPatternData.getFormatterForField(field); + formatsByDetector[dtr.detector_index!] = dataView.getFormatterForField(field); } } }); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts index b4d3c47364a3dd..8c05d5a4182192 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts @@ -6,7 +6,7 @@ */ import { DataView, DataViewsContract } from '../../../../../../../src/plugins/data_views/public'; -import { getIndexPatternAndSavedSearch } from '../../util/index_utils'; +import { getDataViewAndSavedSearch } from '../../util/index_utils'; import { JobType } from '../../../../common/types/saved_objects'; import { newJobCapsServiceAnalytics } from '../new_job_capabilities/new_job_capabilities_service_analytics'; import { newJobCapsService } from '../new_job_capabilities/new_job_capabilities_service'; @@ -17,9 +17,9 @@ export const DATA_FRAME_ANALYTICS = 'data-frame-analytics'; // called in the routing resolve block to initialize the NewJobCapabilites // service for the corresponding job type with the currently selected data view export function loadNewJobCapabilities( - indexPatternId: string, + dataViewId: string, savedSearchId: string, - indexPatterns: DataViewsContract, + dataViewContract: DataViewsContract, jobType: JobType ) { return new Promise(async (resolve, reject) => { @@ -27,24 +27,24 @@ export function loadNewJobCapabilities( const serviceToUse = jobType === ANOMALY_DETECTOR ? newJobCapsService : newJobCapsServiceAnalytics; - if (indexPatternId !== undefined) { + if (dataViewId !== undefined) { // index pattern is being used - const indexPattern: DataView = await indexPatterns.get(indexPatternId); - await serviceToUse.initializeFromIndexPattern(indexPattern); + const dataView: DataView = await dataViewContract.get(dataViewId); + await serviceToUse.initializeFromDataVIew(dataView); resolve(serviceToUse.newJobCaps); } else if (savedSearchId !== undefined) { // saved search is being used // load the data view from the saved search - const { indexPattern } = await getIndexPatternAndSavedSearch(savedSearchId); + const { dataView } = await getDataViewAndSavedSearch(savedSearchId); - if (indexPattern === null) { + if (dataView === null) { // eslint-disable-next-line no-console console.error('Cannot retrieve data view from saved search'); reject(); return; } - await serviceToUse.initializeFromIndexPattern(indexPattern); + await serviceToUse.initializeFromDataVIew(dataView); resolve(serviceToUse.newJobCaps); } else { reject(); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts index 49c8b08007d52d..373de76d594576 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts @@ -20,7 +20,7 @@ jest.mock('../ml_api_service', () => ({ }, })); -const indexPattern = { +const dataView = { id: 'cloudwatch-*', title: 'cloudwatch-*', } as unknown as DataView; @@ -28,7 +28,7 @@ const indexPattern = { describe('new_job_capabilities_service', () => { describe('cloudwatch newJobCaps()', () => { it('can construct job caps objects from endpoint json', async () => { - await newJobCapsService.initializeFromIndexPattern(indexPattern); + await newJobCapsService.initializeFromDataVIew(dataView); const { fields, aggs } = await newJobCapsService.newJobCaps; const networkOutField = fields.find((f) => f.id === 'NetworkOut') || { aggs: [] }; @@ -47,7 +47,7 @@ describe('new_job_capabilities_service', () => { }); it('job caps including text fields', async () => { - await newJobCapsService.initializeFromIndexPattern(indexPattern, true, false); + await newJobCapsService.initializeFromDataVIew(dataView, true, false); const { fields, aggs } = await newJobCapsService.newJobCaps; expect(fields).toHaveLength(13); // one more field @@ -55,7 +55,7 @@ describe('new_job_capabilities_service', () => { }); it('job caps excluding event rate', async () => { - await newJobCapsService.initializeFromIndexPattern(indexPattern, false, true); + await newJobCapsService.initializeFromDataVIew(dataView, false, true); const { fields, aggs } = await newJobCapsService.newJobCaps; expect(fields).toHaveLength(11); // one less field diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts index 45dc71ed6a6b93..210c409e8e281c 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts @@ -36,8 +36,8 @@ class NewJobCapsService extends NewJobCapabilitiesServiceBase { return filterCategoryFields(this._fields); } - public async initializeFromIndexPattern( - indexPattern: DataView, + public async initializeFromDataVIew( + dataView: DataView, includeEventRateField = true, removeTextFields = true ) { @@ -45,8 +45,8 @@ class NewJobCapsService extends NewJobCapabilitiesServiceBase { this._includeEventRateField = includeEventRateField; this._removeTextFields = removeTextFields; - const resp = await ml.jobs.newJobCaps(indexPattern.title, indexPattern.type === 'rollup'); - const { fields: allFields, aggs } = createObjects(resp, indexPattern.title); + const resp = await ml.jobs.newJobCaps(dataView.title, dataView.type === 'rollup'); + const { fields: allFields, aggs } = createObjects(resp, dataView.title); if (this._includeEventRateField === true) { addEventRateField(aggs, allFields); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts index f8f9ae6b2b0a37..2786c14127ecd3 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts @@ -43,14 +43,14 @@ export function removeNestedFieldChildren(resp: NewJobCapsResponse, indexPattern } class NewJobCapsServiceAnalytics extends NewJobCapabilitiesServiceBase { - public async initializeFromIndexPattern(indexPattern: DataView) { + public async initializeFromDataVIew(dataView: DataView) { try { const resp: NewJobCapsResponse = await ml.dataFrameAnalytics.newJobCapsAnalytics( - indexPattern.title, - indexPattern.type === 'rollup' + dataView.title, + dataView.type === 'rollup' ); - const allFields = removeNestedFieldChildren(resp, indexPattern.title); + const allFields = removeNestedFieldChildren(resp, dataView.title); const { fieldsPreferringKeyword } = processTextAndKeywordFields(allFields); diff --git a/x-pack/plugins/ml/public/application/services/toast_notification_service/index.ts b/x-pack/plugins/ml/public/application/services/toast_notification_service/index.ts index d303cef3203b6d..d2f946777284d5 100644 --- a/x-pack/plugins/ml/public/application/services/toast_notification_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/toast_notification_service/index.ts @@ -5,8 +5,8 @@ * 2.0. */ +export type { ToastNotificationService } from './toast_notification_service'; export { - ToastNotificationService, toastNotificationServiceProvider, useToastNotificationService, getToastNotificationService, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js index 1854982c8db0bf..7f9fcc7bc55170 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js @@ -78,6 +78,12 @@ function getColumns(viewForecast) { // TODO - add in ml-info-icon to the h3 element, // then remove tooltip and inline style. export function ForecastsList({ forecasts, viewForecast }) { + const getRowProps = (item) => { + return { + 'data-test-subj': `mlForecastsListRow row-${item.rowId}`, + }; + }; + return (

); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 87131583e44eb4..cad5bb68fb62b7 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -547,9 +547,18 @@ class TimeseriesChartIntl extends Component { // Create the path elements for the forecast value line and bounds area. if (contextForecastData) { - fcsGroup.append('path').attr('class', 'area forecast'); - fcsGroup.append('path').attr('class', 'values-line forecast'); - fcsGroup.append('g').attr('class', 'focus-chart-markers forecast'); + fcsGroup + .append('path') + .attr('class', 'area forecast') + .attr('data-test-subj', 'mlForecastArea'); + fcsGroup + .append('path') + .attr('class', 'values-line forecast') + .attr('data-test-subj', 'mlForecastValuesline'); + fcsGroup + .append('g') + .attr('class', 'focus-chart-markers forecast') + .attr('data-test-subj', 'mlForecastMarkers'); } fcsGroup diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 9b8770350909ea..7d90d748218e94 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -720,9 +720,7 @@ export class TimeSeriesExplorer extends React.Component { appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, detectorId); } // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. - mlFieldFormatService.populateFormats([jobId]).catch((err) => { - console.log('Error populating field formats:', err); - }); + mlFieldFormatService.populateFormats([jobId]); } componentDidMount() { @@ -1170,9 +1168,13 @@ export class TimeSeriesExplorer extends React.Component { + {i18n.translate('xpack.ml.timeSeriesExplorer.showForecastLabel', { + defaultMessage: 'show forecast', + })} + + } checked={showForecast} onChange={this.toggleShowForecastHandler} /> diff --git a/x-pack/plugins/ml/public/application/util/component_utils.ts b/x-pack/plugins/ml/public/application/util/component_utils.ts new file mode 100644 index 00000000000000..764e4f0edd83ba --- /dev/null +++ b/x-pack/plugins/ml/public/application/util/component_utils.ts @@ -0,0 +1,17 @@ +/* + * 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 { MouseEvent } from 'react'; + +/** + * Removes focus from a button element when clicked, for example to + * ensure a wrapping tooltip is hidden on click. + */ +export const blurButtonOnClick = (callback: Function) => (event: MouseEvent) => { + (event.target as HTMLButtonElement).blur(); + callback(); +}; diff --git a/x-pack/plugins/ml/public/application/util/index_utils.ts b/x-pack/plugins/ml/public/application/util/index_utils.ts index b105761e5ebcff..5d35081dcb0fc6 100644 --- a/x-pack/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/plugins/ml/public/application/util/index_utils.ts @@ -9,17 +9,13 @@ import { i18n } from '@kbn/i18n'; import type { Query } from '../../../../../../src/plugins/data/public'; import type { DataView, DataViewsContract } from '../../../../../../src/plugins/data_views/public'; import type { SavedSearchSavedObject } from '../../../common/types/kibana'; -import { getToastNotifications, getSavedObjectsClient, getDataViews } from './dependency_cache'; +import { getToastNotifications, getSavedObjectsClient } from './dependency_cache'; -let indexPatternCache: DataView[] = []; let savedSearchesCache: SavedSearchSavedObject[] = []; -let indexPatternsContract: DataViewsContract | null = null; +let dataViewsContract: DataViewsContract | null = null; -export async function loadIndexPatterns(indexPatterns: DataViewsContract) { - indexPatternsContract = indexPatterns; - const dataViewsContract = getDataViews(); - indexPatternCache = await dataViewsContract.find('*', 10000); - return indexPatternCache; +export async function cacheDataViewsContract(dvc: DataViewsContract) { + dataViewsContract = dvc; } export function loadSavedSearches() { @@ -41,29 +37,45 @@ export async function loadSavedSearchById(id: string) { return ss.error === undefined ? ss : null; } -export function getIndexPatterns() { - return indexPatternCache; +export async function getDataViewNames() { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } + return (await dataViewsContract.getIdsWithTitle()).map(({ title }) => title); } -export function getIndexPatternsContract() { - return indexPatternsContract; +export async function getDataViewIdFromName(name: string): Promise { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } + const [dv] = await dataViewsContract?.find(name); + if (!dv) { + return null; + } + return dv.id ?? dv.title; } -export function getIndexPatternNames() { - return indexPatternCache.map((i) => i.title); -} +export function getDataViewById(id: string): Promise { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } -export function getIndexPatternIdFromName(name: string) { - return indexPatternCache.find((i) => i.title === name)?.id ?? null; + if (id) { + return dataViewsContract.get(id); + } else { + return dataViewsContract.create({}); + } } -export interface IndexPatternAndSavedSearch { + +export interface DataViewAndSavedSearch { savedSearch: SavedSearchSavedObject | null; - indexPattern: DataView | null; + dataView: DataView | null; } -export async function getIndexPatternAndSavedSearch(savedSearchId: string) { - const resp: IndexPatternAndSavedSearch = { + +export async function getDataViewAndSavedSearch(savedSearchId: string) { + const resp: DataViewAndSavedSearch = { savedSearch: null, - indexPattern: null, + dataView: null, }; if (savedSearchId === undefined) { @@ -74,8 +86,8 @@ export async function getIndexPatternAndSavedSearch(savedSearchId: string) { if (ss === null) { return resp; } - const indexPatternId = ss.references.find((r) => r.type === 'index-pattern')?.id; - resp.indexPattern = await getIndexPatternById(indexPatternId!); + const dataViewId = ss.references.find((r) => r.type === 'index-pattern')?.id; + resp.dataView = await getDataViewById(dataViewId!); resp.savedSearch = ss; return resp; } @@ -88,18 +100,6 @@ export function getQueryFromSavedSearchObject(savedSearch: SavedSearchSavedObjec }; } -export function getIndexPatternById(id: string): Promise { - if (indexPatternsContract !== null) { - if (id) { - return indexPatternsContract.get(id); - } else { - return indexPatternsContract.create({}); - } - } else { - throw new Error('Data views are not initialized!'); - } -} - export function getSavedSearchById(id: string): SavedSearchSavedObject | undefined { return savedSearchesCache.find((s) => s.id === id); } @@ -109,14 +109,14 @@ export function getSavedSearchById(id: string): SavedSearchSavedObject | undefin * an optional flag will trigger the display a notification at the top of the page * warning that the index is not time based */ -export function timeBasedIndexCheck(indexPattern: DataView, showNotification = false) { - if (!indexPattern.isTimeBased()) { +export function timeBasedIndexCheck(dataView: DataView, showNotification = false) { + if (!dataView.isTimeBased()) { if (showNotification) { const toastNotifications = getToastNotifications(); toastNotifications.addWarning({ title: i18n.translate('xpack.ml.dataViewNotBasedOnTimeSeriesNotificationTitle', { defaultMessage: 'The data view {dataViewName} is not based on a time series', - values: { dataViewName: indexPattern.title }, + values: { dataViewName: dataView.title }, }), text: i18n.translate('xpack.ml.dataViewNotBasedOnTimeSeriesNotificationDescription', { defaultMessage: 'Anomaly detection only runs over time-based indices', diff --git a/x-pack/plugins/ml/public/index.ts b/x-pack/plugins/ml/public/index.ts index 6af8b8a6c876dc..4913969bf54833 100755 --- a/x-pack/plugins/ml/public/index.ts +++ b/x-pack/plugins/ml/public/index.ts @@ -54,7 +54,8 @@ export { export { ES_CLIENT_TOTAL_HITS_RELATION } from '../common/types/es_client'; export { ANOMALY_SEVERITY } from '../common'; -export { useMlHref, ML_PAGES, MlLocator, MlLocatorDefinition } from './locator'; +export type { MlLocator } from './locator'; +export { useMlHref, ML_PAGES, MlLocatorDefinition } from './locator'; // Bundled shared exports // Exported this way so the code doesn't end up in ML's page load bundle diff --git a/x-pack/plugins/ml/public/locator/ml_locator.ts b/x-pack/plugins/ml/public/locator/ml_locator.ts index f1bcd84e77d7d3..7fa573c3e653d0 100644 --- a/x-pack/plugins/ml/public/locator/ml_locator.ts +++ b/x-pack/plugins/ml/public/locator/ml_locator.ts @@ -28,7 +28,7 @@ import { } from './formatters'; import { formatTrainedModelsManagementUrl } from './formatters/trained_models'; -export { MlLocatorParams, MlLocator }; +export type { MlLocatorParams, MlLocator }; export class MlLocatorDefinition implements LocatorDefinition { public readonly id = ML_APP_LOCATOR; diff --git a/x-pack/plugins/ml/readme.md b/x-pack/plugins/ml/readme.md index 8425054d3f6482..79ba88e67be8b2 100644 --- a/x-pack/plugins/ml/readme.md +++ b/x-pack/plugins/ml/readme.md @@ -146,11 +146,11 @@ and Kibana instance that the tests will be run against. You can find the ML shared functions in the following files in GitHub: ``` -https://github.com/elastic/kibana/blob/master/x-pack/plugins/ml/public/shared.ts +https://github.com/elastic/kibana/blob/main/x-pack/plugins/ml/public/shared.ts ``` ``` -https://github.com/elastic/kibana/blob/master/x-pack/plugins/ml/server/shared.ts +https://github.com/elastic/kibana/blob/main/x-pack/plugins/ml/server/shared.ts ``` These functions are shared from the root of the ML plugin, you can import them with an import statement. For example: diff --git a/x-pack/plugins/ml/server/lib/capabilities/index.ts b/x-pack/plugins/ml/server/lib/capabilities/index.ts index 623ebe56085ff5..7c27aee6bf4af5 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/index.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/index.ts @@ -5,9 +5,6 @@ * 2.0. */ -export { - capabilitiesProvider, - hasMlCapabilitiesProvider, - HasMlCapabilities, -} from './check_capabilities'; +export type { HasMlCapabilities } from './check_capabilities'; +export { capabilitiesProvider, hasMlCapabilitiesProvider } from './check_capabilities'; export { setupCapabilitiesSwitcher } from './capabilities_switcher'; diff --git a/x-pack/plugins/ml/server/lib/log.ts b/x-pack/plugins/ml/server/lib/log.ts index afa5e977d9056d..3efd651e91d5b0 100644 --- a/x-pack/plugins/ml/server/lib/log.ts +++ b/x-pack/plugins/ml/server/lib/log.ts @@ -25,7 +25,7 @@ export let mlLog: MlLog; export function initMlServerLog(logInitialization: LogInitialization) { mlLog = { fatal: (message: string) => logInitialization.log.fatal(message), - error: (message: string) => logInitialization.log.error(message), + error: (message: string | Error) => logInitialization.log.error(message), warn: (message: string) => logInitialization.log.warn(message), info: (message: string) => logInitialization.log.info(message), debug: (message: string) => logInitialization.log.debug(message), diff --git a/x-pack/plugins/ml/server/lib/ml_client/index.ts b/x-pack/plugins/ml/server/lib/ml_client/index.ts index 509b83ef1edf71..b5329ba2cfbca5 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/index.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/index.ts @@ -7,4 +7,4 @@ export { getMlClient } from './ml_client'; export { MLJobNotFound } from './errors'; -export { MlClient } from './types'; +export type { MlClient } from './types'; diff --git a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts index 91c4327f953ff0..8833f2eb617a64 100644 --- a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts +++ b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts @@ -15,10 +15,16 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup) defaultMessage: 'ML jobs', }); const { addAppLinksToSampleDataset } = plugins.home.sampleData; + const getCreateJobPath = (jobId: string, dataViewId: string) => + `/app/ml/modules/check_view_or_create?id=${jobId}&index=${dataViewId}`; addAppLinksToSampleDataset('ecommerce', [ { - path: '/app/ml/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f', + sampleObject: { + type: 'index-pattern', + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + }, + getPath: (objectId) => getCreateJobPath('sample_data_ecommerce', objectId), label: sampleDataLinkLabel, icon: 'machineLearningApp', }, @@ -26,7 +32,11 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup) addAppLinksToSampleDataset('logs', [ { - path: '/app/ml/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247', + sampleObject: { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + }, + getPath: (objectId) => getCreateJobPath('sample_data_weblogs', objectId), label: sampleDataLinkLabel, icon: 'machineLearningApp', }, diff --git a/x-pack/plugins/ml/server/models/calendar/index.ts b/x-pack/plugins/ml/server/models/calendar/index.ts index c5177dd675ca1c..ca05246dff0100 100644 --- a/x-pack/plugins/ml/server/models/calendar/index.ts +++ b/x-pack/plugins/ml/server/models/calendar/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { CalendarManager, Calendar, FormCalendar } from './calendar_manager'; +export type { Calendar, FormCalendar } from './calendar_manager'; +export { CalendarManager } from './calendar_manager'; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts index 568be4197baf8f..b0135b0295fd80 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts @@ -7,17 +7,17 @@ import { DataViewsService } from '../../../../../../src/plugins/data_views/common'; -export class IndexPatternHandler { +export class DataViewHandler { constructor(private dataViewService: DataViewsService) {} // returns a id based on an index pattern name - async getIndexPatternId(indexName: string) { + async getDataViewId(indexName: string) { const dv = (await this.dataViewService.find(indexName)).find( ({ title }) => title === indexName ); return dv?.id; } - async deleteIndexPatternById(indexId: string) { - return await this.dataViewService.delete(indexId); + async deleteDataViewById(dataViewId: string) { + return await this.dataViewService.delete(dataViewId); } } diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts index b404f517e4b6fd..2f40081f1458d6 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts @@ -49,22 +49,31 @@ export function modelsProvider( modelIds.map((id: string) => [id, null]) ); - const { body } = await client.asCurrentUser.ingest.getPipeline(); - - for (const [pipelineName, pipelineDefinition] of Object.entries(body)) { - const { processors } = pipelineDefinition as { processors: Array> }; - - for (const processor of processors) { - const id = processor.inference?.model_id; - if (modelIdsMap.has(id)) { - const obj = modelIdsMap.get(id); - if (obj === null) { - modelIdsMap.set(id, { [pipelineName]: pipelineDefinition }); - } else { - obj![pipelineName] = pipelineDefinition; + try { + const { body } = await client.asCurrentUser.ingest.getPipeline(); + + for (const [pipelineName, pipelineDefinition] of Object.entries(body)) { + const { processors } = pipelineDefinition as { processors: Array> }; + + for (const processor of processors) { + const id = processor.inference?.model_id; + if (modelIdsMap.has(id)) { + const obj = modelIdsMap.get(id); + if (obj === null) { + modelIdsMap.set(id, { [pipelineName]: pipelineDefinition }); + } else { + obj![pipelineName] = pipelineDefinition; + } } } } + } catch (error) { + if (error.statusCode === 404) { + // ES returns 404 when there are no pipelines + // Instead, we should return the modelIdsMap and a 200 + return modelIdsMap; + } + throw error; } return modelIdsMap; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/types.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/types.ts index c9cca5756ada66..216cfe50cc5397 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/types.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/types.ts @@ -11,7 +11,7 @@ import { AnalyticsMapNodeElement, AnalyticsMapEdgeElement, } from '../../../common/types/data_frame_analytics'; -export { +export type { MapElements, AnalyticsMapReturnType, AnalyticsMapNodeElement, diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 1cd9aae79777b8..9e02a93a3c0f1f 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -293,6 +293,8 @@ export class DataRecognizer { index, size, body: searchBody, + // Ignored indices that are frozen + ignore_throttled: true, }); // @ts-expect-error incorrect search response type diff --git a/x-pack/plugins/ml/server/models/data_recognizer/index.ts b/x-pack/plugins/ml/server/models/data_recognizer/index.ts index fbddf17a50ede0..55595c3a12bde1 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/index.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { DataRecognizer, RecognizeResult, dataRecognizerFactory } from './data_recognizer'; +export type { RecognizeResult } from './data_recognizer'; +export { DataRecognizer, dataRecognizerFactory } from './data_recognizer'; diff --git a/x-pack/plugins/ml/server/models/filter/index.ts b/x-pack/plugins/ml/server/models/filter/index.ts index d5663478b48123..678fe73007ea27 100644 --- a/x-pack/plugins/ml/server/models/filter/index.ts +++ b/x-pack/plugins/ml/server/models/filter/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { FilterManager, FormFilter, UpdateFilter } from './filter_manager'; +export type { FormFilter, UpdateFilter } from './filter_manager'; +export { FilterManager } from './filter_manager'; diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index ac90659a6f3c91..7c435a247d7fa1 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -29,7 +29,7 @@ import type { GetAnalyticsMapArgs, ExtendAnalyticsMapArgs, } from '../models/data_frame_analytics/types'; -import { IndexPatternHandler } from '../models/data_frame_analytics/index_patterns'; +import { DataViewHandler } from '../models/data_frame_analytics/index_patterns'; import { AnalyticsManager } from '../models/data_frame_analytics/analytics_manager'; import { validateAnalyticsJob } from '../models/data_frame_analytics/validation'; import { fieldServiceProvider } from '../models/job_service/new_job_caps/field_service'; @@ -38,14 +38,14 @@ import { getAuthorizationHeader } from '../lib/request_authorization'; import type { MlClient } from '../lib/ml_client'; import type { DataViewsService } from '../../../../../src/plugins/data_views/common'; -function getIndexPatternId(dataViewsService: DataViewsService, patternName: string) { - const iph = new IndexPatternHandler(dataViewsService); - return iph.getIndexPatternId(patternName); +function getDataViewId(dataViewsService: DataViewsService, patternName: string) { + const iph = new DataViewHandler(dataViewsService); + return iph.getDataViewId(patternName); } -function deleteDestIndexPatternById(dataViewsService: DataViewsService, indexPatternId: string) { - const iph = new IndexPatternHandler(dataViewsService); - return iph.deleteIndexPatternById(indexPatternId); +function deleteDestDataViewById(dataViewsService: DataViewsService, dataViewId: string) { + const iph = new DataViewHandler(dataViewsService); + return iph.deleteDataViewById(dataViewId); } function getAnalyticsMap( @@ -427,9 +427,9 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout if (destinationIndex && deleteDestIndexPattern) { try { const dataViewsService = await getDataViewsService(); - const indexPatternId = await getIndexPatternId(dataViewsService, destinationIndex); - if (indexPatternId) { - await deleteDestIndexPatternById(dataViewsService, indexPatternId); + const dataViewId = await getDataViewId(dataViewsService, destinationIndex); + if (dataViewId) { + await deleteDestDataViewById(dataViewsService, dataViewId); } destIndexPatternDeleted.success = true; } catch (deleteDestIndexPatternError) { diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index a20a97a3fcb42c..1837f9e88edf36 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -15,6 +15,7 @@ import { import { modelsProvider } from '../models/data_frame_analytics'; import { TrainedModelConfigResponse } from '../../common/types/trained_models'; import { memoryOverviewServiceProvider } from '../models/memory_overview'; +import { mlLog } from '../lib/log'; export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) { /** @@ -77,8 +78,7 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) } } catch (e) { // the user might not have required permissions to fetch pipelines - // eslint-disable-next-line no-console - console.log(e); + mlLog.error(e); } return response.ok({ diff --git a/x-pack/plugins/ml/server/saved_objects/index.ts b/x-pack/plugins/ml/server/saved_objects/index.ts index cbd31c92611302..652f47122ae2f9 100644 --- a/x-pack/plugins/ml/server/saved_objects/index.ts +++ b/x-pack/plugins/ml/server/saved_objects/index.ts @@ -6,7 +6,8 @@ */ export { setupSavedObjects } from './saved_objects'; -export { JobObject, JobSavedObjectService, jobSavedObjectServiceFactory } from './service'; +export type { JobObject, JobSavedObjectService } from './service'; +export { jobSavedObjectServiceFactory } from './service'; export { checksFactory } from './checks'; export { syncSavedObjectsFactory } from './sync'; export { jobSavedObjectsInitializationFactory } from './initialization'; diff --git a/x-pack/plugins/ml/server/shared_services/index.ts b/x-pack/plugins/ml/server/shared_services/index.ts index 4126850918f6dc..3a38b0b76bac6c 100644 --- a/x-pack/plugins/ml/server/shared_services/index.ts +++ b/x-pack/plugins/ml/server/shared_services/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { SharedServices, createSharedServices } from './shared_services'; +export type { SharedServices } from './shared_services'; +export { createSharedServices } from './shared_services'; diff --git a/x-pack/plugins/ml/server/shared_services/license_checks/index.ts b/x-pack/plugins/ml/server/shared_services/license_checks/index.ts index 5939ff18a93767..9810084488cae2 100644 --- a/x-pack/plugins/ml/server/shared_services/license_checks/index.ts +++ b/x-pack/plugins/ml/server/shared_services/license_checks/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { LicenseCheck, licenseChecks } from './license_checks'; +export type { LicenseCheck } from './license_checks'; +export { licenseChecks } from './license_checks'; export { InsufficientBasicLicenseError, InsufficientFullLicenseError } from './errors'; diff --git a/x-pack/plugins/monitoring/public/alerts/status.tsx b/x-pack/plugins/monitoring/public/alerts/status.tsx index a29d85d43c5788..28c2ebcce2513a 100644 --- a/x-pack/plugins/monitoring/public/alerts/status.tsx +++ b/x-pack/plugins/monitoring/public/alerts/status.tsx @@ -64,10 +64,12 @@ export const AlertsStatus: React.FC = (props: Props) => { {showOnlyCount ? ( count ) : ( - + + + )} diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts b/x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts index 123dd39f7b54dd..db692889e1140f 100644 --- a/x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts +++ b/x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts @@ -5,8 +5,8 @@ * 2.0. */ import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { showAlertsToast } from '../../alerts/lib/alerts_toast'; import { useRequestErrorHandler } from './use_request_error_handler'; +import { EnableAlertResponse, showAlertsToast } from '../../alerts/lib/alerts_toast'; export const useAlertsModal = () => { const { services } = useKibana(); @@ -29,7 +29,14 @@ export const useAlertsModal = () => { async function enableAlerts() { try { - const response = await services.http?.post('../api/monitoring/v1/alerts/enable', {}); + if (!services.http?.post) { + throw new Error('HTTP service is unavailable'); + } + + const response = await services.http.post( + '../api/monitoring/v1/alerts/enable', + {} + )!; window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', 'true'); showAlertsToast(response); } catch (err) { diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_request_error_handler.tsx b/x-pack/plugins/monitoring/public/application/hooks/use_request_error_handler.tsx index 6c7c86a3301352..b4c2a4e86d3745 100644 --- a/x-pack/plugins/monitoring/public/application/hooks/use_request_error_handler.tsx +++ b/x-pack/plugins/monitoring/public/application/hooks/use_request_error_handler.tsx @@ -7,14 +7,14 @@ import React, { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { includes } from 'lodash'; -import { IHttpFetchError } from 'kibana/public'; +import { IHttpFetchError, ResponseErrorBody } from 'kibana/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; import { formatMsg } from '../../../../../../src/plugins/kibana_legacy/public'; import { toMountPoint, useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { MonitoringStartPluginDependencies } from '../../types'; -export function formatMonitoringError(err: IHttpFetchError) { +export function formatMonitoringError(err: IHttpFetchError) { if (err.response?.status && err.response?.status !== -1) { return ( @@ -37,7 +37,7 @@ export const useRequestErrorHandler = () => { const { services } = useKibana(); const history = useHistory(); return useCallback( - (err: IHttpFetchError) => { + (err: IHttpFetchError) => { if (err.response?.status === 403) { // redirect to error message view history.push('/access-denied'); diff --git a/x-pack/plugins/monitoring/public/application/pages/apm/instance.tsx b/x-pack/plugins/monitoring/public/application/pages/apm/instance.tsx index 3fa7819c5e4178..9f8260b4fa0d90 100644 --- a/x-pack/plugins/monitoring/public/application/pages/apm/instance.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/apm/instance.tsx @@ -59,7 +59,7 @@ export const ApmInstancePage: React.FC = ({ clusters }) => { const getPageData = useCallback(async () => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/apm/${instance}`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ apmSummary: { name: string } }>(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -71,7 +71,7 @@ export const ApmInstancePage: React.FC = ({ clusters }) => { }); setData(response); - setInstanceName(response.apmSummary.name); + setInstanceName(response?.apmSummary.name || ''); }, [ccs, clusterUuid, instance, services.data?.query.timefilter.timefilter, services.http]); return ( diff --git a/x-pack/plugins/monitoring/public/application/pages/apm/instances.tsx b/x-pack/plugins/monitoring/public/application/pages/apm/instances.tsx index 2543b054ee5bbf..fa3420d439ecad 100644 --- a/x-pack/plugins/monitoring/public/application/pages/apm/instances.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/apm/instances.tsx @@ -60,7 +60,7 @@ export const ApmInstancesPage: React.FC = ({ clusters }) => { const getPageData = useCallback(async () => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/apm/instances`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ stats: { total: number } }>(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -72,7 +72,7 @@ export const ApmInstancesPage: React.FC = ({ clusters }) => { }); setData(response); - updateTotalItemCount(response.stats.total); + updateTotalItemCount(response?.stats.total); }, [ ccs, clusterUuid, diff --git a/x-pack/plugins/monitoring/public/application/pages/apm/overview.tsx b/x-pack/plugins/monitoring/public/application/pages/apm/overview.tsx index 516c293c535461..39144505d9818b 100644 --- a/x-pack/plugins/monitoring/public/application/pages/apm/overview.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/apm/overview.tsx @@ -50,7 +50,7 @@ export const ApmOverviewPage: React.FC = ({ clusters }) => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/apm`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ ccs, diff --git a/x-pack/plugins/monitoring/public/application/pages/beats/instance.tsx b/x-pack/plugins/monitoring/public/application/pages/beats/instance.tsx index 4c66bbba631fbb..70dba8d5f0d3c1 100644 --- a/x-pack/plugins/monitoring/public/application/pages/beats/instance.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/beats/instance.tsx @@ -59,7 +59,7 @@ export const BeatsInstancePage: React.FC = ({ clusters }) => { const getPageData = useCallback(async () => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/beats/beat/${instance}`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ summary: { name: string } }>(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -71,7 +71,7 @@ export const BeatsInstancePage: React.FC = ({ clusters }) => { }); setData(response); - setBeatName(response.summary.name); + setBeatName(response?.summary.name || ''); }, [ccs, clusterUuid, instance, services.data?.query.timefilter.timefilter, services.http]); return ( diff --git a/x-pack/plugins/monitoring/public/application/pages/beats/instances.tsx b/x-pack/plugins/monitoring/public/application/pages/beats/instances.tsx index b33789f510f2ed..a677d22cbd3a70 100644 --- a/x-pack/plugins/monitoring/public/application/pages/beats/instances.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/beats/instances.tsx @@ -49,7 +49,7 @@ export const BeatsInstancesPage: React.FC = ({ clusters }) => { const getPageData = useCallback(async () => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/beats/beats`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ stats: { total: number } }>(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -61,7 +61,7 @@ export const BeatsInstancesPage: React.FC = ({ clusters }) => { }); setData(response); - updateTotalItemCount(response.stats.total); + updateTotalItemCount(response?.stats.total); }, [ ccs, clusterUuid, diff --git a/x-pack/plugins/monitoring/public/application/pages/beats/overview.tsx b/x-pack/plugins/monitoring/public/application/pages/beats/overview.tsx index aec89c92055c45..fad66cd03e444c 100644 --- a/x-pack/plugins/monitoring/public/application/pages/beats/overview.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/beats/overview.tsx @@ -50,7 +50,7 @@ export const BeatsOverviewPage: React.FC = ({ clusters }) => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/beats`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ ccs, diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_page.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_page.tsx index db8c40ba22943b..5a3a48bdfe17f4 100644 --- a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_page.tsx @@ -69,7 +69,7 @@ export const ElasticsearchIndexPage: React.FC = ({ clusters }) = const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/indices/${index}`; if (services.http?.fetch && clusterUuid) { - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ shards: unknown[]; nodes: unknown[] }>(url, { method: 'POST', body: JSON.stringify({ timeRange: { diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/ml_jobs_page.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/ml_jobs_page.tsx index 46bb4cc20242fe..0f002323c310f0 100644 --- a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/ml_jobs_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/ml_jobs_page.tsx @@ -56,7 +56,7 @@ export const ElasticsearchMLJobsPage: React.FC = ({ clusters }) const getPageData = useCallback(async () => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/ml_jobs`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ rows: MLJobs; clusterStatus: unknown }>(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -67,8 +67,8 @@ export const ElasticsearchMLJobsPage: React.FC = ({ clusters }) }), }); setData({ - clusterStatus: response.clusterStatus, - jobs: (response.rows as MLJobs).map((job) => { + clusterStatus: response?.clusterStatus, + jobs: response?.rows?.map((job) => { if ('ml' in job && job.ml?.job) { return { ...job.ml.job, diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/node_page.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/node_page.tsx index f6402dd8cba630..e09a1a2343c7b1 100644 --- a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/node_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/node_page.tsx @@ -82,7 +82,7 @@ export const ElasticsearchNodePage: React.FC = ({ clusters }) => const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/nodes/${node}`; if (services.http?.fetch && clusterUuid) { - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ shards: unknown[]; nodes: unknown[] }>(url, { method: 'POST', body: JSON.stringify({ showSystemIndices, diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx index 9933188b887d54..5d5fa4df458e18 100644 --- a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx @@ -66,7 +66,7 @@ export const ElasticsearchNodesPage: React.FC = ({ clusters }) = const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/nodes`; if (services.http?.fetch && clusterUuid) { setIsLoading(true); - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ totalNodeCount: number }>(url, { method: 'POST', body: JSON.stringify({ ccs, diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/overview.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/overview.tsx index d093ba6e6f0daa..16fa6de24b6b70 100644 --- a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/overview.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/overview.tsx @@ -57,7 +57,7 @@ export const ElasticsearchOverviewPage: React.FC = ({ clusters } const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ ccs, diff --git a/x-pack/plugins/monitoring/public/application/pages/kibana/instance.tsx b/x-pack/plugins/monitoring/public/application/pages/kibana/instance.tsx index 2d2fe99758ff78..262590b6806afc 100644 --- a/x-pack/plugins/monitoring/public/application/pages/kibana/instance.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/kibana/instance.tsx @@ -138,7 +138,7 @@ export const KibanaInstancePage: React.FC = ({ clusters }) => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/kibana/${instance}`; if (services.http?.fetch && clusterUuid) { - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ kibanaSummary: { name: string } }>(url, { method: 'POST', body: JSON.stringify({ ccs, diff --git a/x-pack/plugins/monitoring/public/application/pages/kibana/instances.tsx b/x-pack/plugins/monitoring/public/application/pages/kibana/instances.tsx index a27c1418eabc1d..f20099b286808e 100644 --- a/x-pack/plugins/monitoring/public/application/pages/kibana/instances.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/kibana/instances.tsx @@ -54,7 +54,7 @@ export const KibanaInstancesPage: React.FC = ({ clusters }) => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/kibana/instances`; if (services.http?.fetch && clusterUuid) { - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch<{ kibanas: { length: number } }>(url, { method: 'POST', body: JSON.stringify({ ccs, diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/logstash_template.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/logstash_template.tsx index 12b664129211f0..96ac605b1ae54f 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/logstash_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/logstash_template.tsx @@ -63,6 +63,7 @@ export const LogstashTemplate: React.FC = ({ defaultMessage: 'Pipelines', }), route: `/logstash/node/${instance.nodeSummary?.uuid}/pipelines`, + testSubj: 'logstashNodeDetailPipelinesLink', }); tabs.push({ id: 'advanced', @@ -70,6 +71,7 @@ export const LogstashTemplate: React.FC = ({ defaultMessage: 'Advanced', }), route: `/logstash/node/${instance.nodeSummary?.uuid}/advanced`, + testSubj: 'logstashNodeDetailAdvancedLink', }); } } diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx index 0d89adeaeadd7c..35f4c0ce2ac44c 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx @@ -62,7 +62,7 @@ export const LogStashNodePipelinesPage: React.FC = ({ clusters } const getPageData = useCallback(async () => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/logstash/node/${match.params.uuid}/pipelines`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -105,16 +105,18 @@ export const LogStashNodePipelinesPage: React.FC = ({ clusters } cluster={cluster} > {data.pipelines && ( - +
+ +
)} ); diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/nodes.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/nodes.tsx index 4822841cd241c1..a6031062420820 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/nodes.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/nodes.tsx @@ -92,7 +92,7 @@ export const LogStashNodesPage: React.FC = ({ clusters }) => { getPageData={getPageData} cluster={cluster} > -
+
( diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/overview.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/overview.tsx index d1e7faed56cbf8..60c9463d39bda9 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/overview.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/overview.tsx @@ -41,7 +41,7 @@ export const LogStashOverviewPage: React.FC = ({ clusters }) => const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/logstash`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -85,10 +85,9 @@ export const LogStashOverviewPage: React.FC = ({ clusters }) => title={title} pageTitle={pageTitle} getPageData={getPageData} - data-test-subj="elasticsearchOverviewPage" cluster={cluster} > -
{renderOverview(data)}
+
{renderOverview(data)}
); }; diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/pipeline.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/pipeline.tsx index cf9b5628222f4a..7d057863ba3fcf 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/pipeline.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/pipeline.tsx @@ -65,7 +65,7 @@ export const LogStashPipelinePage: React.FC = ({ clusters }) => ? `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipeline/${pipelineId}/${pipelineHash}` : `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipeline/${pipelineId}`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ ccs, diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx index e3a5deebd6cad5..ace8c23a480ba0 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx @@ -49,7 +49,7 @@ export const LogStashPipelinesPage: React.FC = ({ clusters }) => const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipelines`; - const response = await services.http?.fetch(url, { + const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -105,7 +105,6 @@ export const LogStashPipelinesPage: React.FC = ({ clusters }) => title={title} pageTitle={pageTitle} getPageData={getPageData} - data-test-subj="logstashPipelinesListing" cluster={cluster} >
{renderOverview(data)}
diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index a508714612c28f..c951d325c13f4a 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -8,7 +8,7 @@ import { EuiTab, EuiTabs } from '@elastic/eui'; import React, { useContext, useState, useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { IHttpFetchError } from 'kibana/public'; +import { IHttpFetchError, ResponseErrorBody } from 'kibana/public'; import { useTitle } from '../hooks/use_title'; import { MonitoringToolbar } from '../../components/shared/toolbar'; import { MonitoringTimeContainer } from '../hooks/use_monitoring_time'; @@ -66,7 +66,7 @@ export const PageTemplate: React.FC = ({ setIsRequestPending(true); getPageData?.() .then(getPageDataResponseHandler) - .catch((err: IHttpFetchError) => { + .catch((err: IHttpFetchError) => { handleRequestError(err); setHasError(true); }) diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js index 1afe75cda4027d..217bd1d24ff0e1 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js @@ -98,6 +98,7 @@ export function LogstashPanel(props) { setupModeEnabled={setupMode.enabled} setupModeData={setupModeData} href={goToLogstash()} + data-test-subj="lsOverview" aria-label={i18n.translate( 'xpack.monitoring.cluster.overview.logstashPanel.overviewLinkAriaLabel', { diff --git a/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js b/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js index 04eb6c5957c2d1..ddf6a7d7382a26 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js @@ -66,12 +66,15 @@ export class Listing extends PureComponent { return (
-
- +
+ {name}
-
{node.logstash.http_address}
+
{node.logstash.http_address}
{setupModeStatus}
); @@ -92,7 +95,9 @@ export class Listing extends PureComponent { }), field: 'cpu_usage', sortable: true, - render: (value) => formatPercentageUsage(value, 100), + render: (value) => ( + {formatPercentageUsage(value, 100)} + ), }, { name: i18n.translate('xpack.monitoring.logstash.nodes.loadAverageTitle', { @@ -100,7 +105,7 @@ export class Listing extends PureComponent { }), field: 'load_average', sortable: true, - render: (value) => formatNumber(value, '0.00'), + render: (value) => {formatNumber(value, '0.00')}, }, { name: i18n.translate('xpack.monitoring.logstash.nodes.jvmHeapUsedTitle', { @@ -109,7 +114,9 @@ export class Listing extends PureComponent { }), field: 'jvm_heap_used', sortable: true, - render: (value) => formatPercentageUsage(value, 100), + render: (value) => ( + {formatPercentageUsage(value, 100)} + ), }, { name: i18n.translate('xpack.monitoring.logstash.nodes.eventsIngestedTitle', { @@ -117,7 +124,7 @@ export class Listing extends PureComponent { }), field: 'events_out', sortable: true, - render: (value) => formatNumber(value, '0.[0]a'), + render: (value) => {formatNumber(value, '0.[0]a')}, }, { name: i18n.translate('xpack.monitoring.logstash.nodes.configReloadsTitle', { @@ -126,14 +133,14 @@ export class Listing extends PureComponent { sortable: true, render: (node) => (
-
+
-
+
formatNumber(value), + render: (value) => {formatNumber(value)}, }, ]; } diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx index e582f4aa408128..2fae120012d735 100644 --- a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx +++ b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render } from 'react-dom'; import { get, includes } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { HttpStart, IHttpFetchError } from 'kibana/public'; +import { HttpStart, IHttpFetchError, ResponseErrorBody } from 'kibana/public'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { Legacy } from '../legacy_shims'; import { SetupModeEnterButton } from '../components/setup_mode/enter_button'; @@ -23,7 +23,7 @@ function isOnPage(hash: string) { let globalState: GlobalState; let httpService: HttpStart; -let errorHandler: (error: IHttpFetchError) => void; +let errorHandler: (error: IHttpFetchError) => void; interface ISetupModeState { enabled: boolean; @@ -162,7 +162,7 @@ export const setSetupModeMenuItem = () => { export const initSetupModeState = async ( state: GlobalState, http: HttpStart, - handleErrors: (error: IHttpFetchError) => void, + handleErrors: (error: IHttpFetchError) => void, callback?: () => void ) => { globalState = state; diff --git a/x-pack/plugins/monitoring/public/types.ts b/x-pack/plugins/monitoring/public/types.ts index 4817ac235e2bdf..fa92cfacafdcc3 100644 --- a/x-pack/plugins/monitoring/public/types.ts +++ b/x-pack/plugins/monitoring/public/types.ts @@ -12,9 +12,9 @@ import { TriggersAndActionsUIPublicPluginStart } from '../../triggers_actions_ui import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { MonitoringConfig } from '../server'; +export type { MonitoringConfig } from '../server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { MLJobs } from '../server/lib/elasticsearch/get_ml_jobs'; +export type { MLJobs } from '../server/lib/elasticsearch/get_ml_jobs'; export interface MonitoringStartPluginDependencies { navigation: NavigationStart; diff --git a/x-pack/plugins/monitoring/server/index.ts b/x-pack/plugins/monitoring/server/index.ts index 63cc61e5039170..44aaff7d51c4a4 100644 --- a/x-pack/plugins/monitoring/server/index.ts +++ b/x-pack/plugins/monitoring/server/index.ts @@ -11,9 +11,9 @@ import { MonitoringPlugin } from './plugin'; import { configSchema } from './config'; import { deprecations } from './deprecations'; -export { KibanaSettingsCollector } from './kibana_monitoring/collectors'; -export { MonitoringConfig } from './config'; -export { MonitoringPluginSetup, IBulkUploader } from './types'; +export type { KibanaSettingsCollector } from './kibana_monitoring/collectors'; +export type { MonitoringConfig } from './config'; +export type { MonitoringPluginSetup, IBulkUploader } from './types'; export const plugin = (initContext: PluginInitializerContext) => new MonitoringPlugin(initContext); export const config: PluginConfigDescriptor> = { diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts index 06d47f00447286..9fa7ea8fdaf6ae 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts @@ -11,7 +11,8 @@ import { getSettingsCollector } from './get_settings_collector'; import { getMonitoringUsageCollector } from './get_usage_collector'; import { MonitoringConfig } from '../../config'; -export { KibanaSettingsCollector, getKibanaSettings } from './get_settings_collector'; +export type { KibanaSettingsCollector } from './get_settings_collector'; +export { getKibanaSettings } from './get_settings_collector'; export function registerCollectors( usageCollection: UsageCollectionSetup, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/index.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/index.ts index 8f474c02848444..00f3370dfde516 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/index.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/index.ts @@ -7,6 +7,7 @@ export { getNodes } from './get_nodes'; export { getNodeSummary } from './get_node_summary'; -export { calculateNodeType, Node } from './calculate_node_type'; +export type { Node } from './calculate_node_type'; +export { calculateNodeType } from './calculate_node_type'; export { getNodeTypeClassLabel } from './get_node_type_class_label'; export { getDefaultNodeFromId } from './get_default_node_from_id'; diff --git a/x-pack/plugins/monitoring/server/lib/metrics/index.ts b/x-pack/plugins/monitoring/server/lib/metrics/index.ts index ba43a8c316d3b8..2fdaeb81ee6205 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/index.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/index.ts @@ -9,9 +9,11 @@ export { ElasticsearchMetric } from './elasticsearch/classes'; // @ts-ignore export { KibanaClusterMetric, KibanaMetric } from './kibana/classes'; -export { ApmMetric, ApmClusterMetric, ApmMetricFields } from './apm/classes'; +export type { ApmMetricFields } from './apm/classes'; +export { ApmMetric, ApmClusterMetric } from './apm/classes'; // @ts-ignore export { LogstashClusterMetric, LogstashMetric } from './logstash/classes'; -export { BeatsClusterMetric, BeatsMetric, BeatsMetricFields } from './beats/classes'; +export type { BeatsMetricFields } from './beats/classes'; +export { BeatsClusterMetric, BeatsMetric } from './beats/classes'; // @ts-ignore export { metrics } from './metrics'; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/helpers.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/helpers.ts index e8ba66c3327782..56e5610e1b117c 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/helpers.ts +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/helpers.ts @@ -28,7 +28,7 @@ export const useFetchAlertDetail = (alertId: string): [boolean, TopAlert | null] const fetchData = async () => { try { setLoading(true); - const response = await http.get('/internal/rac/alerts', { + const response = await http.get>('/internal/rac/alerts', { query: { id: alertId, }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx index 5529f289270281..32994b37fffe34 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx @@ -79,8 +79,10 @@ export function DateRangePicker({ seriesId, series }: { seriesId: number; series return ( } endDateControl={ } /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts index c12e67bc9b1ae5..aac5ac7136d7ab 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts @@ -21,6 +21,7 @@ import { BROWSER_VERSION_LABEL, CLS_LABEL, CORE_WEB_VITALS_LABEL, + DCL_LABEL, DEVICE_DISTRIBUTION_LABEL, DEVICE_LABEL, ENVIRONMENT_LABEL, @@ -50,8 +51,18 @@ import { PAGE_LOAD_TIME_LABEL, LABELS_FIELD, STEP_NAME_LABEL, + STEP_DURATION_LABEL, } from './labels'; -import { SYNTHETICS_STEP_NAME } from './field_names/synthetics'; +import { + MONITOR_DURATION_US, + SYNTHETICS_CLS, + SYNTHETICS_DCL, + SYNTHETICS_DOCUMENT_ONLOAD, + SYNTHETICS_FCP, + SYNTHETICS_LCP, + SYNTHETICS_STEP_DURATION, + SYNTHETICS_STEP_NAME, +} from './field_names/synthetics'; export const DEFAULT_TIME = { from: 'now-1h', to: 'now' }; @@ -73,12 +84,19 @@ export const FieldLabels: Record = { [TBT_FIELD]: TBT_LABEL, [FID_FIELD]: FID_LABEL, [CLS_FIELD]: CLS_LABEL, + + [SYNTHETICS_CLS]: CLS_LABEL, + [SYNTHETICS_DCL]: DCL_LABEL, + [SYNTHETICS_STEP_DURATION]: STEP_DURATION_LABEL, + [SYNTHETICS_LCP]: LCP_LABEL, + [SYNTHETICS_FCP]: FCP_LABEL, + [SYNTHETICS_DOCUMENT_ONLOAD]: PAGE_LOAD_TIME_LABEL, [TRANSACTION_TIME_TO_FIRST_BYTE]: BACKEND_TIME_LABEL, [TRANSACTION_DURATION]: PAGE_LOAD_TIME_LABEL, 'monitor.id': MONITOR_ID_LABEL, 'monitor.status': MONITOR_STATUS_LABEL, - 'monitor.duration.us': MONITORS_DURATION_LABEL, + [MONITOR_DURATION_US]: MONITORS_DURATION_LABEL, [SYNTHETICS_STEP_NAME]: STEP_NAME_LABEL, 'agent.hostname': AGENT_HOST_LABEL, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts index e548ec2714e146..63bd7e0cf3e810 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts @@ -16,13 +16,13 @@ import { import { CLS_LABEL, DCL_LABEL, - DOCUMENT_ONLOAD_LABEL, DOWN_LABEL, FCP_LABEL, LCP_LABEL, MONITORS_DURATION_LABEL, STEP_DURATION_LABEL, UP_LABEL, + PAGE_LOAD_TIME_LABEL, } from '../constants/labels'; import { MONITOR_DURATION_US, @@ -128,7 +128,7 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon columnFilters: getStepMetricColumnFilter(SYNTHETICS_DCL), }, { - label: DOCUMENT_ONLOAD_LABEL, + label: PAGE_LOAD_TIME_LABEL, field: SYNTHETICS_DOCUMENT_ONLOAD, id: SYNTHETICS_DOCUMENT_ONLOAD, columnType: OPERATION_COLUMN, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index dbf36777b536f1..ae3d57b3c9652a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -91,12 +91,12 @@ export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null const theme = useTheme(); return useMemo(() => { - if (isEmpty(indexPatterns) || isEmpty(allSeries) || !reportType) { - return null; - } - // we only use the data from url to apply, since that get's updated to apply changes + // we only use the data from url to apply, since that gets updated to apply changes const allSeriesT: AllSeries = convertAllShortSeries(storage.get(allSeriesKey) ?? []); + if (isEmpty(indexPatterns) || isEmpty(allSeriesT) || !reportType) { + return null; + } const layerConfigs = getLayerConfigs(allSeriesT, reportType, theme, indexPatterns); if (layerConfigs.length < 1) { @@ -106,5 +106,7 @@ export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null const lensAttributes = new LensAttributes(layerConfigs); return lensAttributes.getJSON(lastRefresh); - }, [indexPatterns, allSeries, reportType, storage, theme, lastRefresh]); + // we also want to check the state on allSeries changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [indexPatterns, reportType, storage, theme, lastRefresh, allSeries]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx index b3ec7ee184f00e..b9dced8036eae6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx @@ -65,7 +65,7 @@ export function LensEmbeddable(props: Props) { [reportType, setSeries, firstSeries, notifications?.toasts] ); - if (!timeRange || !firstSeries) { + if (!timeRange || !lensAttributes) { return null; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.test.tsx index ced4d3af057ff6..f33eb3d515c155 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.test.tsx @@ -54,13 +54,6 @@ describe('OperationTypeSelect', function () { fireEvent.click(screen.getByTestId('operationTypeSelect')); - expect(setSeries).toHaveBeenCalledWith(0, { - operationType: 'median', - dataType: 'ux', - time: { from: 'now-15m', to: 'now' }, - name: 'performance-distribution', - }); - fireEvent.click(screen.getByText('95th Percentile')); expect(setSeries).toHaveBeenCalledWith(0, { operationType: '95th', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.tsx index a223a74d74aea4..3d005e04e9928f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSuperSelect } from '@elastic/eui'; @@ -30,16 +30,9 @@ export function OperationTypeSelect({ setSeries(seriesId, { ...series, operationType: value }); }; - useEffect(() => { - setSeries(seriesId, { ...series, operationType: operationType || defaultOperationType }); - // We only want to call this when defaultOperationType changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultOperationType]); - return ( ); @@ -48,11 +41,9 @@ export function OperationTypeSelect({ export function OperationTypeComponent({ operationType, onChange, - showLabel = false, }: { operationType?: OperationType; onChange: (value: OperationType) => void; - showLabel?: boolean; }) { const options = [ { @@ -107,15 +98,7 @@ export function OperationTypeComponent({ return ( - - + + @@ -74,7 +74,7 @@ export function ExpandedSeriesRow(seriesProps: Props) { {(hasOperationType || (columnType === 'operation' && !hasPercentileBreakdown)) && ( - + { + const applyChanges = jest.fn(); + + const mockSeriesStorage = (allSeries: AllSeries, urlAllSeries: AllSeries) => { + jest.clearAllMocks(); + jest.spyOn(hooks, 'useSeriesStorage').mockReturnValue({ + ...jest.requireActual('../hooks/use_series_storage'), + allSeries, + applyChanges, + storage: { get: jest.fn().mockReturnValue(urlAllSeries) } as any, + }); + }; + + const assertApplyIsEnabled = async () => { + render(); + + const applyBtn = screen.getByText(/Apply changes/i); + + const btnComponent = screen.getByTestId('seriesChangesApplyButton'); + + expect(btnComponent.classList).not.toContain('euiButton-isDisabled'); + + fireEvent.click(applyBtn); + + await waitFor(() => { + expect(applyChanges).toBeCalledTimes(1); + }); + }; + + it('renders ViewActions', async () => { + mockSeriesStorage([], []); + render(); + + expect(screen.getByText(/Apply changes/i)).toBeInTheDocument(); + }); + + it('apply button is disabled when no changes', async () => { + mockSeriesStorage([], []); + + render(); + const applyBtn = screen.getByText(/Apply changes/i); + + const btnComponent = screen.getByTestId('seriesChangesApplyButton'); + + expect(btnComponent.classList).toContain('euiButton-isDisabled'); + + fireEvent.click(applyBtn); + + await waitFor(() => { + expect(applyChanges).toBeCalledTimes(0); + }); + }); + + it('should call apply changes when series length is different', async function () { + mockSeriesStorage([], [{ name: 'testSeries' } as any]); + + await assertApplyIsEnabled(); + }); + + it('should call apply changes when series content is different', async function () { + mockSeriesStorage([{ name: 'testSeriesChange' } as any], [{ name: 'testSeries' } as any]); + + await assertApplyIsEnabled(); + }); + + it('should call apply changes when series content is different as in undefined', async function () { + mockSeriesStorage( + [{ name: undefined } as any], + [{ name: 'testSeries', operationType: undefined } as any] + ); + + await assertApplyIsEnabled(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/views/view_actions.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/views/view_actions.tsx index ee2668aa0c39a3..e85ce8ff40c693 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/views/view_actions.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/views/view_actions.tsx @@ -8,22 +8,41 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEqual } from 'lodash'; +import { isEqual, pickBy } from 'lodash'; import { allSeriesKey, convertAllShortSeries, useSeriesStorage } from '../hooks/use_series_storage'; interface Props { onApply?: () => void; } +export function removeUndefinedProps(obj: T): Partial { + return pickBy(obj, (value) => value !== undefined); +} + export function ViewActions({ onApply }: Props) { const { allSeries, storage, applyChanges } = useSeriesStorage(); - const noChanges = isEqual(allSeries, convertAllShortSeries(storage.get(allSeriesKey) ?? [])); + const urlAllSeries = convertAllShortSeries(storage.get(allSeriesKey) ?? []); + + let noChanges = allSeries.length === urlAllSeries.length; + + if (noChanges) { + noChanges = !allSeries.some( + (series, index) => + !isEqual(removeUndefinedProps(series), removeUndefinedProps(urlAllSeries[index])) + ); + } return ( - applyChanges(onApply)} isDisabled={noChanges} fill size="s"> + applyChanges(onApply)} + isDisabled={noChanges} + fill + size="s" + data-test-subj={'seriesChangesApplyButton'} + > {i18n.translate('xpack.observability.expView.seriesBuilder.apply', { defaultMessage: 'Apply changes', })} diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index a586a8bf0bccec..1eb4108b121811 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -556,7 +556,7 @@ describe('HasDataContextProvider', () => { status: 'success', }, }, - hasAnyData: false, + hasAnyData: true, isAllRequestsComplete: true, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), diff --git a/x-pack/plugins/observability/public/context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context.tsx index caed130543accc..b6a45784a53b4b 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.tsx @@ -146,9 +146,12 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode return appStatus !== undefined && appStatus !== FETCH_STATUS.LOADING; }); - const hasAnyData = (Object.keys(hasDataMap) as ObservabilityFetchDataPlugins[]).some( - (app) => hasDataMap[app]?.hasData === true - ); + const hasAnyData = (Object.keys(hasDataMap) as ObservabilityFetchDataPlugins[]).some((app) => { + const appHasData = hasDataMap[app]?.hasData; + return ( + appHasData === true || (Array.isArray(appHasData) && (appHasData as Alert[])?.length > 0) + ); + }); return ( = [ { columnHeaderType: 'not-filtered', - displayAsText: i18n.translate('xpack.observability.alertsTGrid.statusColumnDescription', { - defaultMessage: 'Alert Status', - }), + displayAsText: translations.statusColumnDescription, id: ALERT_STATUS, initialWidth: 110, }, { columnHeaderType: 'not-filtered', - displayAsText: i18n.translate('xpack.observability.alertsTGrid.lastUpdatedColumnDescription', { - defaultMessage: 'Last updated', - }), + displayAsText: translations.lastUpdatedColumnDescription, id: TIMESTAMP, initialWidth: 230, }, { columnHeaderType: 'not-filtered', - displayAsText: i18n.translate('xpack.observability.alertsTGrid.durationColumnDescription', { - defaultMessage: 'Duration', - }), + displayAsText: translations.durationColumnDescription, id: ALERT_DURATION, initialWidth: 116, }, { columnHeaderType: 'not-filtered', - displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonColumnDescription', { - defaultMessage: 'Reason', - }), + displayAsText: translations.reasonColumnDescription, id: ALERT_REASON, linkField: '*', }, @@ -249,73 +241,61 @@ function ObservabilityActions({ ]; }, [afterCaseSelection, casePermissions, timelines, event, statusActionItems, alertPermissions]); - const viewDetailsTextLabel = i18n.translate( - 'xpack.observability.alertsTable.viewDetailsTextLabel', - { - defaultMessage: 'View details', - } - ); - const viewInAppTextLabel = i18n.translate('xpack.observability.alertsTable.viewInAppTextLabel', { - defaultMessage: 'View in app', - }); - const moreActionsTextLabel = i18n.translate( - 'xpack.observability.alertsTable.moreActionsTextLabel', - { - defaultMessage: 'More actions', - } - ); + const actionsToolTip = + actionsMenuItems.length <= 0 + ? translations.notEnoughPermissions + : translations.moreActionsTextLabel; return ( <> - + setFlyoutAlert(alert)} data-test-subj="openFlyoutButton" - aria-label={viewDetailsTextLabel} + aria-label={translations.viewDetailsTextLabel} /> - + - {actionsMenuItems.length > 0 && ( - - - toggleActionsPopover(eventId)} - data-test-subj="alerts-table-row-action-more" - /> - - } - isOpen={openActionsPopoverId === eventId} - closePopover={closeActionsPopover} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - - - )} + + + toggleActionsPopover(eventId)} + data-test-subj="alerts-table-row-action-more" + /> + + } + isOpen={openActionsPopoverId === eventId} + closePopover={closeActionsPopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + ); @@ -363,13 +343,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { id: 'expand', width: 120, headerCellRender: () => { - return ( - - {i18n.translate('xpack.observability.alertsTable.actionsTextLabel', { - defaultMessage: 'Actions', - })} - - ); + return {translations.actionsTextLabel}; }, rowCellRender: (actionProps: ActionProps) => { return ( @@ -400,18 +374,16 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { hasAlertsCrudPermissions, indexNames, itemsPerPageOptions: [10, 25, 50], - loadingText: i18n.translate('xpack.observability.alertsTable.loadingTextLabel', { - defaultMessage: 'loading alerts', - }), - footerText: i18n.translate('xpack.observability.alertsTable.footerTextLabel', { - defaultMessage: 'alerts', - }), + loadingText: translations.loadingTextLabel, + footerText: translations.footerTextLabel, query: { query: `${ALERT_WORKFLOW_STATUS}: ${workflowStatus}${kuery !== '' ? ` and ${kuery}` : ''}`, language: 'kuery', }, renderCellValue: getRenderCellValue({ setFlyoutAlert }), rowRenderers: NO_ROW_RENDER, + // TODO: implement Kibana data view runtime fields in observability + runtimeMappings: {}, start: rangeFrom, setRefetch, sort: [ @@ -424,11 +396,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { filterStatus: workflowStatus as AlertWorkflowStatus, leadingControlColumns, trailingControlColumns, - unit: (totalAlerts: number) => - i18n.translate('xpack.observability.alertsTable.showingAlertsTitle', { - values: { totalAlerts }, - defaultMessage: '{totalAlerts, plural, =1 {alert} other {alerts}}', - }), + unit: (totalAlerts: number) => translations.showingAlertsTitle(totalAlerts), }; }, [ casePermissions, @@ -443,6 +411,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { leadingControlColumns, deletedEventIds, ]); + const handleFlyoutClose = () => setFlyoutAlert(undefined); const { observabilityRuleTypeRegistry } = usePluginContext(); diff --git a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx index 5ad4804f88d5e0..3adfb0a1d9c891 100644 --- a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx @@ -7,58 +7,16 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ObservabilityPublicPluginsStart } from '../..'; import { getMappedNonEcsValue } from './render_cell_value'; import FilterForValueButton from './filter_for_value'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { TimelineNonEcsData } from '../../../../timelines/common/search_strategy'; import { TGridCellAction } from '../../../../timelines/common/types/timeline'; -import { getPageRowIndex, TimelinesUIStart } from '../../../../timelines/public'; +import { getPageRowIndex } from '../../../../timelines/public'; export const FILTER_FOR_VALUE = i18n.translate('xpack.observability.hoverActions.filterForValue', { defaultMessage: 'Filter for value', }); -/** a hook to eliminate the verbose boilerplate required to use common services */ -const useKibanaServices = () => { - const { timelines } = useKibana<{ timelines: TimelinesUIStart }>().services; - const { - services: { - data: { - query: { filterManager }, - }, - }, - } = useKibana(); - - return { timelines, filterManager }; -}; - -/** actions common to all cells (e.g. copy to clipboard) */ -const commonCellActions: TGridCellAction[] = [ - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => - ({ rowIndex, columnId, Component }) => { - const { timelines } = useKibanaServices(); - - const value = getMappedNonEcsValue({ - data: data[getPageRowIndex(rowIndex, pageSize)], - fieldName: columnId, - }); - - return ( - <> - {timelines.getHoverActions().getCopyButton({ - Component, - field: columnId, - isHoverAction: false, - ownFocus: false, - showTooltip: false, - value, - })} - - ); - }, -]; - /** actions for adding filters to the search bar */ const buildFilterCellActions = (addToQuery: (value: string) => void): TGridCellAction[] => [ ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => @@ -80,7 +38,5 @@ const buildFilterCellActions = (addToQuery: (value: string) => void): TGridCellA ]; /** returns the default actions shown in `EuiDataGrid` cells */ -export const getDefaultCellActions = ({ addToQuery }: { addToQuery: (value: string) => void }) => [ - ...buildFilterCellActions(addToQuery), - ...commonCellActions, -]; +export const getDefaultCellActions = ({ addToQuery }: { addToQuery: (value: string) => void }) => + buildFilterCellActions(addToQuery); diff --git a/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx b/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx index 77cac9d482a375..f75ae488c9b289 100644 --- a/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; export const filterForValueButtonLabel = i18n.translate( 'xpack.observability.hoverActions.filterForValueButtonLabel', { - defaultMessage: 'Filter for value', + defaultMessage: 'Filter in', } ); diff --git a/x-pack/plugins/observability/public/pages/alerts/translations.ts b/x-pack/plugins/observability/public/pages/alerts/translations.ts new file mode 100644 index 00000000000000..4578987e839a05 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/translations.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const translations = { + viewDetailsTextLabel: i18n.translate('xpack.observability.alertsTable.viewDetailsTextLabel', { + defaultMessage: 'View details', + }), + viewInAppTextLabel: i18n.translate('xpack.observability.alertsTable.viewInAppTextLabel', { + defaultMessage: 'View in app', + }), + moreActionsTextLabel: i18n.translate('xpack.observability.alertsTable.moreActionsTextLabel', { + defaultMessage: 'More actions', + }), + notEnoughPermissions: i18n.translate('xpack.observability.alertsTable.notEnoughPermissions', { + defaultMessage: 'Additional privileges required', + }), + statusColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.statusColumnDescription', + { + defaultMessage: 'Alert Status', + } + ), + lastUpdatedColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.lastUpdatedColumnDescription', + { + defaultMessage: 'Last updated', + } + ), + durationColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.durationColumnDescription', + { + defaultMessage: 'Duration', + } + ), + reasonColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.reasonColumnDescription', + { + defaultMessage: 'Reason', + } + ), + actionsTextLabel: i18n.translate('xpack.observability.alertsTable.actionsTextLabel', { + defaultMessage: 'Actions', + }), + loadingTextLabel: i18n.translate('xpack.observability.alertsTable.loadingTextLabel', { + defaultMessage: 'loading alerts', + }), + footerTextLabel: i18n.translate('xpack.observability.alertsTable.footerTextLabel', { + defaultMessage: 'alerts', + }), + showingAlertsTitle: (totalAlerts: number) => + i18n.translate('xpack.observability.alertsTable.showingAlertsTitle', { + values: { totalAlerts }, + defaultMessage: '{totalAlerts, plural, =1 {alert} other {alerts}}', + }), +}; diff --git a/x-pack/plugins/observability/public/services/call_observability_api/types.ts b/x-pack/plugins/observability/public/services/call_observability_api/types.ts index 8722aecd908007..f517772821f426 100644 --- a/x-pack/plugins/observability/public/services/call_observability_api/types.ts +++ b/x-pack/plugins/observability/public/services/call_observability_api/types.ts @@ -31,4 +31,4 @@ export type ObservabilityClient = RouteRepositoryClient< ObservabilityClientOptions >; -export { ObservabilityAPIReturnType }; +export type { ObservabilityAPIReturnType }; diff --git a/x-pack/plugins/observability/public/utils/no_data_config.ts b/x-pack/plugins/observability/public/utils/no_data_config.ts index 2c87b1434a0b4e..c8e7daaf688bc2 100644 --- a/x-pack/plugins/observability/public/utils/no_data_config.ts +++ b/x-pack/plugins/observability/public/utils/no_data_config.ts @@ -32,7 +32,7 @@ export function getNoDataConfig({ defaultMessage: 'Use Beats and APM agents to send observability data to Elasticsearch. We make it easy with support for many popular systems, apps, and languages.', }), - href: basePath.prepend(`/app/integrations`), + href: basePath.prepend(`/app/home#/tutorial/apm`), }, }, docsLink, diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 22a469dbedbddc..d4d7127d8baee5 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -43,11 +43,5 @@ export type ObservabilityConfig = TypeOf; export const plugin = (initContext: PluginInitializerContext) => new ObservabilityPlugin(initContext); -export { - createOrUpdateIndex, - Mappings, - ObservabilityPluginSetup, - ScopedAnnotationsClient, - unwrapEsResponse, - WrappedElasticsearchClientError, -}; +export type { Mappings, ObservabilityPluginSetup, ScopedAnnotationsClient }; +export { createOrUpdateIndex, unwrapEsResponse, WrappedElasticsearchClientError }; diff --git a/x-pack/plugins/observability/server/routes/types.ts b/x-pack/plugins/observability/server/routes/types.ts index 5075b21fdf1faf..b316b1fffc7700 100644 --- a/x-pack/plugins/observability/server/routes/types.ts +++ b/x-pack/plugins/observability/server/routes/types.ts @@ -17,7 +17,7 @@ import { RuleDataPluginService } from '../../../rule_registry/server'; import { ObservabilityServerRouteRepository } from './get_global_observability_server_route_repository'; import { ObservabilityRequestHandlerContext } from '../types'; -export { ObservabilityServerRouteRepository }; +export type { ObservabilityServerRouteRepository }; export interface ObservabilityRouteHandlerResources { core: { diff --git a/x-pack/plugins/observability/typings/common.ts b/x-pack/plugins/observability/typings/common.ts index 2bc95447d9203c..368dd33f1f13fd 100644 --- a/x-pack/plugins/observability/typings/common.ts +++ b/x-pack/plugins/observability/typings/common.ts @@ -23,6 +23,6 @@ export type PromiseReturnType = Func extends (...args: any[]) => Promise; export const ecsMapping = t.record( t.string, - t.type({ + t.partial({ field: t.string, + value: t.string, }) ); export type ECSMapping = t.TypeOf; diff --git a/x-pack/plugins/osquery/common/shared_imports.ts b/x-pack/plugins/osquery/common/shared_imports.ts index 1461fb31bb9ff1..298cf92c4eeb23 100644 --- a/x-pack/plugins/osquery/common/shared_imports.ts +++ b/x-pack/plugins/osquery/common/shared_imports.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { Agent } from '../../fleet/common'; +export type { Agent } from '../../fleet/common'; diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx index 9da9ac72f273ac..e04f7836084205 100644 --- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx @@ -13,7 +13,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { AgentIdToName } from '../agents/agent_id_to_name'; import { useActionResults } from './use_action_results'; -import { useAllResults } from '../results/use_all_results'; import { Direction } from '../../common/search_strategy'; import { useActionResultsPrivileges } from './use_action_privileges'; @@ -70,38 +69,8 @@ const ActionResultsSummaryComponent: React.FC = ({ }); } - const { data: logsResults } = useAllResults({ - actionId, - activePage: pageIndex, - limit: pageSize, - sort: [ - { - field: '@timestamp', - direction: Direction.asc, - }, - ], - isLive, - skip: !hasActionResultsPrivileges, - }); - const renderAgentIdColumn = useCallback((agentId) => , []); - - const renderRowsColumn = useCallback( - (_, item) => { - if (!logsResults) return '-'; - const agentId = item.fields.agent_id[0]; - - return ( - // @ts-expect-error update types - logsResults?.rawResponse?.aggregations?.count_by_agent_id?.buckets?.find( - // @ts-expect-error update types - (bucket) => bucket.key === agentId - )?.doc_count ?? '-' - ); - }, - [logsResults] - ); - + const renderRowsColumn = useCallback((rowsCount) => rowsCount ?? '-', []); const renderStatusColumn = useCallback( (_, item) => { if (!item.fields.completed_at) { @@ -145,7 +114,7 @@ const ActionResultsSummaryComponent: React.FC = ({ render: renderAgentIdColumn, }, { - field: 'fields.rows[0]', + field: '_source.action_response.osquery.count', name: i18n.translate( 'xpack.osquery.liveQueryActionResults.table.resultRowsNumberColumnTitle', { @@ -177,18 +146,9 @@ const ActionResultsSummaryComponent: React.FC = ({ setIsLive(() => { if (!agentIds?.length || expired) return false; - const uniqueAgentsRepliedCount = - // @ts-expect-error update types - logsResults?.rawResponse.aggregations?.unique_agents.value ?? 0; - - return !!(uniqueAgentsRepliedCount !== agentIds?.length - aggregations.failed); + return !!(aggregations.totalResponded !== agentIds?.length); }); - }, [ - agentIds?.length, - aggregations.failed, - expired, - logsResults?.rawResponse.aggregations?.unique_agents, - ]); + }, [agentIds?.length, aggregations.totalResponded, expired]); return edges.length ? ( diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts index 29bff0819956a8..e4b6ef14eb1e99 100644 --- a/x-pack/plugins/osquery/public/action_results/use_action_results.ts +++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts @@ -84,6 +84,9 @@ export const useActionResults = ({ const totalResponded = // @ts-expect-error update types responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.doc_count ?? 0; + const totalRowCount = + // @ts-expect-error update types + responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.rows_count?.value ?? 0; const aggsBuckets = // @ts-expect-error update types responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.responses.buckets; @@ -100,6 +103,7 @@ export const useActionResults = ({ ...responseData, edges: reverse(uniqBy('fields.agent_id[0]', flatten([responseData.edges, previousEdges]))), aggregations: { + totalRowCount, totalResponded, // @ts-expect-error update types successful: aggsBuckets?.find((bucket) => bucket.key === 'success')?.doc_count ?? 0, diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts index 15f1e48f1536e5..678ea76ea5e84e 100644 --- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts +++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts @@ -21,7 +21,7 @@ export const useAgentPolicy = ({ policyId, skip, silent }: UseAgentPolicy) => { const { http } = useKibana().services; const setErrorToast = useErrorToast(); - return useQuery( + return useQuery( ['agentPolicy', { policyId }], () => http.get(`/internal/osquery/fleet_wrapper/agent_policies/${policyId}`), { diff --git a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts index cf777092da5cf4..759e9f22c71b8c 100644 --- a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts +++ b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts @@ -18,10 +18,12 @@ export const useOsqueryPolicies = () => { const { isLoading: osqueryPoliciesLoading, data: osqueryPolicies = [] } = useQuery( ['osqueryPolicies'], - () => http.get('/internal/osquery/fleet_wrapper/package_policies'), + () => + http.get<{ items: Array<{ policy_id: string }> }>( + '/internal/osquery/fleet_wrapper/package_policies' + ), { - select: (response) => - uniq(response.items.map((p: { policy_id: string }) => p.policy_id)), + select: (response) => uniq(response.items.map((p) => p.policy_id)), onSuccess: () => setErrorToast(), onError: (error: Error) => setErrorToast(error, { diff --git a/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx b/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx index 1994ea348e30c1..92f32d6535b4c9 100644 --- a/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx +++ b/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx @@ -15,14 +15,21 @@ export const useOsqueryIntegrationStatus = () => { const { http } = useKibana().services; const setErrorToast = useErrorToast(); - return useQuery('integration', () => http.get('/internal/osquery/status'), { - onError: (error: Error) => - setErrorToast(error, { - title: i18n.translate('xpack.osquery.osquery_integration.fetchError', { - defaultMessage: 'Error while fetching osquery integration', + return useQuery( + 'integration', + () => + http.get<{ name: string; version: string; title: string; install_status: string }>( + '/internal/osquery/status' + ), + { + onError: (error: Error) => + setErrorToast(error, { + title: i18n.translate('xpack.osquery.osquery_integration.fetchError', { + defaultMessage: 'Error while fetching osquery integration', + }), }), - }), - refetchOnReconnect: false, - refetchOnWindowFocus: false, - }); + refetchOnReconnect: false, + refetchOnWindowFocus: false, + } + ); }; diff --git a/x-pack/plugins/osquery/public/common/schemas/ecs/v1.12.1.json b/x-pack/plugins/osquery/public/common/schemas/ecs/v1.12.1.json index 2b4a3c8c92f2f5..a613c8b5765249 100644 --- a/x-pack/plugins/osquery/public/common/schemas/ecs/v1.12.1.json +++ b/x-pack/plugins/osquery/public/common/schemas/ecs/v1.12.1.json @@ -1 +1 @@ -[{"field":"labels","type":"object","description":"Custom key/value pairs."},{"field":"message","type":"match_only_text","description":"Log message optimized for viewing in a log viewer."},{"field":"tags","type":"keyword","description":"List of keywords used to tag each event."},{"field":"agent.build.original","type":"keyword","description":"Extended build information for the agent."},{"field":"client.address","type":"keyword","description":"Client network address."},{"field":"client.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"client.as.organization.name","type":"keyword","description":"Organization name."},{"field":"client.as.organization.name.text","type":"match_only_text","description":"Organization name."},{"field":"client.bytes","type":"long","description":"Bytes sent from the client to the server."},{"field":"client.domain","type":"keyword","description":"Client domain."},{"field":"client.geo.city_name","type":"keyword","description":"City name."},{"field":"client.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"client.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"client.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"client.geo.country_name","type":"keyword","description":"Country name."},{"field":"client.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"client.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"client.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"client.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"client.geo.region_name","type":"keyword","description":"Region name."},{"field":"client.geo.timezone","type":"keyword","description":"Time zone."},{"field":"client.ip","type":"ip","description":"IP address of the client."},{"field":"client.mac","type":"keyword","description":"MAC address of the client."},{"field":"client.nat.ip","type":"ip","description":"Client NAT ip address"},{"field":"client.nat.port","type":"long","description":"Client NAT port"},{"field":"client.packets","type":"long","description":"Packets sent from the client to the server."},{"field":"client.port","type":"long","description":"Port of the client."},{"field":"client.registered_domain","type":"keyword","description":"The highest registered client domain, stripped of the subdomain."},{"field":"client.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"client.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"client.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"client.user.email","type":"keyword","description":"User email address."},{"field":"client.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"client.user.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"client.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"client.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"client.user.group.name","type":"keyword","description":"Name of the group."},{"field":"client.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"client.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"client.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"client.user.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"client.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"cloud.account.id","type":"keyword","description":"The cloud account or organization id."},{"field":"cloud.account.name","type":"keyword","description":"The cloud account name."},{"field":"cloud.availability_zone","type":"keyword","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.instance.id","type":"keyword","description":"Instance ID of the host machine."},{"field":"cloud.instance.name","type":"keyword","description":"Instance name of the host machine."},{"field":"cloud.machine.type","type":"keyword","description":"Machine type of the host machine."},{"field":"cloud.project.id","type":"keyword","description":"The cloud project id."},{"field":"cloud.project.name","type":"keyword","description":"The cloud project name."},{"field":"cloud.provider","type":"keyword","description":"Name of the cloud provider."},{"field":"cloud.region","type":"keyword","description":"Region in which this host, resource, or service is located."},{"field":"cloud.service.name","type":"keyword","description":"The cloud service name."},{"field":"container.id","type":"keyword","description":"Unique container id."},{"field":"container.image.name","type":"keyword","description":"Name of the image the container was built on."},{"field":"container.image.tag","type":"keyword","description":"Container image tags."},{"field":"container.labels","type":"object","description":"Image labels."},{"field":"container.name","type":"keyword","description":"Container name."},{"field":"container.runtime","type":"keyword","description":"Runtime managing this container."},{"field":"data_stream.dataset","type":"constant_keyword","description":"The field can contain anything that makes sense to signify the source of the data."},{"field":"data_stream.namespace","type":"constant_keyword","description":"A user defined namespace. Namespaces are useful to allow grouping of data."},{"field":"data_stream.type","type":"constant_keyword","description":"An overarching type for the data stream."},{"field":"destination.address","type":"keyword","description":"Destination network address."},{"field":"destination.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"destination.as.organization.name","type":"keyword","description":"Organization name."},{"field":"destination.as.organization.name.text","type":"match_only_text","description":"Organization name."},{"field":"destination.bytes","type":"long","description":"Bytes sent from the destination to the source."},{"field":"destination.domain","type":"keyword","description":"Destination domain."},{"field":"destination.geo.city_name","type":"keyword","description":"City name."},{"field":"destination.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"destination.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"destination.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"destination.geo.country_name","type":"keyword","description":"Country name."},{"field":"destination.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"destination.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"destination.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"destination.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"destination.geo.region_name","type":"keyword","description":"Region name."},{"field":"destination.geo.timezone","type":"keyword","description":"Time zone."},{"field":"destination.ip","type":"ip","description":"IP address of the destination."},{"field":"destination.mac","type":"keyword","description":"MAC address of the destination."},{"field":"destination.nat.ip","type":"ip","description":"Destination NAT ip"},{"field":"destination.nat.port","type":"long","description":"Destination NAT Port"},{"field":"destination.packets","type":"long","description":"Packets sent from the destination to the source."},{"field":"destination.port","type":"long","description":"Port of the destination."},{"field":"destination.registered_domain","type":"keyword","description":"The highest registered destination domain, stripped of the subdomain."},{"field":"destination.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"destination.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"destination.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"destination.user.email","type":"keyword","description":"User email address."},{"field":"destination.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"destination.user.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"destination.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"destination.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"destination.user.group.name","type":"keyword","description":"Name of the group."},{"field":"destination.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"destination.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"destination.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"destination.user.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"destination.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"dll.code_signature.digest_algorithm","type":"keyword","description":"Hashing algorithm used to sign the process."},{"field":"dll.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"dll.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"dll.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"dll.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"dll.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"dll.code_signature.timestamp","type":"date","description":"When the signature was generated and signed."},{"field":"dll.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"dll.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"dll.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"dll.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"dll.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"dll.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"dll.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"dll.name","type":"keyword","description":"Name of the library."},{"field":"dll.path","type":"keyword","description":"Full file path of the library."},{"field":"dll.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"dll.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"dll.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"dll.pe.file_version","type":"keyword","description":"Process name."},{"field":"dll.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"dll.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"dll.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"dns.answers","type":"object","description":"Array of DNS answers."},{"field":"dns.answers.class","type":"keyword","description":"The class of DNS data contained in this resource record."},{"field":"dns.answers.data","type":"keyword","description":"The data describing the resource."},{"field":"dns.answers.name","type":"keyword","description":"The domain name to which this resource record pertains."},{"field":"dns.answers.ttl","type":"long","description":"The time interval in seconds that this resource record may be cached before it should be discarded."},{"field":"dns.answers.type","type":"keyword","description":"The type of data contained in this resource record."},{"field":"dns.header_flags","type":"keyword","description":"Array of DNS header flags."},{"field":"dns.id","type":"keyword","description":"The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response."},{"field":"dns.op_code","type":"keyword","description":"The DNS operation code that specifies the kind of query in the message."},{"field":"dns.question.class","type":"keyword","description":"The class of records being queried."},{"field":"dns.question.name","type":"keyword","description":"The name being queried."},{"field":"dns.question.registered_domain","type":"keyword","description":"The highest registered domain, stripped of the subdomain."},{"field":"dns.question.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"dns.question.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"dns.question.type","type":"keyword","description":"The type of record being queried."},{"field":"dns.resolved_ip","type":"ip","description":"Array containing all IPs seen in answers.data"},{"field":"dns.response_code","type":"keyword","description":"The DNS response code."},{"field":"dns.type","type":"keyword","description":"The type of DNS event captured, query or answer."},{"field":"error.code","type":"keyword","description":"Error code describing the error."},{"field":"error.id","type":"keyword","description":"Unique identifier for the error."},{"field":"error.message","type":"match_only_text","description":"Error message."},{"field":"error.stack_trace","type":"wildcard","description":"The stack trace of this error in plain text."},{"field":"error.stack_trace.text","type":"match_only_text","description":"The stack trace of this error in plain text."},{"field":"error.type","type":"keyword","description":"The type of the error, for example the class name of the exception."},{"field":"event.action","type":"keyword","description":"The action captured by the event."},{"field":"event.category","type":"keyword","description":"Event category. The second categorization field in the hierarchy."},{"field":"event.code","type":"keyword","description":"Identification code for this event."},{"field":"event.created","type":"date","description":"Time when the event was first read by an agent or by your pipeline."},{"field":"event.dataset","type":"keyword","description":"Name of the dataset."},{"field":"event.duration","type":"long","description":"Duration of the event in nanoseconds."},{"field":"event.end","type":"date","description":"event.end contains the date when the event ended or when the activity was last observed."},{"field":"event.hash","type":"keyword","description":"Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity."},{"field":"event.id","type":"keyword","description":"Unique ID to describe the event."},{"field":"event.kind","type":"keyword","description":"The kind of the event. The highest categorization field in the hierarchy."},{"field":"event.original","type":"keyword","description":"Raw text message of entire event."},{"field":"event.outcome","type":"keyword","description":"The outcome of the event. The lowest level categorization field in the hierarchy."},{"field":"event.provider","type":"keyword","description":"Source of the event."},{"field":"event.reason","type":"keyword","description":"Reason why this event happened, according to the source"},{"field":"event.reference","type":"keyword","description":"Event reference URL"},{"field":"event.risk_score","type":"float","description":"Risk score or priority of the event (e.g. security solutions). Use your system's original value here."},{"field":"event.risk_score_norm","type":"float","description":"Normalized risk score or priority of the event (0-100)."},{"field":"event.sequence","type":"long","description":"Sequence number of the event."},{"field":"event.severity","type":"long","description":"Numeric severity of the event."},{"field":"event.start","type":"date","description":"event.start contains the date when the event started or when the activity was first observed."},{"field":"event.timezone","type":"keyword","description":"Event time zone."},{"field":"event.type","type":"keyword","description":"Event type. The third categorization field in the hierarchy."},{"field":"event.url","type":"keyword","description":"Event investigation URL"},{"field":"file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"file.code_signature.digest_algorithm","type":"keyword","description":"Hashing algorithm used to sign the process."},{"field":"file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"file.code_signature.timestamp","type":"date","description":"When the signature was generated and signed."},{"field":"file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"file.created","type":"date","description":"File creation time."},{"field":"file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"file.fork_name","type":"keyword","description":"A fork is additional data associated with a filesystem object."},{"field":"file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"file.group","type":"keyword","description":"Primary group name of the file."},{"field":"file.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"file.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"file.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"file.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"file.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"file.owner","type":"keyword","description":"File owner's username."},{"field":"file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"file.path.text","type":"match_only_text","description":"Full path to the file, including the file name."},{"field":"file.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"file.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"file.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"file.pe.file_version","type":"keyword","description":"Process name."},{"field":"file.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"file.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"file.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"file.size","type":"long","description":"File size in bytes."},{"field":"file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"file.target_path.text","type":"match_only_text","description":"Target path for symlinks."},{"field":"file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"file.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"file.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"file.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"file.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"file.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"file.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"file.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"file.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"file.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"file.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"file.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"file.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"file.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"file.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"file.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"file.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"file.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"file.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"file.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"file.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"file.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"file.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"group.name","type":"keyword","description":"Name of the group."},{"field":"host.cpu.usage","type":"scaled_float","description":"Percent CPU used, between 0 and 1."},{"field":"host.disk.read.bytes","type":"long","description":"The number of bytes read by all disks."},{"field":"host.disk.write.bytes","type":"long","description":"The number of bytes written on all disks."},{"field":"host.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"host.geo.city_name","type":"keyword","description":"City name."},{"field":"host.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"host.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"host.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"host.geo.country_name","type":"keyword","description":"Country name."},{"field":"host.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"host.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"host.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"host.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"host.geo.region_name","type":"keyword","description":"Region name."},{"field":"host.geo.timezone","type":"keyword","description":"Time zone."},{"field":"host.name","type":"keyword","description":"Name of the host."},{"field":"host.network.egress.bytes","type":"long","description":"The number of bytes sent on all network interfaces."},{"field":"host.network.egress.packets","type":"long","description":"The number of packets sent on all network interfaces."},{"field":"host.network.ingress.bytes","type":"long","description":"The number of bytes received on all network interfaces."},{"field":"host.network.ingress.packets","type":"long","description":"The number of packets received on all network interfaces."},{"field":"host.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"host.os.full.text","type":"match_only_text","description":"Operating system name, including the version or code name."},{"field":"host.os.name.text","type":"match_only_text","description":"Operating system name, without the version."},{"field":"host.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"host.type","type":"keyword","description":"Type of host."},{"field":"host.uptime","type":"long","description":"Seconds the host has been up."},{"field":"host.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"host.user.email","type":"keyword","description":"User email address."},{"field":"host.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"host.user.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"host.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"host.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"host.user.group.name","type":"keyword","description":"Name of the group."},{"field":"host.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"host.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"host.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"host.user.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"host.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"http.request.body.bytes","type":"long","description":"Size in bytes of the request body."},{"field":"http.request.body.content","type":"wildcard","description":"The full HTTP request body."},{"field":"http.request.body.content.text","type":"match_only_text","description":"The full HTTP request body."},{"field":"http.request.bytes","type":"long","description":"Total size in bytes of the request (body and headers)."},{"field":"http.request.id","type":"keyword","description":"HTTP request ID."},{"field":"http.request.method","type":"keyword","description":"HTTP request method."},{"field":"http.request.mime_type","type":"keyword","description":"Mime type of the body of the request."},{"field":"http.request.referrer","type":"keyword","description":"Referrer for this HTTP request."},{"field":"http.response.body.bytes","type":"long","description":"Size in bytes of the response body."},{"field":"http.response.body.content","type":"wildcard","description":"The full HTTP response body."},{"field":"http.response.body.content.text","type":"match_only_text","description":"The full HTTP response body."},{"field":"http.response.bytes","type":"long","description":"Total size in bytes of the response (body and headers)."},{"field":"http.response.mime_type","type":"keyword","description":"Mime type of the body of the response."},{"field":"http.response.status_code","type":"long","description":"HTTP response status code."},{"field":"http.version","type":"keyword","description":"HTTP version."},{"field":"log.file.path","type":"keyword","description":"Full path to the log file this event came from."},{"field":"log.level","type":"keyword","description":"Log level of the log event."},{"field":"log.logger","type":"keyword","description":"Name of the logger."},{"field":"log.origin.file.line","type":"integer","description":"The line number of the file which originated the log event."},{"field":"log.origin.file.name","type":"keyword","description":"The code file which originated the log event."},{"field":"log.origin.function","type":"keyword","description":"The function which originated the log event."},{"field":"log.original","type":"keyword","description":"Deprecated original log message with light interpretation only (encoding, newlines)."},{"field":"log.syslog","type":"object","description":"Syslog metadata"},{"field":"log.syslog.facility.code","type":"long","description":"Syslog numeric facility of the event."},{"field":"log.syslog.facility.name","type":"keyword","description":"Syslog text-based facility of the event."},{"field":"log.syslog.priority","type":"long","description":"Syslog priority of the event."},{"field":"log.syslog.severity.code","type":"long","description":"Syslog numeric severity of the event."},{"field":"log.syslog.severity.name","type":"keyword","description":"Syslog text-based severity of the event."},{"field":"network.application","type":"keyword","description":"Application level protocol name."},{"field":"network.bytes","type":"long","description":"Total bytes transferred in both directions."},{"field":"network.community_id","type":"keyword","description":"A hash of source and destination IPs and ports."},{"field":"network.direction","type":"keyword","description":"Direction of the network traffic."},{"field":"network.forwarded_ip","type":"ip","description":"Host IP address when the source IP address is the proxy."},{"field":"network.iana_number","type":"keyword","description":"IANA Protocol Number."},{"field":"network.inner","type":"object","description":"Inner VLAN tag information"},{"field":"network.inner.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"network.inner.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"network.name","type":"keyword","description":"Name given by operators to sections of their network."},{"field":"network.packets","type":"long","description":"Total packets transferred in both directions."},{"field":"network.protocol","type":"keyword","description":"L7 Network protocol name."},{"field":"network.transport","type":"keyword","description":"Protocol Name corresponding to the field `iana_number`."},{"field":"network.type","type":"keyword","description":"In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc"},{"field":"network.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"network.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress","type":"object","description":"Object field for egress information"},{"field":"observer.egress.interface.alias","type":"keyword","description":"Interface alias"},{"field":"observer.egress.interface.id","type":"keyword","description":"Interface ID"},{"field":"observer.egress.interface.name","type":"keyword","description":"Interface name"},{"field":"observer.egress.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"observer.egress.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress.zone","type":"keyword","description":"Observer Egress zone"},{"field":"observer.geo.city_name","type":"keyword","description":"City name."},{"field":"observer.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"observer.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"observer.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"observer.geo.country_name","type":"keyword","description":"Country name."},{"field":"observer.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"observer.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"observer.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"observer.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"observer.geo.region_name","type":"keyword","description":"Region name."},{"field":"observer.geo.timezone","type":"keyword","description":"Time zone."},{"field":"observer.hostname","type":"keyword","description":"Hostname of the observer."},{"field":"observer.ingress","type":"object","description":"Object field for ingress information"},{"field":"observer.ingress.interface.alias","type":"keyword","description":"Interface alias"},{"field":"observer.ingress.interface.id","type":"keyword","description":"Interface ID"},{"field":"observer.ingress.interface.name","type":"keyword","description":"Interface name"},{"field":"observer.ingress.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"observer.ingress.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.ingress.zone","type":"keyword","description":"Observer ingress zone"},{"field":"observer.ip","type":"ip","description":"IP addresses of the observer."},{"field":"observer.mac","type":"keyword","description":"MAC addresses of the observer."},{"field":"observer.name","type":"keyword","description":"Custom name of the observer."},{"field":"observer.os.family","type":"keyword","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"observer.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"observer.os.full.text","type":"match_only_text","description":"Operating system name, including the version or code name."},{"field":"observer.os.kernel","type":"keyword","description":"Operating system kernel version as a raw string."},{"field":"observer.os.name","type":"keyword","description":"Operating system name, without the version."},{"field":"observer.os.name.text","type":"match_only_text","description":"Operating system name, without the version."},{"field":"observer.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"observer.os.type","type":"keyword","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"observer.os.version","type":"keyword","description":"Operating system version as a raw string."},{"field":"observer.product","type":"keyword","description":"The product name of the observer."},{"field":"observer.serial_number","type":"keyword","description":"Observer serial number."},{"field":"observer.type","type":"keyword","description":"The type of the observer the data is coming from."},{"field":"observer.vendor","type":"keyword","description":"Vendor name of the observer."},{"field":"observer.version","type":"keyword","description":"Observer version."},{"field":"orchestrator.api_version","type":"keyword","description":"API version being used to carry out the action"},{"field":"orchestrator.cluster.name","type":"keyword","description":"Name of the cluster."},{"field":"orchestrator.cluster.url","type":"keyword","description":"URL of the API used to manage the cluster."},{"field":"orchestrator.cluster.version","type":"keyword","description":"The version of the cluster."},{"field":"orchestrator.namespace","type":"keyword","description":"Namespace in which the action is taking place."},{"field":"orchestrator.organization","type":"keyword","description":"Organization affected by the event (for multi-tenant orchestrator setups)."},{"field":"orchestrator.resource.name","type":"keyword","description":"Name of the resource being acted upon."},{"field":"orchestrator.resource.type","type":"keyword","description":"Type of resource being acted upon."},{"field":"orchestrator.type","type":"keyword","description":"Orchestrator cluster type (e.g. kubernetes, nomad or cloudfoundry)."},{"field":"organization.id","type":"keyword","description":"Unique identifier for the organization."},{"field":"organization.name","type":"keyword","description":"Organization name."},{"field":"organization.name.text","type":"match_only_text","description":"Organization name."},{"field":"package.architecture","type":"keyword","description":"Package architecture."},{"field":"package.build_version","type":"keyword","description":"Build version information"},{"field":"package.checksum","type":"keyword","description":"Checksum of the installed package for verification."},{"field":"package.description","type":"keyword","description":"Description of the package."},{"field":"package.install_scope","type":"keyword","description":"Indicating how the package was installed, e.g. user-local, global."},{"field":"package.installed","type":"date","description":"Time when package was installed."},{"field":"package.license","type":"keyword","description":"Package license"},{"field":"package.name","type":"keyword","description":"Package name"},{"field":"package.path","type":"keyword","description":"Path where the package is installed."},{"field":"package.reference","type":"keyword","description":"Package home page or reference URL"},{"field":"package.size","type":"long","description":"Package size in bytes."},{"field":"package.type","type":"keyword","description":"Package type"},{"field":"package.version","type":"keyword","description":"Package version"},{"field":"process.args","type":"keyword","description":"Array of process arguments."},{"field":"process.args_count","type":"long","description":"Length of the process.args array."},{"field":"process.code_signature.digest_algorithm","type":"keyword","description":"Hashing algorithm used to sign the process."},{"field":"process.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"process.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"process.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"process.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"process.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"process.code_signature.timestamp","type":"date","description":"When the signature was generated and signed."},{"field":"process.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"process.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.command_line","type":"wildcard","description":"Full command line that started the process."},{"field":"process.command_line.text","type":"match_only_text","description":"Full command line that started the process."},{"field":"process.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"process.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"process.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"process.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"process.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"process.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"process.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"process.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"process.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"process.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"process.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"process.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"process.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"process.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"process.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"process.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"process.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"process.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"process.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"process.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"process.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"process.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"process.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"process.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"process.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"process.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"process.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"process.end","type":"date","description":"The time the process ended."},{"field":"process.entity_id","type":"keyword","description":"Unique identifier for the process."},{"field":"process.executable","type":"keyword","description":"Absolute path to the process executable."},{"field":"process.executable.text","type":"match_only_text","description":"Absolute path to the process executable."},{"field":"process.exit_code","type":"long","description":"The exit code of the process."},{"field":"process.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"process.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"process.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"process.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"process.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"process.name","type":"keyword","description":"Process name."},{"field":"process.name.text","type":"match_only_text","description":"Process name."},{"field":"process.parent.args","type":"keyword","description":"Array of process arguments."},{"field":"process.parent.args_count","type":"long","description":"Length of the process.args array."},{"field":"process.parent.code_signature.digest_algorithm","type":"keyword","description":"Hashing algorithm used to sign the process."},{"field":"process.parent.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"process.parent.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"process.parent.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"process.parent.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"process.parent.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"process.parent.code_signature.timestamp","type":"date","description":"When the signature was generated and signed."},{"field":"process.parent.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"process.parent.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.parent.command_line","type":"wildcard","description":"Full command line that started the process."},{"field":"process.parent.command_line.text","type":"match_only_text","description":"Full command line that started the process."},{"field":"process.parent.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"process.parent.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"process.parent.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"process.parent.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"process.parent.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"process.parent.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.parent.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"process.parent.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"process.parent.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"process.parent.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"process.parent.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.parent.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"process.parent.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"process.parent.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"process.parent.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"process.parent.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"process.parent.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"process.parent.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"process.parent.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"process.parent.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"process.parent.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"process.parent.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"process.parent.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"process.parent.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"process.parent.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"process.parent.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"process.parent.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"process.parent.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"process.parent.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"process.parent.end","type":"date","description":"The time the process ended."},{"field":"process.parent.entity_id","type":"keyword","description":"Unique identifier for the process."},{"field":"process.parent.executable","type":"keyword","description":"Absolute path to the process executable."},{"field":"process.parent.executable.text","type":"match_only_text","description":"Absolute path to the process executable."},{"field":"process.parent.exit_code","type":"long","description":"The exit code of the process."},{"field":"process.parent.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"process.parent.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"process.parent.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"process.parent.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"process.parent.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"process.parent.name","type":"keyword","description":"Process name."},{"field":"process.parent.name.text","type":"match_only_text","description":"Process name."},{"field":"process.parent.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"process.parent.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"process.parent.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"process.parent.pe.file_version","type":"keyword","description":"Process name."},{"field":"process.parent.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"process.parent.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"process.parent.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"process.parent.pgid","type":"long","description":"Identifier of the group of processes the process belongs to."},{"field":"process.parent.pid","type":"long","description":"Process id."},{"field":"process.parent.ppid","type":"long","description":"Parent process' pid."},{"field":"process.parent.start","type":"date","description":"The time the process started."},{"field":"process.parent.thread.id","type":"long","description":"Thread ID."},{"field":"process.parent.thread.name","type":"keyword","description":"Thread name."},{"field":"process.parent.title","type":"keyword","description":"Process title."},{"field":"process.parent.title.text","type":"match_only_text","description":"Process title."},{"field":"process.parent.uptime","type":"long","description":"Seconds the process has been up."},{"field":"process.parent.working_directory","type":"keyword","description":"The working directory of the process."},{"field":"process.parent.working_directory.text","type":"match_only_text","description":"The working directory of the process."},{"field":"process.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"process.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"process.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"process.pe.file_version","type":"keyword","description":"Process name."},{"field":"process.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"process.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"process.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"process.pgid","type":"long","description":"Identifier of the group of processes the process belongs to."},{"field":"process.pid","type":"long","description":"Process id."},{"field":"process.ppid","type":"long","description":"Parent process' pid."},{"field":"process.start","type":"date","description":"The time the process started."},{"field":"process.thread.id","type":"long","description":"Thread ID."},{"field":"process.thread.name","type":"keyword","description":"Thread name."},{"field":"process.title","type":"keyword","description":"Process title."},{"field":"process.title.text","type":"match_only_text","description":"Process title."},{"field":"process.uptime","type":"long","description":"Seconds the process has been up."},{"field":"process.working_directory","type":"keyword","description":"The working directory of the process."},{"field":"process.working_directory.text","type":"match_only_text","description":"The working directory of the process."},{"field":"registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"registry.data.strings","type":"wildcard","description":"List of strings representing what was written to the registry."},{"field":"registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"registry.value","type":"keyword","description":"Name of the value written."},{"field":"related.hash","type":"keyword","description":"All the hashes seen on your event."},{"field":"related.hosts","type":"keyword","description":"All the host identifiers seen on your event."},{"field":"related.ip","type":"ip","description":"All of the IPs seen on your event."},{"field":"related.user","type":"keyword","description":"All the user names or other user identifiers seen on the event."},{"field":"rule.author","type":"keyword","description":"Rule author"},{"field":"rule.category","type":"keyword","description":"Rule category"},{"field":"rule.description","type":"keyword","description":"Rule description"},{"field":"rule.id","type":"keyword","description":"Rule ID"},{"field":"rule.license","type":"keyword","description":"Rule license"},{"field":"rule.name","type":"keyword","description":"Rule name"},{"field":"rule.reference","type":"keyword","description":"Rule reference URL"},{"field":"rule.ruleset","type":"keyword","description":"Rule ruleset"},{"field":"rule.uuid","type":"keyword","description":"Rule UUID"},{"field":"rule.version","type":"keyword","description":"Rule version"},{"field":"server.address","type":"keyword","description":"Server network address."},{"field":"server.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"server.as.organization.name","type":"keyword","description":"Organization name."},{"field":"server.as.organization.name.text","type":"match_only_text","description":"Organization name."},{"field":"server.bytes","type":"long","description":"Bytes sent from the server to the client."},{"field":"server.domain","type":"keyword","description":"Server domain."},{"field":"server.geo.city_name","type":"keyword","description":"City name."},{"field":"server.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"server.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"server.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"server.geo.country_name","type":"keyword","description":"Country name."},{"field":"server.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"server.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"server.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"server.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"server.geo.region_name","type":"keyword","description":"Region name."},{"field":"server.geo.timezone","type":"keyword","description":"Time zone."},{"field":"server.ip","type":"ip","description":"IP address of the server."},{"field":"server.mac","type":"keyword","description":"MAC address of the server."},{"field":"server.nat.ip","type":"ip","description":"Server NAT ip"},{"field":"server.nat.port","type":"long","description":"Server NAT port"},{"field":"server.packets","type":"long","description":"Packets sent from the server to the client."},{"field":"server.port","type":"long","description":"Port of the server."},{"field":"server.registered_domain","type":"keyword","description":"The highest registered server domain, stripped of the subdomain."},{"field":"server.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"server.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"server.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"server.user.email","type":"keyword","description":"User email address."},{"field":"server.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"server.user.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"server.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"server.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"server.user.group.name","type":"keyword","description":"Name of the group."},{"field":"server.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"server.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"server.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"server.user.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"server.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"service.address","type":"keyword","description":"Address of this service."},{"field":"service.environment","type":"keyword","description":"Environment of the service."},{"field":"service.ephemeral_id","type":"keyword","description":"Ephemeral identifier of this service."},{"field":"service.id","type":"keyword","description":"Unique identifier of the running service."},{"field":"service.name","type":"keyword","description":"Name of the service."},{"field":"service.node.name","type":"keyword","description":"Name of the service node."},{"field":"service.state","type":"keyword","description":"Current state of the service."},{"field":"service.type","type":"keyword","description":"The type of the service."},{"field":"service.version","type":"keyword","description":"Version of the service."},{"field":"source.address","type":"keyword","description":"Source network address."},{"field":"source.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"source.as.organization.name","type":"keyword","description":"Organization name."},{"field":"source.as.organization.name.text","type":"match_only_text","description":"Organization name."},{"field":"source.bytes","type":"long","description":"Bytes sent from the source to the destination."},{"field":"source.domain","type":"keyword","description":"Source domain."},{"field":"source.geo.city_name","type":"keyword","description":"City name."},{"field":"source.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"source.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"source.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"source.geo.country_name","type":"keyword","description":"Country name."},{"field":"source.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"source.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"source.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"source.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"source.geo.region_name","type":"keyword","description":"Region name."},{"field":"source.geo.timezone","type":"keyword","description":"Time zone."},{"field":"source.ip","type":"ip","description":"IP address of the source."},{"field":"source.mac","type":"keyword","description":"MAC address of the source."},{"field":"source.nat.ip","type":"ip","description":"Source NAT ip"},{"field":"source.nat.port","type":"long","description":"Source NAT port"},{"field":"source.packets","type":"long","description":"Packets sent from the source to the destination."},{"field":"source.port","type":"long","description":"Port of the source."},{"field":"source.registered_domain","type":"keyword","description":"The highest registered source domain, stripped of the subdomain."},{"field":"source.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"source.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"source.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"source.user.email","type":"keyword","description":"User email address."},{"field":"source.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"source.user.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"source.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"source.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"source.user.group.name","type":"keyword","description":"Name of the group."},{"field":"source.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"source.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"source.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"source.user.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"source.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"span.id","type":"keyword","description":"Unique identifier of the span within the scope of its trace."},{"field":"threat.enrichments","type":"nested","description":"List of objects containing indicators enriching the event."},{"field":"threat.enrichments.indicator","type":"object","description":"Object containing indicators enriching the event."},{"field":"threat.enrichments.indicator.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"threat.enrichments.indicator.as.organization.name","type":"keyword","description":"Organization name."},{"field":"threat.enrichments.indicator.as.organization.name.text","type":"match_only_text","description":"Organization name."},{"field":"threat.enrichments.indicator.confidence","type":"keyword","description":"Indicator confidence rating"},{"field":"threat.enrichments.indicator.description","type":"keyword","description":"Indicator description"},{"field":"threat.enrichments.indicator.email.address","type":"keyword","description":"Indicator email address"},{"field":"threat.enrichments.indicator.file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"threat.enrichments.indicator.file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"threat.enrichments.indicator.file.code_signature.digest_algorithm","type":"keyword","description":"Hashing algorithm used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"threat.enrichments.indicator.file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"threat.enrichments.indicator.file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"threat.enrichments.indicator.file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.timestamp","type":"date","description":"When the signature was generated and signed."},{"field":"threat.enrichments.indicator.file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"threat.enrichments.indicator.file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.enrichments.indicator.file.created","type":"date","description":"File creation time."},{"field":"threat.enrichments.indicator.file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"threat.enrichments.indicator.file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"threat.enrichments.indicator.file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"threat.enrichments.indicator.file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"threat.enrichments.indicator.file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"threat.enrichments.indicator.file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"threat.enrichments.indicator.file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"threat.enrichments.indicator.file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.enrichments.indicator.file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"threat.enrichments.indicator.file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.enrichments.indicator.file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"threat.enrichments.indicator.file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"threat.enrichments.indicator.file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"threat.enrichments.indicator.file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"threat.enrichments.indicator.file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"threat.enrichments.indicator.file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"threat.enrichments.indicator.file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"threat.enrichments.indicator.file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"threat.enrichments.indicator.file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"threat.enrichments.indicator.file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"threat.enrichments.indicator.file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"threat.enrichments.indicator.file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"threat.enrichments.indicator.file.fork_name","type":"keyword","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.enrichments.indicator.file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"threat.enrichments.indicator.file.group","type":"keyword","description":"Primary group name of the file."},{"field":"threat.enrichments.indicator.file.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"threat.enrichments.indicator.file.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"threat.enrichments.indicator.file.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"threat.enrichments.indicator.file.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"threat.enrichments.indicator.file.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"threat.enrichments.indicator.file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"threat.enrichments.indicator.file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.enrichments.indicator.file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"threat.enrichments.indicator.file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"threat.enrichments.indicator.file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"threat.enrichments.indicator.file.owner","type":"keyword","description":"File owner's username."},{"field":"threat.enrichments.indicator.file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.path.text","type":"match_only_text","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"threat.enrichments.indicator.file.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.file_version","type":"keyword","description":"Process name."},{"field":"threat.enrichments.indicator.file.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"threat.enrichments.indicator.file.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.size","type":"long","description":"File size in bytes."},{"field":"threat.enrichments.indicator.file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.target_path.text","type":"match_only_text","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"threat.enrichments.indicator.file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.enrichments.indicator.file.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.file.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.enrichments.indicator.file.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.file.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.file.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.file.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.file.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.file.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.file.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.enrichments.indicator.file.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.file.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.enrichments.indicator.first_seen","type":"date","description":"Date/time indicator was first reported."},{"field":"threat.enrichments.indicator.geo.city_name","type":"keyword","description":"City name."},{"field":"threat.enrichments.indicator.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"threat.enrichments.indicator.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"threat.enrichments.indicator.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"threat.enrichments.indicator.geo.country_name","type":"keyword","description":"Country name."},{"field":"threat.enrichments.indicator.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"threat.enrichments.indicator.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"threat.enrichments.indicator.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"threat.enrichments.indicator.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"threat.enrichments.indicator.geo.region_name","type":"keyword","description":"Region name."},{"field":"threat.enrichments.indicator.geo.timezone","type":"keyword","description":"Time zone."},{"field":"threat.enrichments.indicator.ip","type":"ip","description":"Indicator IP address"},{"field":"threat.enrichments.indicator.last_seen","type":"date","description":"Date/time indicator was last reported."},{"field":"threat.enrichments.indicator.marking.tlp","type":"keyword","description":"Indicator TLP marking"},{"field":"threat.enrichments.indicator.modified_at","type":"date","description":"Date/time indicator was last updated."},{"field":"threat.enrichments.indicator.port","type":"long","description":"Indicator port"},{"field":"threat.enrichments.indicator.provider","type":"keyword","description":"Indicator provider"},{"field":"threat.enrichments.indicator.reference","type":"keyword","description":"Indicator reference URL"},{"field":"threat.enrichments.indicator.registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"threat.enrichments.indicator.registry.data.strings","type":"wildcard","description":"List of strings representing what was written to the registry."},{"field":"threat.enrichments.indicator.registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"threat.enrichments.indicator.registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"threat.enrichments.indicator.registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"threat.enrichments.indicator.registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"threat.enrichments.indicator.registry.value","type":"keyword","description":"Name of the value written."},{"field":"threat.enrichments.indicator.scanner_stats","type":"long","description":"Scanner statistics"},{"field":"threat.enrichments.indicator.sightings","type":"long","description":"Number of times indicator observed"},{"field":"threat.enrichments.indicator.type","type":"keyword","description":"Type of indicator"},{"field":"threat.enrichments.indicator.url.domain","type":"keyword","description":"Domain of the url."},{"field":"threat.enrichments.indicator.url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.enrichments.indicator.url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"threat.enrichments.indicator.url.full","type":"wildcard","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.full.text","type":"match_only_text","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.original","type":"wildcard","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.original.text","type":"match_only_text","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.password","type":"keyword","description":"Password of the request."},{"field":"threat.enrichments.indicator.url.path","type":"wildcard","description":"Path of the request, such as \"/search\"."},{"field":"threat.enrichments.indicator.url.port","type":"long","description":"Port of the request, such as 443."},{"field":"threat.enrichments.indicator.url.query","type":"keyword","description":"Query string of the request."},{"field":"threat.enrichments.indicator.url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.enrichments.indicator.url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"threat.enrichments.indicator.url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"threat.enrichments.indicator.url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.enrichments.indicator.url.username","type":"keyword","description":"Username of the request."},{"field":"threat.enrichments.indicator.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.enrichments.indicator.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.enrichments.indicator.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.enrichments.matched.atomic","type":"keyword","description":"Matched indicator value"},{"field":"threat.enrichments.matched.field","type":"keyword","description":"Matched indicator field"},{"field":"threat.enrichments.matched.id","type":"keyword","description":"Matched indicator identifier"},{"field":"threat.enrichments.matched.index","type":"keyword","description":"Matched indicator index"},{"field":"threat.enrichments.matched.type","type":"keyword","description":"Type of indicator match"},{"field":"threat.framework","type":"keyword","description":"Threat classification framework."},{"field":"threat.group.alias","type":"keyword","description":"Alias of the group."},{"field":"threat.group.id","type":"keyword","description":"ID of the group."},{"field":"threat.group.name","type":"keyword","description":"Name of the group."},{"field":"threat.group.reference","type":"keyword","description":"Reference URL of the group."},{"field":"threat.indicator.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"threat.indicator.as.organization.name","type":"keyword","description":"Organization name."},{"field":"threat.indicator.as.organization.name.text","type":"match_only_text","description":"Organization name."},{"field":"threat.indicator.confidence","type":"keyword","description":"Indicator confidence rating"},{"field":"threat.indicator.description","type":"keyword","description":"Indicator description"},{"field":"threat.indicator.email.address","type":"keyword","description":"Indicator email address"},{"field":"threat.indicator.file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"threat.indicator.file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"threat.indicator.file.code_signature.digest_algorithm","type":"keyword","description":"Hashing algorithm used to sign the process."},{"field":"threat.indicator.file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"threat.indicator.file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"threat.indicator.file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"threat.indicator.file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.timestamp","type":"date","description":"When the signature was generated and signed."},{"field":"threat.indicator.file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"threat.indicator.file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.indicator.file.created","type":"date","description":"File creation time."},{"field":"threat.indicator.file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"threat.indicator.file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"threat.indicator.file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"threat.indicator.file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"threat.indicator.file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"threat.indicator.file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"threat.indicator.file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"threat.indicator.file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"threat.indicator.file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"threat.indicator.file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.indicator.file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"threat.indicator.file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"threat.indicator.file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"threat.indicator.file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"threat.indicator.file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.indicator.file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"threat.indicator.file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"threat.indicator.file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"threat.indicator.file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"threat.indicator.file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"threat.indicator.file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"threat.indicator.file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"threat.indicator.file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"threat.indicator.file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"threat.indicator.file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"threat.indicator.file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"threat.indicator.file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"threat.indicator.file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"threat.indicator.file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"threat.indicator.file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"threat.indicator.file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"threat.indicator.file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"threat.indicator.file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"threat.indicator.file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"threat.indicator.file.fork_name","type":"keyword","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.indicator.file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"threat.indicator.file.group","type":"keyword","description":"Primary group name of the file."},{"field":"threat.indicator.file.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"threat.indicator.file.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"threat.indicator.file.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"threat.indicator.file.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"threat.indicator.file.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"threat.indicator.file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"threat.indicator.file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.indicator.file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"threat.indicator.file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"threat.indicator.file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"threat.indicator.file.owner","type":"keyword","description":"File owner's username."},{"field":"threat.indicator.file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.path.text","type":"match_only_text","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"threat.indicator.file.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.file_version","type":"keyword","description":"Process name."},{"field":"threat.indicator.file.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"threat.indicator.file.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.indicator.file.size","type":"long","description":"File size in bytes."},{"field":"threat.indicator.file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"threat.indicator.file.target_path.text","type":"match_only_text","description":"Target path for symlinks."},{"field":"threat.indicator.file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"threat.indicator.file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.indicator.file.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.file.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.indicator.file.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.file.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.file.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.file.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.indicator.file.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.file.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.file.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.indicator.file.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.indicator.file.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.file.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.indicator.file.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.file.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.indicator.first_seen","type":"date","description":"Date/time indicator was first reported."},{"field":"threat.indicator.geo.city_name","type":"keyword","description":"City name."},{"field":"threat.indicator.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"threat.indicator.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"threat.indicator.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"threat.indicator.geo.country_name","type":"keyword","description":"Country name."},{"field":"threat.indicator.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"threat.indicator.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"threat.indicator.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"threat.indicator.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"threat.indicator.geo.region_name","type":"keyword","description":"Region name."},{"field":"threat.indicator.geo.timezone","type":"keyword","description":"Time zone."},{"field":"threat.indicator.ip","type":"ip","description":"Indicator IP address"},{"field":"threat.indicator.last_seen","type":"date","description":"Date/time indicator was last reported."},{"field":"threat.indicator.marking.tlp","type":"keyword","description":"Indicator TLP marking"},{"field":"threat.indicator.modified_at","type":"date","description":"Date/time indicator was last updated."},{"field":"threat.indicator.port","type":"long","description":"Indicator port"},{"field":"threat.indicator.provider","type":"keyword","description":"Indicator provider"},{"field":"threat.indicator.reference","type":"keyword","description":"Indicator reference URL"},{"field":"threat.indicator.registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"threat.indicator.registry.data.strings","type":"wildcard","description":"List of strings representing what was written to the registry."},{"field":"threat.indicator.registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"threat.indicator.registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"threat.indicator.registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"threat.indicator.registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"threat.indicator.registry.value","type":"keyword","description":"Name of the value written."},{"field":"threat.indicator.scanner_stats","type":"long","description":"Scanner statistics"},{"field":"threat.indicator.sightings","type":"long","description":"Number of times indicator observed"},{"field":"threat.indicator.type","type":"keyword","description":"Type of indicator"},{"field":"threat.indicator.url.domain","type":"keyword","description":"Domain of the url."},{"field":"threat.indicator.url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.indicator.url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"threat.indicator.url.full","type":"wildcard","description":"Full unparsed URL."},{"field":"threat.indicator.url.full.text","type":"match_only_text","description":"Full unparsed URL."},{"field":"threat.indicator.url.original","type":"wildcard","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.original.text","type":"match_only_text","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.password","type":"keyword","description":"Password of the request."},{"field":"threat.indicator.url.path","type":"wildcard","description":"Path of the request, such as \"/search\"."},{"field":"threat.indicator.url.port","type":"long","description":"Port of the request, such as 443."},{"field":"threat.indicator.url.query","type":"keyword","description":"Query string of the request."},{"field":"threat.indicator.url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.indicator.url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"threat.indicator.url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"threat.indicator.url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.indicator.url.username","type":"keyword","description":"Username of the request."},{"field":"threat.indicator.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.indicator.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.indicator.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.indicator.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.indicator.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.indicator.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.software.alias","type":"keyword","description":"Alias of the software"},{"field":"threat.software.id","type":"keyword","description":"ID of the software"},{"field":"threat.software.name","type":"keyword","description":"Name of the software."},{"field":"threat.software.platforms","type":"keyword","description":"Platforms of the software."},{"field":"threat.software.reference","type":"keyword","description":"Software reference URL."},{"field":"threat.software.type","type":"keyword","description":"Software type."},{"field":"threat.tactic.id","type":"keyword","description":"Threat tactic id."},{"field":"threat.tactic.name","type":"keyword","description":"Threat tactic."},{"field":"threat.tactic.reference","type":"keyword","description":"Threat tactic URL reference."},{"field":"threat.technique.id","type":"keyword","description":"Threat technique id."},{"field":"threat.technique.name","type":"keyword","description":"Threat technique name."},{"field":"threat.technique.name.text","type":"match_only_text","description":"Threat technique name."},{"field":"threat.technique.reference","type":"keyword","description":"Threat technique URL reference."},{"field":"threat.technique.subtechnique.id","type":"keyword","description":"Threat subtechnique id."},{"field":"threat.technique.subtechnique.name","type":"keyword","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.name.text","type":"match_only_text","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.reference","type":"keyword","description":"Threat subtechnique URL reference."},{"field":"tls.cipher","type":"keyword","description":"String indicating the cipher used during the current connection."},{"field":"tls.client.certificate","type":"keyword","description":"PEM-encoded stand-alone certificate offered by the client."},{"field":"tls.client.certificate_chain","type":"keyword","description":"Array of PEM-encoded certificates that make up the certificate chain offered by the client."},{"field":"tls.client.hash.md5","type":"keyword","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha1","type":"keyword","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha256","type":"keyword","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.issuer","type":"keyword","description":"Distinguished name of subject of the issuer of the x.509 certificate presented by the client."},{"field":"tls.client.ja3","type":"keyword","description":"A hash that identifies clients based on how they perform an SSL/TLS handshake."},{"field":"tls.client.not_after","type":"date","description":"Date/Time indicating when client certificate is no longer considered valid."},{"field":"tls.client.not_before","type":"date","description":"Date/Time indicating when client certificate is first considered valid."},{"field":"tls.client.server_name","type":"keyword","description":"Hostname the client is trying to connect to. Also called the SNI."},{"field":"tls.client.subject","type":"keyword","description":"Distinguished name of subject of the x.509 certificate presented by the client."},{"field":"tls.client.supported_ciphers","type":"keyword","description":"Array of ciphers offered by the client during the client hello."},{"field":"tls.client.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"tls.client.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"tls.client.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.client.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.client.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.client.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.client.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"tls.client.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"tls.client.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.client.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.client.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"tls.client.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"tls.client.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"tls.client.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"tls.client.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"tls.client.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.client.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.client.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"tls.client.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"tls.client.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"tls.curve","type":"keyword","description":"String indicating the curve used for the given cipher, when applicable."},{"field":"tls.established","type":"boolean","description":"Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel."},{"field":"tls.next_protocol","type":"keyword","description":"String indicating the protocol being tunneled."},{"field":"tls.resumed","type":"boolean","description":"Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation."},{"field":"tls.server.certificate","type":"keyword","description":"PEM-encoded stand-alone certificate offered by the server."},{"field":"tls.server.certificate_chain","type":"keyword","description":"Array of PEM-encoded certificates that make up the certificate chain offered by the server."},{"field":"tls.server.hash.md5","type":"keyword","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha1","type":"keyword","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha256","type":"keyword","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.issuer","type":"keyword","description":"Subject of the issuer of the x.509 certificate presented by the server."},{"field":"tls.server.ja3s","type":"keyword","description":"A hash that identifies servers based on how they perform an SSL/TLS handshake."},{"field":"tls.server.not_after","type":"date","description":"Timestamp indicating when server certificate is no longer considered valid."},{"field":"tls.server.not_before","type":"date","description":"Timestamp indicating when server certificate is first considered valid."},{"field":"tls.server.subject","type":"keyword","description":"Subject of the x.509 certificate presented by the server."},{"field":"tls.server.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"tls.server.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"tls.server.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.server.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.server.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.server.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.server.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"tls.server.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"tls.server.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.server.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.server.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"tls.server.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"tls.server.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"tls.server.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"tls.server.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"tls.server.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.server.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.server.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"tls.server.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"tls.server.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"tls.version","type":"keyword","description":"Numeric part of the version parsed from the original string."},{"field":"tls.version_protocol","type":"keyword","description":"Normalized lowercase protocol name parsed from original string."},{"field":"trace.id","type":"keyword","description":"Unique identifier of the trace."},{"field":"transaction.id","type":"keyword","description":"Unique identifier of the transaction within the scope of its trace."},{"field":"url.domain","type":"keyword","description":"Domain of the url."},{"field":"url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"url.full","type":"wildcard","description":"Full unparsed URL."},{"field":"url.full.text","type":"match_only_text","description":"Full unparsed URL."},{"field":"url.original","type":"wildcard","description":"Unmodified original url as seen in the event source."},{"field":"url.original.text","type":"match_only_text","description":"Unmodified original url as seen in the event source."},{"field":"url.password","type":"keyword","description":"Password of the request."},{"field":"url.path","type":"wildcard","description":"Path of the request, such as \"/search\"."},{"field":"url.port","type":"long","description":"Port of the request, such as 443."},{"field":"url.query","type":"keyword","description":"Query string of the request."},{"field":"url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"url.username","type":"keyword","description":"Username of the request."},{"field":"user.changes.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.changes.email","type":"keyword","description":"User email address."},{"field":"user.changes.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.changes.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"user.changes.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.changes.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.changes.group.name","type":"keyword","description":"Name of the group."},{"field":"user.changes.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.changes.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.changes.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.changes.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"user.changes.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.effective.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.effective.email","type":"keyword","description":"User email address."},{"field":"user.effective.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.effective.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"user.effective.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.effective.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.effective.group.name","type":"keyword","description":"Name of the group."},{"field":"user.effective.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.effective.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.effective.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.effective.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"user.effective.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.email","type":"keyword","description":"User email address."},{"field":"user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.group.name","type":"keyword","description":"Name of the group."},{"field":"user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.target.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.target.email","type":"keyword","description":"User email address."},{"field":"user.target.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.target.full_name.text","type":"match_only_text","description":"User's full name, if available."},{"field":"user.target.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.target.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.target.group.name","type":"keyword","description":"Name of the group."},{"field":"user.target.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.target.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.target.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.target.name.text","type":"match_only_text","description":"Short name or login of the user."},{"field":"user.target.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user_agent.device.name","type":"keyword","description":"Name of the device."},{"field":"user_agent.name","type":"keyword","description":"Name of the user agent."},{"field":"user_agent.original","type":"keyword","description":"Unparsed user_agent string."},{"field":"user_agent.original.text","type":"match_only_text","description":"Unparsed user_agent string."},{"field":"user_agent.os.family","type":"keyword","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"user_agent.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.full.text","type":"match_only_text","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.kernel","type":"keyword","description":"Operating system kernel version as a raw string."},{"field":"user_agent.os.name","type":"keyword","description":"Operating system name, without the version."},{"field":"user_agent.os.name.text","type":"match_only_text","description":"Operating system name, without the version."},{"field":"user_agent.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"user_agent.os.type","type":"keyword","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"user_agent.os.version","type":"keyword","description":"Operating system version as a raw string."},{"field":"user_agent.version","type":"keyword","description":"Version of the user agent."},{"field":"vulnerability.category","type":"keyword","description":"Category of a vulnerability."},{"field":"vulnerability.classification","type":"keyword","description":"Classification of the vulnerability."},{"field":"vulnerability.description","type":"keyword","description":"Description of the vulnerability."},{"field":"vulnerability.description.text","type":"match_only_text","description":"Description of the vulnerability."},{"field":"vulnerability.enumeration","type":"keyword","description":"Identifier of the vulnerability."},{"field":"vulnerability.id","type":"keyword","description":"ID of the vulnerability."},{"field":"vulnerability.reference","type":"keyword","description":"Reference of the vulnerability."},{"field":"vulnerability.report_id","type":"keyword","description":"Scan identification number."},{"field":"vulnerability.scanner.vendor","type":"keyword","description":"Name of the scanner vendor."},{"field":"vulnerability.score.base","type":"float","description":"Vulnerability Base score."},{"field":"vulnerability.score.environmental","type":"float","description":"Vulnerability Environmental score."},{"field":"vulnerability.score.temporal","type":"float","description":"Vulnerability Temporal score."},{"field":"vulnerability.score.version","type":"keyword","description":"CVSS version."},{"field":"vulnerability.severity","type":"keyword","description":"Severity of the vulnerability."}] \ No newline at end of file +[{"field":"labels","type":"object","normalization":"","example":{"application":"foo-bar","env":"production"},"description":"Custom key/value pairs."},{"field":"message","type":"match_only_text","normalization":"","example":"Hello World","description":"Log message optimized for viewing in a log viewer."},{"field":"tags","type":"keyword","normalization":"array","example":["production","env2"],"description":"List of keywords used to tag each event."},{"field":"agent.build.original","type":"keyword","normalization":"","example":"metricbeat version 7.6.0 (amd64), libbeat 7.6.0 [6a23e8f8f30f5001ba344e4e54d8d9cb82cb107c built 2020-02-05 23:10:10 +0000 UTC]","description":"Extended build information for the agent."},{"field":"client.address","type":"keyword","normalization":"","example":"","description":"Client network address."},{"field":"client.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"client.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"client.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"client.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the client to the server."},{"field":"client.domain","type":"keyword","normalization":"","example":"","description":"Client domain."},{"field":"client.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"client.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"client.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"client.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"client.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"client.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"client.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"client.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"client.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"client.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"client.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"client.ip","type":"ip","normalization":"","example":"","description":"IP address of the client."},{"field":"client.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the client."},{"field":"client.nat.ip","type":"ip","normalization":"","example":"","description":"Client NAT ip address"},{"field":"client.nat.port","type":"long","normalization":"","example":"","description":"Client NAT port"},{"field":"client.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the client to the server."},{"field":"client.port","type":"long","normalization":"","example":"","description":"Port of the client."},{"field":"client.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered client domain, stripped of the subdomain."},{"field":"client.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"client.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"client.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"client.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"client.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"client.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"client.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"client.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"client.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"client.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"client.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"client.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"client.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"client.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"cloud.account.id","type":"keyword","normalization":"","example":666777888999,"description":"The cloud account or organization id."},{"field":"cloud.account.name","type":"keyword","normalization":"","example":"elastic-dev","description":"The cloud account name."},{"field":"cloud.availability_zone","type":"keyword","normalization":"","example":"us-east-1c","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.instance.id","type":"keyword","normalization":"","example":"i-1234567890abcdef0","description":"Instance ID of the host machine."},{"field":"cloud.instance.name","type":"keyword","normalization":"","example":"","description":"Instance name of the host machine."},{"field":"cloud.machine.type","type":"keyword","normalization":"","example":"t2.medium","description":"Machine type of the host machine."},{"field":"cloud.project.id","type":"keyword","normalization":"","example":"my-project","description":"The cloud project id."},{"field":"cloud.project.name","type":"keyword","normalization":"","example":"my project","description":"The cloud project name."},{"field":"cloud.provider","type":"keyword","normalization":"","example":"aws","description":"Name of the cloud provider."},{"field":"cloud.region","type":"keyword","normalization":"","example":"us-east-1","description":"Region in which this host, resource, or service is located."},{"field":"cloud.service.name","type":"keyword","normalization":"","example":"lambda","description":"The cloud service name."},{"field":"container.id","type":"keyword","normalization":"","example":"","description":"Unique container id."},{"field":"container.image.name","type":"keyword","normalization":"","example":"","description":"Name of the image the container was built on."},{"field":"container.image.tag","type":"keyword","normalization":"array","example":"","description":"Container image tags."},{"field":"container.labels","type":"object","normalization":"","example":"","description":"Image labels."},{"field":"container.name","type":"keyword","normalization":"","example":"","description":"Container name."},{"field":"container.runtime","type":"keyword","normalization":"","example":"docker","description":"Runtime managing this container."},{"field":"data_stream.dataset","type":"constant_keyword","normalization":"","example":"nginx.access","description":"The field can contain anything that makes sense to signify the source of the data."},{"field":"data_stream.namespace","type":"constant_keyword","normalization":"","example":"production","description":"A user defined namespace. Namespaces are useful to allow grouping of data."},{"field":"data_stream.type","type":"constant_keyword","normalization":"","example":"logs","description":"An overarching type for the data stream."},{"field":"destination.address","type":"keyword","normalization":"","example":"","description":"Destination network address."},{"field":"destination.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"destination.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"destination.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"destination.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the destination to the source."},{"field":"destination.domain","type":"keyword","normalization":"","example":"","description":"Destination domain."},{"field":"destination.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"destination.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"destination.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"destination.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"destination.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"destination.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"destination.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"destination.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"destination.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"destination.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"destination.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"destination.ip","type":"ip","normalization":"","example":"","description":"IP address of the destination."},{"field":"destination.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the destination."},{"field":"destination.nat.ip","type":"ip","normalization":"","example":"","description":"Destination NAT ip"},{"field":"destination.nat.port","type":"long","normalization":"","example":"","description":"Destination NAT Port"},{"field":"destination.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the destination to the source."},{"field":"destination.port","type":"long","normalization":"","example":"","description":"Port of the destination."},{"field":"destination.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered destination domain, stripped of the subdomain."},{"field":"destination.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"destination.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"destination.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"destination.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"destination.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"destination.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"destination.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"destination.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"destination.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"destination.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"destination.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"destination.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"destination.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"destination.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"dll.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"dll.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"dll.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"dll.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"dll.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"dll.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"dll.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"dll.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"dll.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"dll.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"dll.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"dll.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"dll.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"dll.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"dll.name","type":"keyword","normalization":"","example":"kernel32.dll","description":"Name of the library."},{"field":"dll.path","type":"keyword","normalization":"","example":"C:\\Windows\\System32\\kernel32.dll","description":"Full file path of the library."},{"field":"dll.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"dll.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"dll.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"dll.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"dll.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"dll.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"dll.pe.product","type":"keyword","normalization":"","example":"Microsoft® Windows® Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"dns.answers","type":"object","normalization":"array","example":"","description":"Array of DNS answers."},{"field":"dns.answers.class","type":"keyword","normalization":"","example":"IN","description":"The class of DNS data contained in this resource record."},{"field":"dns.answers.data","type":"keyword","normalization":"","example":"10.10.10.10","description":"The data describing the resource."},{"field":"dns.answers.name","type":"keyword","normalization":"","example":"www.example.com","description":"The domain name to which this resource record pertains."},{"field":"dns.answers.ttl","type":"long","normalization":"","example":180,"description":"The time interval in seconds that this resource record may be cached before it should be discarded."},{"field":"dns.answers.type","type":"keyword","normalization":"","example":"CNAME","description":"The type of data contained in this resource record."},{"field":"dns.header_flags","type":"keyword","normalization":"array","example":["RD","RA"],"description":"Array of DNS header flags."},{"field":"dns.id","type":"keyword","normalization":"","example":62111,"description":"The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response."},{"field":"dns.op_code","type":"keyword","normalization":"","example":"QUERY","description":"The DNS operation code that specifies the kind of query in the message."},{"field":"dns.question.class","type":"keyword","normalization":"","example":"IN","description":"The class of records being queried."},{"field":"dns.question.name","type":"keyword","normalization":"","example":"www.example.com","description":"The name being queried."},{"field":"dns.question.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered domain, stripped of the subdomain."},{"field":"dns.question.subdomain","type":"keyword","normalization":"","example":"www","description":"The subdomain of the domain."},{"field":"dns.question.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"dns.question.type","type":"keyword","normalization":"","example":"AAAA","description":"The type of record being queried."},{"field":"dns.resolved_ip","type":"ip","normalization":"array","example":["10.10.10.10","10.10.10.11"],"description":"Array containing all IPs seen in answers.data"},{"field":"dns.response_code","type":"keyword","normalization":"","example":"NOERROR","description":"The DNS response code."},{"field":"dns.type","type":"keyword","normalization":"","example":"answer","description":"The type of DNS event captured, query or answer."},{"field":"error.code","type":"keyword","normalization":"","example":"","description":"Error code describing the error."},{"field":"error.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the error."},{"field":"error.message","type":"match_only_text","normalization":"","example":"","description":"Error message."},{"field":"error.stack_trace","type":"wildcard","normalization":"","example":"","description":"The stack trace of this error in plain text."},{"field":"error.stack_trace.text","type":"match_only_text","normalization":"","example":"","description":"The stack trace of this error in plain text."},{"field":"error.type","type":"keyword","normalization":"","example":"java.lang.NullPointerException","description":"The type of the error, for example the class name of the exception."},{"field":"event.action","type":"keyword","normalization":"","example":"user-password-change","description":"The action captured by the event."},{"field":"event.category","type":"keyword","normalization":"array","example":"authentication","description":"Event category. The second categorization field in the hierarchy."},{"field":"event.code","type":"keyword","normalization":"","example":4648,"description":"Identification code for this event."},{"field":"event.created","type":"date","normalization":"","example":"2016-05-23T08:05:34.857Z","description":"Time when the event was first read by an agent or by your pipeline."},{"field":"event.dataset","type":"keyword","normalization":"","example":"apache.access","description":"Name of the dataset."},{"field":"event.duration","type":"long","normalization":"","example":"","description":"Duration of the event in nanoseconds."},{"field":"event.end","type":"date","normalization":"","example":"","description":"event.end contains the date when the event ended or when the activity was last observed."},{"field":"event.hash","type":"keyword","normalization":"","example":"123456789012345678901234567890ABCD","description":"Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity."},{"field":"event.id","type":"keyword","normalization":"","example":"8a4f500d","description":"Unique ID to describe the event."},{"field":"event.kind","type":"keyword","normalization":"","example":"alert","description":"The kind of the event. The highest categorization field in the hierarchy."},{"field":"event.original","type":"keyword","normalization":"","example":"Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100| worm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232","description":"Raw text message of entire event."},{"field":"event.outcome","type":"keyword","normalization":"","example":"success","description":"The outcome of the event. The lowest level categorization field in the hierarchy."},{"field":"event.provider","type":"keyword","normalization":"","example":"kernel","description":"Source of the event."},{"field":"event.reason","type":"keyword","normalization":"","example":"Terminated an unexpected process","description":"Reason why this event happened, according to the source"},{"field":"event.reference","type":"keyword","normalization":"","example":"https://system.example.com/event/#0001234","description":"Event reference URL"},{"field":"event.risk_score","type":"float","normalization":"","example":"","description":"Risk score or priority of the event (e.g. security solutions). Use your system's original value here."},{"field":"event.risk_score_norm","type":"float","normalization":"","example":"","description":"Normalized risk score or priority of the event (0-100)."},{"field":"event.sequence","type":"long","normalization":"","example":"","description":"Sequence number of the event."},{"field":"event.severity","type":"long","normalization":"","example":7,"description":"Numeric severity of the event."},{"field":"event.start","type":"date","normalization":"","example":"","description":"event.start contains the date when the event started or when the activity was first observed."},{"field":"event.timezone","type":"keyword","normalization":"","example":"","description":"Event time zone."},{"field":"event.type","type":"keyword","normalization":"array","example":"","description":"Event type. The third categorization field in the hierarchy."},{"field":"event.url","type":"keyword","normalization":"","example":"https://mysystem.example.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe","description":"Event investigation URL"},{"field":"file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"file.pe.product","type":"keyword","normalization":"","example":"Microsoft® Windows® Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) codes"},{"field":"file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.not_after","type":"date","normalization":"","example":"2020-07-16 03:15:39+00:00","description":"Time at which the certificate is no longer considered valid."},{"field":"file.x509.not_before","type":"date","normalization":"","example":"2019-08-16 01:40:25+00:00","description":"Time at which the certificate is first considered valid."},{"field":"file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) code"},{"field":"file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"host.cpu.usage","type":"scaled_float","normalization":"","example":"","description":"Percent CPU used, between 0 and 1."},{"field":"host.disk.read.bytes","type":"long","normalization":"","example":"","description":"The number of bytes read by all disks."},{"field":"host.disk.write.bytes","type":"long","normalization":"","example":"","description":"The number of bytes written on all disks."},{"field":"host.domain","type":"keyword","normalization":"","example":"CONTOSO","description":"Name of the directory the group is a member of."},{"field":"host.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"host.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"host.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"host.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"host.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"host.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"host.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"host.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"host.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"host.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"host.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"host.name","type":"keyword","normalization":"","example":"","description":"Name of the host."},{"field":"host.network.egress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes sent on all network interfaces."},{"field":"host.network.egress.packets","type":"long","normalization":"","example":"","description":"The number of packets sent on all network interfaces."},{"field":"host.network.ingress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes received on all network interfaces."},{"field":"host.network.ingress.packets","type":"long","normalization":"","example":"","description":"The number of packets received on all network interfaces."},{"field":"host.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"host.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"host.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"host.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"host.type","type":"keyword","normalization":"","example":"","description":"Type of host."},{"field":"host.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the host has been up."},{"field":"host.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"host.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"host.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"host.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"host.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"host.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"host.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"host.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"host.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"host.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"host.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"host.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"http.request.body.bytes","type":"long","normalization":"","example":887,"description":"Size in bytes of the request body."},{"field":"http.request.body.content","type":"wildcard","normalization":"","example":"Hello world","description":"The full HTTP request body."},{"field":"http.request.body.content.text","type":"match_only_text","normalization":"","example":"Hello world","description":"The full HTTP request body."},{"field":"http.request.bytes","type":"long","normalization":"","example":1437,"description":"Total size in bytes of the request (body and headers)."},{"field":"http.request.id","type":"keyword","normalization":"","example":"123e4567-e89b-12d3-a456-426614174000","description":"HTTP request ID."},{"field":"http.request.method","type":"keyword","normalization":"","example":"GET, POST, PUT, PoST","description":"HTTP request method."},{"field":"http.request.mime_type","type":"keyword","normalization":"","example":"image/gif","description":"Mime type of the body of the request."},{"field":"http.request.referrer","type":"keyword","normalization":"","example":"https://blog.example.com/","description":"Referrer for this HTTP request."},{"field":"http.response.body.bytes","type":"long","normalization":"","example":887,"description":"Size in bytes of the response body."},{"field":"http.response.body.content","type":"wildcard","normalization":"","example":"Hello world","description":"The full HTTP response body."},{"field":"http.response.body.content.text","type":"match_only_text","normalization":"","example":"Hello world","description":"The full HTTP response body."},{"field":"http.response.bytes","type":"long","normalization":"","example":1437,"description":"Total size in bytes of the response (body and headers)."},{"field":"http.response.mime_type","type":"keyword","normalization":"","example":"image/gif","description":"Mime type of the body of the response."},{"field":"http.response.status_code","type":"long","normalization":"","example":404,"description":"HTTP response status code."},{"field":"http.version","type":"keyword","normalization":"","example":1.1,"description":"HTTP version."},{"field":"log.file.path","type":"keyword","normalization":"","example":"/var/log/fun-times.log","description":"Full path to the log file this event came from."},{"field":"log.level","type":"keyword","normalization":"","example":"error","description":"Log level of the log event."},{"field":"log.logger","type":"keyword","normalization":"","example":"org.elasticsearch.bootstrap.Bootstrap","description":"Name of the logger."},{"field":"log.origin.file.line","type":"integer","normalization":"","example":42,"description":"The line number of the file which originated the log event."},{"field":"log.origin.file.name","type":"keyword","normalization":"","example":"Bootstrap.java","description":"The code file which originated the log event."},{"field":"log.origin.function","type":"keyword","normalization":"","example":"init","description":"The function which originated the log event."},{"field":"log.original","type":"keyword","normalization":"","example":"Sep 19 08:26:10 localhost My log","description":"Deprecated original log message with light interpretation only (encoding, newlines)."},{"field":"log.syslog","type":"object","normalization":"","example":"","description":"Syslog metadata"},{"field":"log.syslog.facility.code","type":"long","normalization":"","example":23,"description":"Syslog numeric facility of the event."},{"field":"log.syslog.facility.name","type":"keyword","normalization":"","example":"local7","description":"Syslog text-based facility of the event."},{"field":"log.syslog.priority","type":"long","normalization":"","example":135,"description":"Syslog priority of the event."},{"field":"log.syslog.severity.code","type":"long","normalization":"","example":3,"description":"Syslog numeric severity of the event."},{"field":"log.syslog.severity.name","type":"keyword","normalization":"","example":"Error","description":"Syslog text-based severity of the event."},{"field":"network.application","type":"keyword","normalization":"","example":"aim","description":"Application level protocol name."},{"field":"network.bytes","type":"long","normalization":"","example":368,"description":"Total bytes transferred in both directions."},{"field":"network.community_id","type":"keyword","normalization":"","example":"1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=","description":"A hash of source and destination IPs and ports."},{"field":"network.direction","type":"keyword","normalization":"","example":"inbound","description":"Direction of the network traffic."},{"field":"network.forwarded_ip","type":"ip","normalization":"","example":"192.1.1.2","description":"Host IP address when the source IP address is the proxy."},{"field":"network.iana_number","type":"keyword","normalization":"","example":6,"description":"IANA Protocol Number."},{"field":"network.inner","type":"object","normalization":"","example":"","description":"Inner VLAN tag information"},{"field":"network.inner.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"network.inner.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"network.name","type":"keyword","normalization":"","example":"Guest Wifi","description":"Name given by operators to sections of their network."},{"field":"network.packets","type":"long","normalization":"","example":24,"description":"Total packets transferred in both directions."},{"field":"network.protocol","type":"keyword","normalization":"","example":"http","description":"L7 Network protocol name."},{"field":"network.transport","type":"keyword","normalization":"","example":"tcp","description":"Protocol Name corresponding to the field `iana_number`."},{"field":"network.type","type":"keyword","normalization":"","example":"ipv4","description":"In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc"},{"field":"network.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"network.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress","type":"object","normalization":"","example":"","description":"Object field for egress information"},{"field":"observer.egress.interface.alias","type":"keyword","normalization":"","example":"outside","description":"Interface alias"},{"field":"observer.egress.interface.id","type":"keyword","normalization":"","example":10,"description":"Interface ID"},{"field":"observer.egress.interface.name","type":"keyword","normalization":"","example":"eth0","description":"Interface name"},{"field":"observer.egress.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"observer.egress.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress.zone","type":"keyword","normalization":"","example":"Public_Internet","description":"Observer Egress zone"},{"field":"observer.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"observer.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"observer.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"observer.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"observer.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"observer.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"observer.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"observer.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"observer.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"observer.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"observer.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"observer.hostname","type":"keyword","normalization":"","example":"","description":"Hostname of the observer."},{"field":"observer.ingress","type":"object","normalization":"","example":"","description":"Object field for ingress information"},{"field":"observer.ingress.interface.alias","type":"keyword","normalization":"","example":"outside","description":"Interface alias"},{"field":"observer.ingress.interface.id","type":"keyword","normalization":"","example":10,"description":"Interface ID"},{"field":"observer.ingress.interface.name","type":"keyword","normalization":"","example":"eth0","description":"Interface name"},{"field":"observer.ingress.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"observer.ingress.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.ingress.zone","type":"keyword","normalization":"","example":"DMZ","description":"Observer ingress zone"},{"field":"observer.ip","type":"ip","normalization":"array","example":"","description":"IP addresses of the observer."},{"field":"observer.mac","type":"keyword","normalization":"array","example":["00-00-5E-00-53-23","00-00-5E-00-53-24"],"description":"MAC addresses of the observer."},{"field":"observer.name","type":"keyword","normalization":"","example":"1_proxySG","description":"Custom name of the observer."},{"field":"observer.os.family","type":"keyword","normalization":"","example":"debian","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"observer.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"observer.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"observer.os.kernel","type":"keyword","normalization":"","example":"4.4.0-112-generic","description":"Operating system kernel version as a raw string."},{"field":"observer.os.name","type":"keyword","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"observer.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"observer.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"observer.os.type","type":"keyword","normalization":"","example":"macos","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"observer.os.version","type":"keyword","normalization":"","example":"10.14.1","description":"Operating system version as a raw string."},{"field":"observer.product","type":"keyword","normalization":"","example":"s200","description":"The product name of the observer."},{"field":"observer.serial_number","type":"keyword","normalization":"","example":"","description":"Observer serial number."},{"field":"observer.type","type":"keyword","normalization":"","example":"firewall","description":"The type of the observer the data is coming from."},{"field":"observer.vendor","type":"keyword","normalization":"","example":"Symantec","description":"Vendor name of the observer."},{"field":"observer.version","type":"keyword","normalization":"","example":"","description":"Observer version."},{"field":"orchestrator.api_version","type":"keyword","normalization":"","example":"v1beta1","description":"API version being used to carry out the action"},{"field":"orchestrator.cluster.name","type":"keyword","normalization":"","example":"","description":"Name of the cluster."},{"field":"orchestrator.cluster.url","type":"keyword","normalization":"","example":"","description":"URL of the API used to manage the cluster."},{"field":"orchestrator.cluster.version","type":"keyword","normalization":"","example":"","description":"The version of the cluster."},{"field":"orchestrator.namespace","type":"keyword","normalization":"","example":"kube-system","description":"Namespace in which the action is taking place."},{"field":"orchestrator.organization","type":"keyword","normalization":"","example":"elastic","description":"Organization affected by the event (for multi-tenant orchestrator setups)."},{"field":"orchestrator.resource.name","type":"keyword","normalization":"","example":"test-pod-cdcws","description":"Name of the resource being acted upon."},{"field":"orchestrator.resource.type","type":"keyword","normalization":"","example":"service","description":"Type of resource being acted upon."},{"field":"orchestrator.type","type":"keyword","normalization":"","example":"kubernetes","description":"Orchestrator cluster type (e.g. kubernetes, nomad or cloudfoundry)."},{"field":"organization.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the organization."},{"field":"organization.name","type":"keyword","normalization":"","example":"","description":"Organization name."},{"field":"organization.name.text","type":"match_only_text","normalization":"","example":"","description":"Organization name."},{"field":"package.architecture","type":"keyword","normalization":"","example":"x86_64","description":"Package architecture."},{"field":"package.build_version","type":"keyword","normalization":"","example":"36f4f7e89dd61b0988b12ee000b98966867710cd","description":"Build version information"},{"field":"package.checksum","type":"keyword","normalization":"","example":"68b329da9893e34099c7d8ad5cb9c940","description":"Checksum of the installed package for verification."},{"field":"package.description","type":"keyword","normalization":"","example":"Open source programming language to build simple/reliable/efficient software.","description":"Description of the package."},{"field":"package.install_scope","type":"keyword","normalization":"","example":"global","description":"Indicating how the package was installed, e.g. user-local, global."},{"field":"package.installed","type":"date","normalization":"","example":"","description":"Time when package was installed."},{"field":"package.license","type":"keyword","normalization":"","example":"Apache License 2.0","description":"Package license"},{"field":"package.name","type":"keyword","normalization":"","example":"go","description":"Package name"},{"field":"package.path","type":"keyword","normalization":"","example":"/usr/local/Cellar/go/1.12.9/","description":"Path where the package is installed."},{"field":"package.reference","type":"keyword","normalization":"","example":"https://golang.org","description":"Package home page or reference URL"},{"field":"package.size","type":"long","normalization":"","example":62231,"description":"Package size in bytes."},{"field":"package.type","type":"keyword","normalization":"","example":"rpm","description":"Package type"},{"field":"package.version","type":"keyword","normalization":"","example":"1.12.9","description":"Package version"},{"field":"process.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"process.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"process.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"process.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"process.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"process.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"process.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"process.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"process.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"process.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"process.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"process.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"process.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"process.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"process.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"process.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"process.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"process.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"process.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"process.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"process.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"process.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"process.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"process.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"process.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"process.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"process.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"process.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"process.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"process.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"process.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"process.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"process.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"process.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"process.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"process.end","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process ended."},{"field":"process.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.exit_code","type":"long","normalization":"","example":137,"description":"The exit code of the process."},{"field":"process.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"process.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"process.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"process.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"process.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"process.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.parent.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.parent.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"process.parent.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"process.parent.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"process.parent.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"process.parent.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"process.parent.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"process.parent.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"process.parent.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"process.parent.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.parent.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.parent.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.parent.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"process.parent.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"process.parent.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"process.parent.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"process.parent.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"process.parent.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.parent.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"process.parent.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"process.parent.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"process.parent.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"process.parent.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.parent.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"process.parent.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"process.parent.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"process.parent.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"process.parent.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"process.parent.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"process.parent.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"process.parent.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"process.parent.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"process.parent.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"process.parent.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"process.parent.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"process.parent.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"process.parent.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"process.parent.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"process.parent.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"process.parent.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"process.parent.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"process.parent.end","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process ended."},{"field":"process.parent.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.parent.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.parent.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.parent.exit_code","type":"long","normalization":"","example":137,"description":"The exit code of the process."},{"field":"process.parent.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"process.parent.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"process.parent.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"process.parent.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"process.parent.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"process.parent.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"process.parent.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"process.parent.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"process.parent.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"process.parent.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"process.parent.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"process.parent.pe.product","type":"keyword","normalization":"","example":"Microsoft® Windows® Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"process.parent.pgid","type":"long","normalization":"","example":"","description":"Identifier of the group of processes the process belongs to."},{"field":"process.parent.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.parent.ppid","type":"long","normalization":"","example":4241,"description":"Parent process' pid."},{"field":"process.parent.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.parent.thread.id","type":"long","normalization":"","example":4242,"description":"Thread ID."},{"field":"process.parent.thread.name","type":"keyword","normalization":"","example":"thread-0","description":"Thread name."},{"field":"process.parent.title","type":"keyword","normalization":"","example":"","description":"Process title."},{"field":"process.parent.title.text","type":"match_only_text","normalization":"","example":"","description":"Process title."},{"field":"process.parent.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the process has been up."},{"field":"process.parent.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.parent.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"process.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"process.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"process.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"process.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"process.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"process.pe.product","type":"keyword","normalization":"","example":"Microsoft® Windows® Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"process.pgid","type":"long","normalization":"","example":"","description":"Identifier of the group of processes the process belongs to."},{"field":"process.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.ppid","type":"long","normalization":"","example":4241,"description":"Parent process' pid."},{"field":"process.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.thread.id","type":"long","normalization":"","example":4242,"description":"Thread ID."},{"field":"process.thread.name","type":"keyword","normalization":"","example":"thread-0","description":"Thread name."},{"field":"process.title","type":"keyword","normalization":"","example":"","description":"Process title."},{"field":"process.title.text","type":"match_only_text","normalization":"","example":"","description":"Process title."},{"field":"process.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the process has been up."},{"field":"process.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"related.hash","type":"keyword","normalization":"array","example":"","description":"All the hashes seen on your event."},{"field":"related.hosts","type":"keyword","normalization":"array","example":"","description":"All the host identifiers seen on your event."},{"field":"related.ip","type":"ip","normalization":"array","example":"","description":"All of the IPs seen on your event."},{"field":"related.user","type":"keyword","normalization":"array","example":"","description":"All the user names or other user identifiers seen on the event."},{"field":"rule.author","type":"keyword","normalization":"array","example":["Star-Lord"],"description":"Rule author"},{"field":"rule.category","type":"keyword","normalization":"","example":"Attempted Information Leak","description":"Rule category"},{"field":"rule.description","type":"keyword","normalization":"","example":"Block requests to public DNS over HTTPS / TLS protocols","description":"Rule description"},{"field":"rule.id","type":"keyword","normalization":"","example":101,"description":"Rule ID"},{"field":"rule.license","type":"keyword","normalization":"","example":"Apache 2.0","description":"Rule license"},{"field":"rule.name","type":"keyword","normalization":"","example":"BLOCK_DNS_over_TLS","description":"Rule name"},{"field":"rule.reference","type":"keyword","normalization":"","example":"https://en.wikipedia.org/wiki/DNS_over_TLS","description":"Rule reference URL"},{"field":"rule.ruleset","type":"keyword","normalization":"","example":"Standard_Protocol_Filters","description":"Rule ruleset"},{"field":"rule.uuid","type":"keyword","normalization":"","example":1100110011,"description":"Rule UUID"},{"field":"rule.version","type":"keyword","normalization":"","example":1.1,"description":"Rule version"},{"field":"server.address","type":"keyword","normalization":"","example":"","description":"Server network address."},{"field":"server.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"server.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"server.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"server.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the server to the client."},{"field":"server.domain","type":"keyword","normalization":"","example":"","description":"Server domain."},{"field":"server.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"server.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"server.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"server.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"server.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"server.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"server.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"server.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"server.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"server.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"server.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"server.ip","type":"ip","normalization":"","example":"","description":"IP address of the server."},{"field":"server.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the server."},{"field":"server.nat.ip","type":"ip","normalization":"","example":"","description":"Server NAT ip"},{"field":"server.nat.port","type":"long","normalization":"","example":"","description":"Server NAT port"},{"field":"server.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the server to the client."},{"field":"server.port","type":"long","normalization":"","example":"","description":"Port of the server."},{"field":"server.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered server domain, stripped of the subdomain."},{"field":"server.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"server.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"server.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"server.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"server.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"server.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"server.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"server.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"server.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"server.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"server.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"server.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"server.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"server.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"service.address","type":"keyword","normalization":"","example":"172.26.0.2:5432","description":"Address of this service."},{"field":"service.environment","type":"keyword","normalization":"","example":"production","description":"Environment of the service."},{"field":"service.ephemeral_id","type":"keyword","normalization":"","example":"8a4f500f","description":"Ephemeral identifier of this service."},{"field":"service.id","type":"keyword","normalization":"","example":"d37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6","description":"Unique identifier of the running service."},{"field":"service.name","type":"keyword","normalization":"","example":"elasticsearch-metrics","description":"Name of the service."},{"field":"service.node.name","type":"keyword","normalization":"","example":"instance-0000000016","description":"Name of the service node."},{"field":"service.state","type":"keyword","normalization":"","example":"","description":"Current state of the service."},{"field":"service.type","type":"keyword","normalization":"","example":"elasticsearch","description":"The type of the service."},{"field":"service.version","type":"keyword","normalization":"","example":"3.2.4","description":"Version of the service."},{"field":"source.address","type":"keyword","normalization":"","example":"","description":"Source network address."},{"field":"source.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"source.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"source.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"source.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the source to the destination."},{"field":"source.domain","type":"keyword","normalization":"","example":"","description":"Source domain."},{"field":"source.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"source.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"source.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"source.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"source.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"source.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"source.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"source.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"source.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"source.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"source.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"source.ip","type":"ip","normalization":"","example":"","description":"IP address of the source."},{"field":"source.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the source."},{"field":"source.nat.ip","type":"ip","normalization":"","example":"","description":"Source NAT ip"},{"field":"source.nat.port","type":"long","normalization":"","example":"","description":"Source NAT port"},{"field":"source.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the source to the destination."},{"field":"source.port","type":"long","normalization":"","example":"","description":"Port of the source."},{"field":"source.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered source domain, stripped of the subdomain."},{"field":"source.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"source.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"source.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"source.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"source.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"source.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"source.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"source.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"source.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"source.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"source.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"source.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"source.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"source.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"span.id","type":"keyword","normalization":"","example":"3ff9a8981b7ccd5a","description":"Unique identifier of the span within the scope of its trace."},{"field":"threat.enrichments","type":"nested","normalization":"array","example":"","description":"List of objects containing indicators enriching the event."},{"field":"threat.enrichments.indicator","type":"object","normalization":"","example":"","description":"Object containing indicators enriching the event."},{"field":"threat.enrichments.indicator.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"threat.enrichments.indicator.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.enrichments.indicator.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.enrichments.indicator.confidence","type":"keyword","normalization":"","example":"High","description":"Indicator confidence rating"},{"field":"threat.enrichments.indicator.description","type":"keyword","normalization":"","example":"IP x.x.x.x was observed delivering the Angler EK.","description":"Indicator description"},{"field":"threat.enrichments.indicator.email.address","type":"keyword","normalization":"","example":"phish@example.com","description":"Indicator email address"},{"field":"threat.enrichments.indicator.file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"threat.enrichments.indicator.file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"threat.enrichments.indicator.file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"threat.enrichments.indicator.file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"threat.enrichments.indicator.file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"threat.enrichments.indicator.file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"threat.enrichments.indicator.file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"threat.enrichments.indicator.file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.enrichments.indicator.file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"threat.enrichments.indicator.file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"threat.enrichments.indicator.file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"threat.enrichments.indicator.file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"threat.enrichments.indicator.file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"threat.enrichments.indicator.file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"threat.enrichments.indicator.file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"threat.enrichments.indicator.file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"threat.enrichments.indicator.file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.enrichments.indicator.file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"threat.enrichments.indicator.file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.enrichments.indicator.file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"threat.enrichments.indicator.file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"threat.enrichments.indicator.file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"threat.enrichments.indicator.file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"threat.enrichments.indicator.file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"threat.enrichments.indicator.file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"threat.enrichments.indicator.file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"threat.enrichments.indicator.file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"threat.enrichments.indicator.file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"threat.enrichments.indicator.file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"threat.enrichments.indicator.file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"threat.enrichments.indicator.file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"threat.enrichments.indicator.file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.enrichments.indicator.file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"threat.enrichments.indicator.file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"threat.enrichments.indicator.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"threat.enrichments.indicator.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"threat.enrichments.indicator.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"threat.enrichments.indicator.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"threat.enrichments.indicator.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"threat.enrichments.indicator.file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"threat.enrichments.indicator.file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.enrichments.indicator.file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"threat.enrichments.indicator.file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"threat.enrichments.indicator.file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"threat.enrichments.indicator.file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"threat.enrichments.indicator.file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"threat.enrichments.indicator.file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"threat.enrichments.indicator.file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"threat.enrichments.indicator.file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.product","type":"keyword","normalization":"","example":"Microsoft® Windows® Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"threat.enrichments.indicator.file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"threat.enrichments.indicator.file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.enrichments.indicator.file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) codes"},{"field":"threat.enrichments.indicator.file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.not_after","type":"date","normalization":"","example":"2020-07-16 03:15:39+00:00","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.file.x509.not_before","type":"date","normalization":"","example":"2019-08-16 01:40:25+00:00","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) code"},{"field":"threat.enrichments.indicator.file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.enrichments.indicator.first_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was first reported."},{"field":"threat.enrichments.indicator.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"threat.enrichments.indicator.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"threat.enrichments.indicator.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"threat.enrichments.indicator.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"threat.enrichments.indicator.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"threat.enrichments.indicator.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"threat.enrichments.indicator.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"threat.enrichments.indicator.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"threat.enrichments.indicator.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"threat.enrichments.indicator.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"threat.enrichments.indicator.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"threat.enrichments.indicator.ip","type":"ip","normalization":"","example":"1.2.3.4","description":"Indicator IP address"},{"field":"threat.enrichments.indicator.last_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last reported."},{"field":"threat.enrichments.indicator.marking.tlp","type":"keyword","normalization":"","example":"White","description":"Indicator TLP marking"},{"field":"threat.enrichments.indicator.modified_at","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last updated."},{"field":"threat.enrichments.indicator.port","type":"long","normalization":"","example":443,"description":"Indicator port"},{"field":"threat.enrichments.indicator.provider","type":"keyword","normalization":"","example":"lrz_urlhaus","description":"Indicator provider"},{"field":"threat.enrichments.indicator.reference","type":"keyword","normalization":"","example":"https://system.example.com/indicator/0001234","description":"Indicator reference URL"},{"field":"threat.enrichments.indicator.registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"threat.enrichments.indicator.registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"threat.enrichments.indicator.registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"threat.enrichments.indicator.registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"threat.enrichments.indicator.registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"threat.enrichments.indicator.registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"threat.enrichments.indicator.registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"threat.enrichments.indicator.scanner_stats","type":"long","normalization":"","example":4,"description":"Scanner statistics"},{"field":"threat.enrichments.indicator.sightings","type":"long","normalization":"","example":20,"description":"Number of times indicator observed"},{"field":"threat.enrichments.indicator.type","type":"keyword","normalization":"","example":"ipv4-addr","description":"Type of indicator"},{"field":"threat.enrichments.indicator.url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"threat.enrichments.indicator.url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.enrichments.indicator.url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"threat.enrichments.indicator.url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"threat.enrichments.indicator.url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"threat.enrichments.indicator.url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"threat.enrichments.indicator.url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"threat.enrichments.indicator.url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.enrichments.indicator.url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"threat.enrichments.indicator.url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"threat.enrichments.indicator.url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.enrichments.indicator.url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"threat.enrichments.indicator.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) codes"},{"field":"threat.enrichments.indicator.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.not_after","type":"date","normalization":"","example":"2020-07-16 03:15:39+00:00","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.x509.not_before","type":"date","normalization":"","example":"2019-08-16 01:40:25+00:00","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) code"},{"field":"threat.enrichments.indicator.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.enrichments.matched.atomic","type":"keyword","normalization":"","example":"bad-domain.com","description":"Matched indicator value"},{"field":"threat.enrichments.matched.field","type":"keyword","normalization":"","example":"file.hash.sha256","description":"Matched indicator field"},{"field":"threat.enrichments.matched.id","type":"keyword","normalization":"","example":"ff93aee5-86a1-4a61-b0e6-0cdc313d01b5","description":"Matched indicator identifier"},{"field":"threat.enrichments.matched.index","type":"keyword","normalization":"","example":"filebeat-8.0.0-2021.05.23-000011","description":"Matched indicator index"},{"field":"threat.enrichments.matched.type","type":"keyword","normalization":"","example":"indicator_match_rule","description":"Type of indicator match"},{"field":"threat.framework","type":"keyword","normalization":"","example":"MITRE ATT&CK","description":"Threat classification framework."},{"field":"threat.group.alias","type":"keyword","normalization":"array","example":["Magecart Group 6"],"description":"Alias of the group."},{"field":"threat.group.id","type":"keyword","normalization":"","example":"G0037","description":"ID of the group."},{"field":"threat.group.name","type":"keyword","normalization":"","example":"FIN6","description":"Name of the group."},{"field":"threat.group.reference","type":"keyword","normalization":"","example":"https://attack.mitre.org/groups/G0037/","description":"Reference URL of the group."},{"field":"threat.indicator.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"threat.indicator.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.indicator.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.indicator.confidence","type":"keyword","normalization":"","example":"High","description":"Indicator confidence rating"},{"field":"threat.indicator.description","type":"keyword","normalization":"","example":"IP x.x.x.x was observed delivering the Angler EK.","description":"Indicator description"},{"field":"threat.indicator.email.address","type":"keyword","normalization":"","example":"phish@example.com","description":"Indicator email address"},{"field":"threat.indicator.file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"threat.indicator.file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"threat.indicator.file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"threat.indicator.file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"threat.indicator.file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"threat.indicator.file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"threat.indicator.file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"threat.indicator.file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"threat.indicator.file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.indicator.file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"threat.indicator.file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"threat.indicator.file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"threat.indicator.file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"threat.indicator.file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"threat.indicator.file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"threat.indicator.file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"threat.indicator.file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"threat.indicator.file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"threat.indicator.file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"threat.indicator.file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.indicator.file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"threat.indicator.file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"threat.indicator.file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"threat.indicator.file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"threat.indicator.file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.indicator.file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"threat.indicator.file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"threat.indicator.file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"threat.indicator.file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"threat.indicator.file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"threat.indicator.file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"threat.indicator.file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"threat.indicator.file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"threat.indicator.file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"threat.indicator.file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"threat.indicator.file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"threat.indicator.file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"threat.indicator.file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"threat.indicator.file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"threat.indicator.file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"threat.indicator.file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"threat.indicator.file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"threat.indicator.file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"threat.indicator.file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"threat.indicator.file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.indicator.file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"threat.indicator.file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"threat.indicator.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"threat.indicator.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"threat.indicator.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"threat.indicator.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"threat.indicator.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"threat.indicator.file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"threat.indicator.file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.indicator.file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"threat.indicator.file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"threat.indicator.file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"threat.indicator.file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"threat.indicator.file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"threat.indicator.file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"threat.indicator.file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"threat.indicator.file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.product","type":"keyword","normalization":"","example":"Microsoft® Windows® Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.indicator.file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"threat.indicator.file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.indicator.file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.indicator.file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"threat.indicator.file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.indicator.file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) codes"},{"field":"threat.indicator.file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.not_after","type":"date","normalization":"","example":"2020-07-16 03:15:39+00:00","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.file.x509.not_before","type":"date","normalization":"","example":"2019-08-16 01:40:25+00:00","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.indicator.file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.indicator.file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) code"},{"field":"threat.indicator.file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.indicator.file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.indicator.first_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was first reported."},{"field":"threat.indicator.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"threat.indicator.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"threat.indicator.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"threat.indicator.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"threat.indicator.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"threat.indicator.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"threat.indicator.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"threat.indicator.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"threat.indicator.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"threat.indicator.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"threat.indicator.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"threat.indicator.ip","type":"ip","normalization":"","example":"1.2.3.4","description":"Indicator IP address"},{"field":"threat.indicator.last_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last reported."},{"field":"threat.indicator.marking.tlp","type":"keyword","normalization":"","example":"WHITE","description":"Indicator TLP marking"},{"field":"threat.indicator.modified_at","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last updated."},{"field":"threat.indicator.port","type":"long","normalization":"","example":443,"description":"Indicator port"},{"field":"threat.indicator.provider","type":"keyword","normalization":"","example":"lrz_urlhaus","description":"Indicator provider"},{"field":"threat.indicator.reference","type":"keyword","normalization":"","example":"https://system.example.com/indicator/0001234","description":"Indicator reference URL"},{"field":"threat.indicator.registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"threat.indicator.registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"threat.indicator.registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"threat.indicator.registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"threat.indicator.registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"threat.indicator.registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"threat.indicator.registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"threat.indicator.scanner_stats","type":"long","normalization":"","example":4,"description":"Scanner statistics"},{"field":"threat.indicator.sightings","type":"long","normalization":"","example":20,"description":"Number of times indicator observed"},{"field":"threat.indicator.type","type":"keyword","normalization":"","example":"ipv4-addr","description":"Type of indicator"},{"field":"threat.indicator.url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"threat.indicator.url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.indicator.url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"threat.indicator.url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.indicator.url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.indicator.url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"threat.indicator.url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"threat.indicator.url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"threat.indicator.url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"threat.indicator.url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.indicator.url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"threat.indicator.url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"threat.indicator.url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.indicator.url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"threat.indicator.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) codes"},{"field":"threat.indicator.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.indicator.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.not_after","type":"date","normalization":"","example":"2020-07-16 03:15:39+00:00","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.x509.not_before","type":"date","normalization":"","example":"2019-08-16 01:40:25+00:00","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.indicator.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.indicator.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) code"},{"field":"threat.indicator.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.indicator.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.indicator.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.software.alias","type":"keyword","normalization":"array","example":["X-Agent"],"description":"Alias of the software"},{"field":"threat.software.id","type":"keyword","normalization":"","example":"S0552","description":"ID of the software"},{"field":"threat.software.name","type":"keyword","normalization":"","example":"AdFind","description":"Name of the software."},{"field":"threat.software.platforms","type":"keyword","normalization":"array","example":["Windows"],"description":"Platforms of the software."},{"field":"threat.software.reference","type":"keyword","normalization":"","example":"https://attack.mitre.org/software/S0552/","description":"Software reference URL."},{"field":"threat.software.type","type":"keyword","normalization":"","example":"Tool","description":"Software type."},{"field":"threat.tactic.id","type":"keyword","normalization":"array","example":"TA0002","description":"Threat tactic id."},{"field":"threat.tactic.name","type":"keyword","normalization":"array","example":"Execution","description":"Threat tactic."},{"field":"threat.tactic.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/tactics/TA0002/","description":"Threat tactic URL reference."},{"field":"threat.technique.id","type":"keyword","normalization":"array","example":"T1059","description":"Threat technique id."},{"field":"threat.technique.name","type":"keyword","normalization":"array","example":"Command and Scripting Interpreter","description":"Threat technique name."},{"field":"threat.technique.name.text","type":"match_only_text","normalization":"","example":"Command and Scripting Interpreter","description":"Threat technique name."},{"field":"threat.technique.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/techniques/T1059/","description":"Threat technique URL reference."},{"field":"threat.technique.subtechnique.id","type":"keyword","normalization":"array","example":"T1059.001","description":"Threat subtechnique id."},{"field":"threat.technique.subtechnique.name","type":"keyword","normalization":"array","example":"PowerShell","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.name.text","type":"match_only_text","normalization":"","example":"PowerShell","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/techniques/T1059/001/","description":"Threat subtechnique URL reference."},{"field":"tls.cipher","type":"keyword","normalization":"","example":"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","description":"String indicating the cipher used during the current connection."},{"field":"tls.client.certificate","type":"keyword","normalization":"","example":"MII...","description":"PEM-encoded stand-alone certificate offered by the client."},{"field":"tls.client.certificate_chain","type":"keyword","normalization":"array","example":["MII...","MII..."],"description":"Array of PEM-encoded certificates that make up the certificate chain offered by the client."},{"field":"tls.client.hash.md5","type":"keyword","normalization":"","example":"0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha1","type":"keyword","normalization":"","example":"9E393D93138888D288266C2D915214D1D1CCEB2A","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha256","type":"keyword","normalization":"","example":"0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.issuer","type":"keyword","normalization":"","example":"CN=Example Root CA, OU=Infrastructure Team, DC=example, DC=com","description":"Distinguished name of subject of the issuer of the x.509 certificate presented by the client."},{"field":"tls.client.ja3","type":"keyword","normalization":"","example":"d4e5b18d6b55c71272893221c96ba240","description":"A hash that identifies clients based on how they perform an SSL/TLS handshake."},{"field":"tls.client.not_after","type":"date","normalization":"","example":"2021-01-01T00:00:00.000Z","description":"Date/Time indicating when client certificate is no longer considered valid."},{"field":"tls.client.not_before","type":"date","normalization":"","example":"1970-01-01T00:00:00.000Z","description":"Date/Time indicating when client certificate is first considered valid."},{"field":"tls.client.server_name","type":"keyword","normalization":"","example":"www.elastic.co","description":"Hostname the client is trying to connect to. Also called the SNI."},{"field":"tls.client.subject","type":"keyword","normalization":"","example":"CN=myclient, OU=Documentation Team, DC=example, DC=com","description":"Distinguished name of subject of the x.509 certificate presented by the client."},{"field":"tls.client.supported_ciphers","type":"keyword","normalization":"array","example":["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","..."],"description":"Array of ciphers offered by the client during the client hello."},{"field":"tls.client.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"tls.client.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) codes"},{"field":"tls.client.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"tls.client.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.client.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.client.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.not_after","type":"date","normalization":"","example":"2020-07-16 03:15:39+00:00","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.client.x509.not_before","type":"date","normalization":"","example":"2019-08-16 01:40:25+00:00","description":"Time at which the certificate is first considered valid."},{"field":"tls.client.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"tls.client.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.client.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.client.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"tls.client.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"tls.client.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"tls.client.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"tls.client.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) code"},{"field":"tls.client.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.client.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"tls.client.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"tls.client.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"tls.client.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"tls.curve","type":"keyword","normalization":"","example":"secp256r1","description":"String indicating the curve used for the given cipher, when applicable."},{"field":"tls.established","type":"boolean","normalization":"","example":"","description":"Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel."},{"field":"tls.next_protocol","type":"keyword","normalization":"","example":"http/1.1","description":"String indicating the protocol being tunneled."},{"field":"tls.resumed","type":"boolean","normalization":"","example":"","description":"Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation."},{"field":"tls.server.certificate","type":"keyword","normalization":"","example":"MII...","description":"PEM-encoded stand-alone certificate offered by the server."},{"field":"tls.server.certificate_chain","type":"keyword","normalization":"array","example":["MII...","MII..."],"description":"Array of PEM-encoded certificates that make up the certificate chain offered by the server."},{"field":"tls.server.hash.md5","type":"keyword","normalization":"","example":"0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha1","type":"keyword","normalization":"","example":"9E393D93138888D288266C2D915214D1D1CCEB2A","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha256","type":"keyword","normalization":"","example":"0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.issuer","type":"keyword","normalization":"","example":"CN=Example Root CA, OU=Infrastructure Team, DC=example, DC=com","description":"Subject of the issuer of the x.509 certificate presented by the server."},{"field":"tls.server.ja3s","type":"keyword","normalization":"","example":"394441ab65754e2207b1e1b457b3641d","description":"A hash that identifies servers based on how they perform an SSL/TLS handshake."},{"field":"tls.server.not_after","type":"date","normalization":"","example":"2021-01-01T00:00:00.000Z","description":"Timestamp indicating when server certificate is no longer considered valid."},{"field":"tls.server.not_before","type":"date","normalization":"","example":"1970-01-01T00:00:00.000Z","description":"Timestamp indicating when server certificate is first considered valid."},{"field":"tls.server.subject","type":"keyword","normalization":"","example":"CN=www.example.com, OU=Infrastructure Team, DC=example, DC=com","description":"Subject of the x.509 certificate presented by the server."},{"field":"tls.server.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"tls.server.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) codes"},{"field":"tls.server.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"tls.server.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.server.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.server.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.not_after","type":"date","normalization":"","example":"2020-07-16 03:15:39+00:00","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.server.x509.not_before","type":"date","normalization":"","example":"2019-08-16 01:40:25+00:00","description":"Time at which the certificate is first considered valid."},{"field":"tls.server.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"tls.server.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.server.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.server.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"tls.server.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"tls.server.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"tls.server.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"tls.server.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country (C) code"},{"field":"tls.server.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.server.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"tls.server.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"tls.server.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"tls.server.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"tls.version","type":"keyword","normalization":"","example":1.2,"description":"Numeric part of the version parsed from the original string."},{"field":"tls.version_protocol","type":"keyword","normalization":"","example":"tls","description":"Normalized lowercase protocol name parsed from original string."},{"field":"trace.id","type":"keyword","normalization":"","example":"4bf92f3577b34da6a3ce929d0e0e4736","description":"Unique identifier of the trace."},{"field":"transaction.id","type":"keyword","normalization":"","example":"00f067aa0ba902b7","description":"Unique identifier of the transaction within the scope of its trace."},{"field":"url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"user.changes.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.changes.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.changes.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.changes.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.changes.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.changes.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.changes.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.changes.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.changes.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.changes.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.changes.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.changes.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.effective.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.effective.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.effective.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.effective.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.effective.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.effective.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.effective.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.effective.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.effective.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.effective.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.effective.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.effective.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.target.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.target.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.target.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.target.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.target.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.target.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.target.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.target.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.target.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.target.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.target.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.target.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user_agent.device.name","type":"keyword","normalization":"","example":"iPhone","description":"Name of the device."},{"field":"user_agent.name","type":"keyword","normalization":"","example":"Safari","description":"Name of the user agent."},{"field":"user_agent.original","type":"keyword","normalization":"","example":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1","description":"Unparsed user_agent string."},{"field":"user_agent.original.text","type":"match_only_text","normalization":"","example":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1","description":"Unparsed user_agent string."},{"field":"user_agent.os.family","type":"keyword","normalization":"","example":"debian","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"user_agent.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.kernel","type":"keyword","normalization":"","example":"4.4.0-112-generic","description":"Operating system kernel version as a raw string."},{"field":"user_agent.os.name","type":"keyword","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"user_agent.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"user_agent.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"user_agent.os.type","type":"keyword","normalization":"","example":"macos","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"user_agent.os.version","type":"keyword","normalization":"","example":"10.14.1","description":"Operating system version as a raw string."},{"field":"user_agent.version","type":"keyword","normalization":"","example":12,"description":"Version of the user agent."},{"field":"vulnerability.category","type":"keyword","normalization":"array","example":["Firewall"],"description":"Category of a vulnerability."},{"field":"vulnerability.classification","type":"keyword","normalization":"","example":"CVSS","description":"Classification of the vulnerability."},{"field":"vulnerability.description","type":"keyword","normalization":"","example":"In macOS before 2.12.6, there is a vulnerability in the RPC...","description":"Description of the vulnerability."},{"field":"vulnerability.description.text","type":"match_only_text","normalization":"","example":"In macOS before 2.12.6, there is a vulnerability in the RPC...","description":"Description of the vulnerability."},{"field":"vulnerability.enumeration","type":"keyword","normalization":"","example":"CVE","description":"Identifier of the vulnerability."},{"field":"vulnerability.id","type":"keyword","normalization":"","example":"CVE-2019-00001","description":"ID of the vulnerability."},{"field":"vulnerability.reference","type":"keyword","normalization":"","example":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111","description":"Reference of the vulnerability."},{"field":"vulnerability.report_id","type":"keyword","normalization":"","example":20191018.0001,"description":"Scan identification number."},{"field":"vulnerability.scanner.vendor","type":"keyword","normalization":"","example":"Tenable","description":"Name of the scanner vendor."},{"field":"vulnerability.score.base","type":"float","normalization":"","example":5.5,"description":"Vulnerability Base score."},{"field":"vulnerability.score.environmental","type":"float","normalization":"","example":5.5,"description":"Vulnerability Environmental score."},{"field":"vulnerability.score.temporal","type":"float","normalization":"","example":"","description":"Vulnerability Temporal score."},{"field":"vulnerability.score.version","type":"keyword","normalization":"","example":2,"description":"CVSS version."},{"field":"vulnerability.severity","type":"keyword","normalization":"","example":"Critical","description":"Severity of the vulnerability."}] \ No newline at end of file diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx index ea1f9698795aac..f4c805d3753511 100644 --- a/x-pack/plugins/osquery/public/components/app.tsx +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -24,7 +24,7 @@ const OsqueryAppComponent = () => { const section = useMemo(() => location.pathname.split('/')[1] ?? 'overview', [location.pathname]); const { data: osqueryIntegration, isFetched } = useOsqueryIntegrationStatus(); - if (isFetched && osqueryIntegration.install_status !== 'installed') { + if (isFetched && osqueryIntegration?.install_status !== 'installed') { return ; } diff --git a/x-pack/plugins/osquery/public/components/layouts/index.tsx b/x-pack/plugins/osquery/public/components/layouts/index.tsx index e9d1987592eb9d..116291e5431298 100644 --- a/x-pack/plugins/osquery/public/components/layouts/index.tsx +++ b/x-pack/plugins/osquery/public/components/layouts/index.tsx @@ -8,5 +8,6 @@ // copied from x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx export { Container, Nav, Wrapper } from './default'; -export { WithHeaderLayout, WithHeaderLayoutProps } from './with_header'; +export type { WithHeaderLayoutProps } from './with_header'; +export { WithHeaderLayout } from './with_header'; export { WithoutHeaderLayout } from './without_header'; diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_custom_button_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_custom_button_extension.tsx index 67791cb34e6839..c3770f202c087e 100644 --- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_custom_button_extension.tsx +++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_custom_button_extension.tsx @@ -24,8 +24,8 @@ export const OsqueryManagedCustomButtonExtension = React.memo { const fetchStatus = () => { - http.get('/internal/osquery/status').then((response) => { - setDisabled(response.install_status !== 'installed'); + http.get<{ install_status: string }>('/internal/osquery/status').then((response) => { + setDisabled(response?.install_status !== 'installed'); }); }; fetchStatus(); diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx index 4ac20e6144c08c..c2ac84ce191da4 100644 --- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx +++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx @@ -165,16 +165,6 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo< defaultValue: { config: JSON.stringify(get(newPolicy, 'inputs[0].config.osquery.value', {}), null, 2), }, - serializer: (formData) => { - let config; - try { - // @ts-expect-error update types - config = JSON.parse(formData.config); - } catch (e) { - config = {}; - } - return { config }; - }, schema: { config: { label: i18n.translate('xpack.osquery.fleetIntegration.osqueryConfig.configFieldLabel', { @@ -243,10 +233,16 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo< if (isValid === undefined) return; const updatedPolicy = produce(newPolicy, (draft) => { - if (isEmpty(config)) { + let parsedConfig; + try { + parsedConfig = JSON.parse(config); + // eslint-disable-next-line no-empty + } catch (e) {} + + if (isEmpty(parsedConfig)) { unset(draft, 'inputs[0].config'); } else { - set(draft, 'inputs[0].config.osquery.value', config); + set(draft, 'inputs[0].config.osquery.value', parsedConfig); } return draft; }); @@ -261,11 +257,14 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo< if (editMode && policyAgentsCount === null) { const fetchAgentsCount = async () => { try { - const response = await http.fetch(agentRouteService.getStatusPath(), { - query: { - policyId: policy?.policy_id, - }, - }); + const response = await http.fetch<{ results: { total: number } }>( + agentRouteService.getStatusPath(), + { + query: { + policyId: policy?.policy_id, + }, + } + ); if (response.results) { setPolicyAgentsCount(response.results.total); } @@ -276,7 +275,7 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo< const fetchAgentPolicyDetails = async () => { if (policy?.policy_id) { try { - const response = await http.fetch( + const response = await http.fetch<{ item: AgentPolicy }>( agentPolicyRouteService.getInfoPath(policy?.policy_id) ); if (response.item) { diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 6d13c76d9d5928..0c0151b36203c3 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -86,7 +86,7 @@ const LiveQueryFormComponent: React.FC = ({ const { data, isLoading, mutateAsync, isError, isSuccess } = useMutation( (payload: Record) => - http.post('/internal/osquery/action', { + http.post('/internal/osquery/action', { body: JSON.stringify(payload), }), { diff --git a/x-pack/plugins/osquery/public/packs/form/index.tsx b/x-pack/plugins/osquery/public/packs/form/index.tsx index f20a26f2791ddc..1930227c2dc9ef 100644 --- a/x-pack/plugins/osquery/public/packs/form/index.tsx +++ b/x-pack/plugins/osquery/public/packs/form/index.tsx @@ -98,14 +98,17 @@ const PackFormComponent: React.FC = ({ defaultValue, editMode = f description: { type: FIELD_TYPES.TEXT, label: i18n.translate('xpack.osquery.pack.form.descriptionFieldLabel', { - defaultMessage: 'Description', + defaultMessage: 'Description (optional)', }), }, policy_ids: { defaultValue: [], type: FIELD_TYPES.COMBO_BOX, label: i18n.translate('xpack.osquery.pack.form.agentPoliciesFieldLabel', { - defaultMessage: 'Agent policies', + defaultMessage: 'Agent policies (optional)', + }), + helpText: i18n.translate('xpack.osquery.pack.form.agentPoliciesFieldHelpText', { + defaultMessage: 'Queries in this pack are scheduled for agents in the selected policies.', }), }, enabled: { diff --git a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx index a32f369922958b..0b661c61a9057c 100644 --- a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx @@ -22,6 +22,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate, FormattedTime, FormattedRelative } from '@kbn/i18n/react'; +import moment from 'moment-timezone'; import { TypedLensByValueInput, @@ -29,7 +30,7 @@ import { PieVisualizationState, } from '../../../lens/public'; import { FilterStateStore, IndexPattern } from '../../../../../src/plugins/data/common'; -import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana'; +import { useKibana } from '../common/lib/kibana'; import { OsqueryManagerPackagePolicyInputStream } from '../../common/types'; import { ScheduledQueryErrorsTable } from './scheduled_query_errors_table'; import { usePackQueryLastResults } from './use_pack_query_last_results'; @@ -207,8 +208,6 @@ const ViewResultsInLensActionComponent: React.FC { - const openInNewTab = !(!isModifiedEvent(event) && isLeftClickEvent(event)); - event.preventDefault(); lensService?.navigateToPrefilledEditor( @@ -222,7 +221,7 @@ const ViewResultsInLensActionComponent: React.FC + {VIEW_IN_DISCOVER} ); @@ -378,6 +377,7 @@ interface ScheduledQueryLastResultsProps { actionId: string; queryId: string; interval: number; + logsIndexPattern: IndexPattern | undefined; toggleErrors: (payload: { queryId: string; interval: number }) => void; expanded: boolean; } @@ -386,12 +386,10 @@ const ScheduledQueryLastResults: React.FC = ({ actionId, queryId, interval, + logsIndexPattern, toggleErrors, expanded, }) => { - const data = useKibana().services.data; - const [logsIndexPattern, setLogsIndexPattern] = useState(undefined); - const { data: lastResultsData, isFetched } = usePackQueryLastResults({ actionId, interval, @@ -409,15 +407,6 @@ const ScheduledQueryLastResults: React.FC = ({ [queryId, interval, toggleErrors] ); - useEffect(() => { - const fetchLogsIndexPattern = async () => { - const indexPattern = await data.indexPatterns.find('logs-*'); - - setLogsIndexPattern(indexPattern[0]); - }; - fetchLogsIndexPattern(); - }, [data.indexPatterns]); - if (!isFetched || !errorsFetched) { return ; } @@ -518,6 +507,86 @@ const ScheduledQueryLastResults: React.FC = ({ const getPackActionId = (actionId: string, packName: string) => `pack_${packName}_${actionId}`; +interface PackViewInActionProps { + item: { + id: string; + interval: number; + }; + logsIndexPattern: IndexPattern | undefined; + packName: string; + agentIds?: string[]; +} + +const PackViewInDiscoverActionComponent: React.FC = ({ + item, + logsIndexPattern, + packName, + agentIds, +}) => { + const { id, interval } = item; + const actionId = getPackActionId(id, packName); + const { data: lastResultsData } = usePackQueryLastResults({ + actionId, + interval, + logsIndexPattern, + }); + + const startDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).subtract(interval, 'seconds').toISOString() + : `now-${interval}s`; + const endDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).toISOString() + : 'now'; + + return ( + + ); +}; + +const PackViewInDiscoverAction = React.memo(PackViewInDiscoverActionComponent); + +const PackViewInLensActionComponent: React.FC = ({ + item, + logsIndexPattern, + packName, + agentIds, +}) => { + const { id, interval } = item; + const actionId = getPackActionId(id, packName); + const { data: lastResultsData } = usePackQueryLastResults({ + actionId, + interval, + logsIndexPattern, + }); + + const startDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).subtract(interval, 'seconds').toISOString() + : `now-${interval}s`; + const endDate = lastResultsData?.['@timestamp'] + ? moment(lastResultsData?.['@timestamp'][0]).toISOString() + : 'now'; + + return ( + + ); +}; + +const PackViewInLensAction = React.memo(PackViewInLensActionComponent); + interface PackQueriesStatusTableProps { agentIds?: string[]; data: OsqueryManagerPackagePolicyInputStream[]; @@ -533,6 +602,18 @@ const PackQueriesStatusTableComponent: React.FC = ( Record> >({}); + const indexPatterns = useKibana().services.data.indexPatterns; + const [logsIndexPattern, setLogsIndexPattern] = useState(undefined); + + useEffect(() => { + const fetchLogsIndexPattern = async () => { + const indexPattern = await indexPatterns.find('logs-*'); + + setLogsIndexPattern(indexPattern[0]); + }; + fetchLogsIndexPattern(); + }, [indexPatterns]); + const renderQueryColumn = useCallback( (query: string) => ( @@ -564,6 +645,7 @@ const PackQueriesStatusTableComponent: React.FC = ( const renderLastResultsColumn = useCallback( (item) => ( = ( expanded={!!itemIdToExpandedRowMap[item.id]} /> ), - [itemIdToExpandedRowMap, packName, toggleErrors] + [itemIdToExpandedRowMap, packName, toggleErrors, logsIndexPattern] ); const renderDiscoverResultsAction = useCallback( (item) => ( - ), - [agentIds, packName] + [agentIds, logsIndexPattern, packName] ); const renderLensResultsAction = useCallback( (item) => ( - ), - [agentIds, packName] + [agentIds, logsIndexPattern, packName] ); const getItemId = useCallback( diff --git a/x-pack/plugins/osquery/public/packs/packs_table.tsx b/x-pack/plugins/osquery/public/packs/packs_table.tsx index 3d4efd88b789fa..dcca0e2f56596f 100644 --- a/x-pack/plugins/osquery/public/packs/packs_table.tsx +++ b/x-pack/plugins/osquery/public/packs/packs_table.tsx @@ -126,7 +126,7 @@ const PacksTableComponent = () => { { field: 'policy_ids', name: i18n.translate('xpack.osquery.packs.table.policyColumnTitle', { - defaultMessage: 'Policies', + defaultMessage: 'Scheduled policies', }), truncateText: true, render: renderAgentPolicy, diff --git a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx index f6967f26cfbc22..85f4b3b3f0fade 100644 --- a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx @@ -30,6 +30,7 @@ import { EuiTitle, EuiText, EuiIcon, + EuiSuperSelect, } from '@elastic/eui'; import sqlParser from 'js-sql-parser'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -54,7 +55,9 @@ import { getUseField, fieldValidators, ValidationFuncArg, + UseMultiFields, } from '../../shared_imports'; +import { OsqueryIcon } from '../../components/osquery_icon'; export const CommonUseField = getUseField({ component: Field }); @@ -77,6 +80,35 @@ const typeMap = { constant_keyword: 'string', }; +const StyledEuiSuperSelect = styled(EuiSuperSelect)` + &.euiFormControlLayout__prepend { + padding-left: 8px; + padding-right: 24px; + box-shadow: none; + + .euiIcon { + padding: 0; + width: 18px; + background: none; + } + } +`; + +// @ts-expect-error update types +const ResultComboBox = styled(EuiComboBox)` + &.euiComboBox--prepended .euiSuperSelect { + border-right: 1px solid ${(props) => props.theme.eui.euiBorderColor}; + + .euiFormControlLayout__childrenWrapper { + border-radius: 6px 0 0 6px; + + .euiFormControlLayoutIcons--right { + right: 6px; + } + } + } +`; + const StyledFieldIcon = styled(FieldIcon)` width: 32px; @@ -90,6 +122,11 @@ const StyledFieldSpan = styled.span` padding-bottom: 0 !important; `; +// align the icon to the inputs +const StyledSemicolonWrapper = styled.div` + margin-top: 8px; +`; + // align the icon to the inputs const StyledButtonWrapper = styled.div` margin-top: 11px; @@ -115,11 +152,10 @@ interface ECSComboboxFieldProps { idAria?: string; } -export const ECSComboboxField: React.FC = ({ +const ECSComboboxFieldComponent: React.FC = ({ field, euiFieldProps = {}, idAria, - ...rest }) => { const { setValue } = field; const [selectedOptions, setSelected] = useState>>( @@ -179,6 +215,21 @@ export const ECSComboboxField: React.FC = ({ [selectedOptions] ); + const helpText = useMemo(() => { + // @ts-expect-error update types + let text = selectedOptions[0]?.value?.description; + + if (!text) return; + + // @ts-expect-error update types + const example = selectedOptions[0]?.value?.example; + if (example) { + text += ` e.g. ${JSON.stringify(example)}`; + } + + return text; + }, [selectedOptions]); + useEffect(() => { // @ts-expect-error update types setSelected(() => { @@ -193,14 +244,12 @@ export const ECSComboboxField: React.FC = ({ return ( = ({ ); }; +export const ECSComboboxField = React.memo(ECSComboboxFieldComponent); + +const OSQUERY_COLUMN_VALUE_TYPE_OPTIONS = [ + { + value: 'field', + inputDisplay: , + dropdownDisplay: ( + + + + + + + + + + + ), + }, + { + value: 'value', + inputDisplay: , + dropdownDisplay: ( + + + + + + + + + + + ), + }, +]; + interface OsqueryColumnFieldProps { - field: FieldHook; + resultType: FieldHook; + resultValue: FieldHook; euiFieldProps: EuiComboBoxProps; idAria?: string; } -export const OsqueryColumnField: React.FC = ({ - field, +const OsqueryColumnFieldComponent: React.FC = ({ + resultType, + resultValue, euiFieldProps = {}, idAria, - ...rest }) => { - const { setValue } = field; - const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const { setValue } = resultValue; + const { setValue: setType } = resultType; + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(resultValue); const describedByIds = useMemo(() => (idAria ? [idAria] : []), [idAria]); const [selectedOptions, setSelected] = useState< Array> @@ -269,19 +363,51 @@ export const OsqueryColumnField: React.FC = ({ [setValue, setSelected] ); + const onTypeChange = useCallback( + (newType) => { + if (newType !== resultType.value) { + setType(newType); + } + }, + [setType, resultType.value] + ); + + const handleCreateOption = useCallback( + (newOption) => { + setValue(newOption); + }, + [setValue] + ); + + const Prepend = useMemo( + () => ( + + ), + [onTypeChange, resultType.value] + ); + useEffect(() => { setSelected(() => { - if (!field.value.length) return []; + if (!resultValue.value.length) return []; - const selectedOption = find(euiFieldProps?.options, ['label', field.value]); + const selectedOption = find(euiFieldProps?.options, ['label', resultValue.value]); - return selectedOption ? [selectedOption] : [{ label: field.value }]; + return selectedOption ? [selectedOption] : [{ label: resultValue.value }]; }); - }, [euiFieldProps?.options, setSelected, field.value]); + }, [euiFieldProps?.options, setSelected, resultValue.value]); return ( = ({ fullWidth describedByIds={describedByIds} isDisabled={euiFieldProps.isDisabled} - {...rest} > - = ({ ); }; +export const OsqueryColumnField = React.memo( + OsqueryColumnFieldComponent, + (prevProps, nextProps) => + prevProps.resultType.value === nextProps.resultType.value && + prevProps.resultType.isChangingValue === nextProps.resultType.isChangingValue && + prevProps.resultType.errors === nextProps.resultType.errors && + prevProps.resultValue.value === nextProps.resultValue.value && + prevProps.resultValue.isChangingValue === nextProps.resultValue.isChangingValue && + prevProps.resultValue.errors === nextProps.resultValue.errors && + deepEqual(prevProps.euiFieldProps, nextProps.euiFieldProps) +); + export interface ECSMappingEditorFieldRef { validate: () => Promise< | Record< @@ -344,7 +483,7 @@ const getEcsFieldValidator = )(args); // @ts-expect-error update types - if (fieldRequiredError && ((!editForm && args.formData['value.field'].length) || editForm)) { + if (fieldRequiredError && ((!editForm && args.formData['result.value'].length) || editForm)) { return fieldRequiredError; } @@ -354,7 +493,7 @@ const getEcsFieldValidator = const getOsqueryResultFieldValidator = (osquerySchemaOptions: OsquerySchemaOption[], editForm: boolean) => ( - args: ValidationFuncArg + args: ValidationFuncArg ) => { const fieldRequiredError = fieldValidators.emptyField( i18n.translate('xpack.osquery.pack.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage', { @@ -366,7 +505,8 @@ const getOsqueryResultFieldValidator = return fieldRequiredError; } - if (!args.value.length) return; + // @ts-expect-error update types + if (!args.value?.length || args.formData['result.type'] !== 'field') return; const osqueryColumnExists = find(osquerySchemaOptions, ['label', args.value]); @@ -383,6 +523,7 @@ const getOsqueryResultFieldValidator = }, } ), + __isBlocking__: false, } : undefined; }; @@ -395,7 +536,8 @@ const FORM_DEFAULT_VALUE = { interface ECSMappingEditorFormData { key: string; value: { - field: string; + field?: string; + value?: string; }; } @@ -413,27 +555,44 @@ export const ECSMappingEditorForm = forwardRef ({ + key: data.key ?? '', + result: { + type: data.value + ? Object.keys(data.value)[0] + : OSQUERY_COLUMN_VALUE_TYPE_OPTIONS[0].value, + value: data.value ? Object.values(data.value)[0] : '', + }, + }), }); const { submit, reset, validate, __validateFields } = form; @@ -442,17 +601,25 @@ export const ECSMappingEditorForm = forwardRef { validate(); - __validateFields(['value.field']); + __validateFields(['result.value']); const { data, isValid } = await submit(); if (isValid) { + const serializedData = { + key: data.key, + value: { + [data.result.type]: data.result.value, + }, + }; if (onAdd) { - onAdd(data); + onAdd(serializedData); + } + if (onChange) { + onChange(serializedData); } reset(); } - return { data, isValid }; - }, [validate, __validateFields, submit, onAdd, reset]); + }, [validate, __validateFields, submit, onAdd, onChange, reset]); const handleDeleteClick = useCallback(() => { if (defaultValue?.key && onDelete) { @@ -460,6 +627,37 @@ export const ECSMappingEditorForm = forwardRef ( + + {(fields) => ( + + )} + + ), + [osquerySchemaOptions, isDisabled] + ); + + const ecsComboBoxEuiFieldProps = useMemo(() => ({ isDisabled }), [isDisabled]); + useImperativeHandle( ref, () => ({ @@ -468,35 +666,37 @@ export const ECSMappingEditorForm = forwardRef { - if (onAdd && !deepEqual(formData, currentFormData.current)) { + if (!deepEqual(formData, currentFormData.current)) { currentFormData.current = formData; handleSubmit(); } }, [handleSubmit, formData, onAdd]); - useEffect(() => { - if (onChange && !deepEqual(formData, currentFormData.current)) { - currentFormData.current = formData; - onChange(formData); - } - }, [defaultValue, formData, handleDeleteClick, onChange]); - - useEffect(() => { - if (defaultValue) { - validate(); - __validateFields(['value.field']); - } - }, [defaultValue, osquerySchemaOptions, validate, __validateFields]); + // useEffect(() => { + // if (defaultValue) { + // validate(); + // __validateFields(['result.value']); + // } + // }, [defaultValue, osquerySchemaOptions, validate, __validateFields]); return (
@@ -507,30 +707,19 @@ export const ECSMappingEditorForm = forwardRef - - - + + : + - - - + {MultiFields} {!isDisabled && ( @@ -578,179 +767,175 @@ interface OsqueryColumn { index: boolean; } -export const ECSMappingEditorField = ({ - field, - query, - fieldRef, - euiFieldProps, -}: ECSMappingEditorFieldProps) => { - const { setValue, value = {} } = field; - const [osquerySchemaOptions, setOsquerySchemaOptions] = useState([]); - const formRefs = useRef>({}); - - useImperativeHandle( - fieldRef, - () => ({ - validate: async () => { - const validations = await Promise.all( - Object.values(formRefs.current).map(async (formRef) => { - const { data, isValid } = await formRef.validate(); - return [data, isValid]; - }) - ); +export const ECSMappingEditorField = React.memo( + ({ field, query, fieldRef, euiFieldProps }: ECSMappingEditorFieldProps) => { + const { setValue, value = {} } = field; + const [osquerySchemaOptions, setOsquerySchemaOptions] = useState([]); + const formRefs = useRef>({}); - if (find(validations, (result) => result[1] === false)) { - return false; - } - - return deepmerge.all(map(validations, '[0]')); - }, - }), - [] - ); + useImperativeHandle( + fieldRef, + () => ({ + validate: async () => { + const validations = await Promise.all( + Object.values(formRefs.current).map(async (formRef) => { + const { data, isValid } = await formRef.validate(); + return [data, isValid]; + }) + ); + + if (find(validations, (result) => result[1] === false)) { + return false; + } - useEffect(() => { - setOsquerySchemaOptions((currentValue) => { - if (!query?.length) { - return currentValue; - } + return deepmerge.all(map(validations, '[0]')); + }, + }), + [] + ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let ast: Record | undefined; + useEffect(() => { + setOsquerySchemaOptions((currentValue) => { + if (!query?.length) { + return currentValue; + } - try { - ast = sqlParser.parse(query)?.value; - } catch (e) { - return currentValue; - } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let ast: Record | undefined; - const astOsqueryTables: Record< - string, - { - columns: OsqueryColumn[]; - order: number; + try { + ast = sqlParser.parse(query)?.value; + } catch (e) { + return currentValue; } - > = - ast?.from?.value?.reduce( - ( - acc: { - [x: string]: { - columns: OsqueryColumn[]; - order: number; - }; - }, - table: { - value: { - left?: { value: { value: string }; alias?: { value: string } }; - right?: { value: { value: string }; alias?: { value: string } }; - value?: { value: string }; - alias?: { value: string }; - }; - } - ) => { - each(['value.left', 'value.right', 'value'], (valueKey) => { - if (valueKey) { - const osqueryTable = find(osquerySchema, [ - 'name', - get(table, `${valueKey}.value.value`), - ]); - - if (osqueryTable) { - acc[ - get(table, `${valueKey}.alias.value`) ?? get(table, `${valueKey}.value.value`) - ] = { - columns: osqueryTable.columns, - order: Object.keys(acc).length, - }; - } + + const astOsqueryTables: Record< + string, + { + columns: OsqueryColumn[]; + order: number; + } + > = + ast?.from?.value?.reduce( + ( + acc: { + [x: string]: { + columns: OsqueryColumn[]; + order: number; + }; + }, + table: { + value: { + left?: { value: { value: string }; alias?: { value: string } }; + right?: { value: { value: string }; alias?: { value: string } }; + value?: { value: string }; + alias?: { value: string }; + }; } - }); + ) => { + each(['value.left', 'value.right', 'value'], (valueKey) => { + if (valueKey) { + const osqueryTable = find(osquerySchema, [ + 'name', + get(table, `${valueKey}.value.value`), + ]); + + if (osqueryTable) { + acc[ + get(table, `${valueKey}.alias.value`) ?? get(table, `${valueKey}.value.value`) + ] = { + columns: osqueryTable.columns, + order: Object.keys(acc).length, + }; + } + } + }); - return acc; - }, - {} - ) ?? {}; + return acc; + }, + {} + ) ?? {}; - // Table doesn't exist in osquery schema - if (isEmpty(astOsqueryTables)) { - return currentValue; - } + // Table doesn't exist in osquery schema + if (isEmpty(astOsqueryTables)) { + return currentValue; + } - const suggestions = - isArray(ast?.selectItems?.value) && - ast?.selectItems?.value - ?.map((selectItem: { type: string; value: string; hasAs: boolean; alias?: string }) => { - if (selectItem.type === 'Identifier') { - /* + const suggestions = + isArray(ast?.selectItems?.value) && + ast?.selectItems?.value + ?.map((selectItem: { type: string; value: string; hasAs: boolean; alias?: string }) => { + if (selectItem.type === 'Identifier') { + /* select * from routes, uptime; */ - if (ast?.selectItems?.value.length === 1 && selectItem.value === '*') { - return reduce( - astOsqueryTables, - (acc, { columns: osqueryColumns, order: tableOrder }, table) => { - acc.push( - ...osqueryColumns.map((osqueryColumn) => ({ - label: osqueryColumn.name, - value: { - name: osqueryColumn.name, - description: osqueryColumn.description, - table, - tableOrder, - suggestion_label: osqueryColumn.name, - }, - })) - ); - return acc; - }, - [] as OsquerySchemaOption[] - ); - } + if (ast?.selectItems?.value.length === 1 && selectItem.value === '*') { + return reduce( + astOsqueryTables, + (acc, { columns: osqueryColumns, order: tableOrder }, table) => { + acc.push( + ...osqueryColumns.map((osqueryColumn) => ({ + label: osqueryColumn.name, + value: { + name: osqueryColumn.name, + description: osqueryColumn.description, + table, + tableOrder, + suggestion_label: osqueryColumn.name, + }, + })) + ); + return acc; + }, + [] as OsquerySchemaOption[] + ); + } - /* + /* select i.*, p.resident_size, p.user_time, p.system_time, time.minutes as counter from osquery_info i, processes p, time where p.pid = i.pid; */ - const [table, column] = selectItem.value.includes('.') - ? selectItem.value?.split('.') - : [Object.keys(astOsqueryTables)[0], selectItem.value]; - - if (column === '*' && astOsqueryTables[table]) { - const { columns: osqueryColumns, order: tableOrder } = astOsqueryTables[table]; - return osqueryColumns.map((osqueryColumn) => ({ - label: osqueryColumn.name, - value: { - name: osqueryColumn.name, - description: osqueryColumn.description, - table, - tableOrder, - suggestion_label: `${osqueryColumn.name}`, - }, - })); - } + const [table, column] = selectItem.value.includes('.') + ? selectItem.value?.split('.') + : [Object.keys(astOsqueryTables)[0], selectItem.value]; + + if (column === '*' && astOsqueryTables[table]) { + const { columns: osqueryColumns, order: tableOrder } = astOsqueryTables[table]; + return osqueryColumns.map((osqueryColumn) => ({ + label: osqueryColumn.name, + value: { + name: osqueryColumn.name, + description: osqueryColumn.description, + table, + tableOrder, + suggestion_label: `${osqueryColumn.name}`, + }, + })); + } + + if (astOsqueryTables[table]) { + const osqueryColumn = find(astOsqueryTables[table].columns, ['name', column]); + + if (osqueryColumn) { + const label = selectItem.hasAs ? selectItem.alias : column; - if (astOsqueryTables[table]) { - const osqueryColumn = find(astOsqueryTables[table].columns, ['name', column]); - - if (osqueryColumn) { - const label = selectItem.hasAs ? selectItem.alias : column; - - return [ - { - label, - value: { - name: osqueryColumn.name, - description: osqueryColumn.description, - table, - tableOrder: astOsqueryTables[table].order, - suggestion_label: `${label}`, + return [ + { + label, + value: { + name: osqueryColumn.name, + description: osqueryColumn.description, + table, + tableOrder: astOsqueryTables[table].order, + suggestion_label: `${label}`, + }, }, - }, - ]; + ]; + } } } - } - /* + /* SELECT pid, uid, name, ROUND(( (user_time + system_time) / (cpu_time.tsb - cpu_time.itsb) ) * 100, 2) AS percentage @@ -764,161 +949,166 @@ export const ECSMappingEditorField = ({ LIMIT 5; */ - if (selectItem.hasAs && selectItem.alias) { - return [ - { - label: selectItem.alias, - value: { - name: selectItem.alias, - description: '', - table: '', - tableOrder: -1, - suggestion_label: selectItem.alias, + if (selectItem.hasAs && selectItem.alias) { + return [ + { + label: selectItem.alias, + value: { + name: selectItem.alias, + description: '', + table: '', + tableOrder: -1, + suggestion_label: selectItem.alias, + }, }, - }, - ]; - } + ]; + } - return []; - }) - .flat(); + return []; + }) + .flat(); - // Remove column duplicates by keeping the column from the table that appears last in the query - return sortedUniqBy( - orderBy(suggestions, ['value.suggestion_label', 'value.tableOrder'], ['asc', 'desc']), - 'label' - ); - }); - }, [query]); - - const handleAddRow = useCallback( - (newRow) => { - if (newRow?.key && newRow?.value) { - setValue( - produce((draft) => { - draft[newRow.key] = newRow.value; - return draft; - }) + // Remove column duplicates by keeping the column from the table that appears last in the query + return sortedUniqBy( + orderBy(suggestions, ['value.suggestion_label', 'value.tableOrder'], ['asc', 'desc']), + 'label' ); - } - }, - [setValue] - ); + }); + }, [query]); + + const handleAddRow = useCallback( + (newRow) => { + if (newRow?.key && newRow?.value) { + setValue( + produce((draft) => { + draft[newRow.key] = newRow.value; + return draft; + }) + ); + } + }, + [setValue] + ); - const handleUpdateRow = useCallback( - (currentKey: string) => (updatedRow: FormData) => { - if (updatedRow?.key && updatedRow?.value) { - setValue( - produce((draft) => { - if (currentKey !== updatedRow.key) { - delete draft[currentKey]; - } + const handleUpdateRow = useCallback( + (currentKey: string) => (updatedRow: FormData) => { + if (updatedRow?.key && updatedRow?.value) { + setValue( + produce((draft) => { + if (currentKey !== updatedRow.key) { + delete draft[currentKey]; + } - draft[updatedRow.key] = updatedRow.value; + draft[updatedRow.key] = updatedRow.value; - return draft; - }) - ); - } - }, - [setValue] - ); + return draft; + }) + ); + } + }, + [setValue] + ); - const handleDeleteRow = useCallback( - (key) => { - if (key) { - setValue( - produce((draft) => { - if (draft[key]) { - delete draft[key]; - } - return draft; - }) - ); + const handleDeleteRow = useCallback( + (key) => { + if (key) { + setValue( + produce((draft) => { + if (draft[key]) { + delete draft[key]; + } + return draft; + }) + ); - if (formRefs.current[key]) { - delete formRefs.current[key]; + if (formRefs.current[key]) { + delete formRefs.current[key]; + } } - } - }, - [setValue] - ); + }, + [setValue] + ); - return ( - <> - - - -
+ return ( + <> + + + +
+ +
+
+ -
-
- - - -
-
- - - - - - - - - - - - - - - {Object.entries(value).map(([ecsKey, ecsValue]) => ( - +
+
+ + + + + + + + + + + + + + + {Object.entries(value).map(([ecsKey, ecsValue]) => ( + { - if (formRef) { - formRefs.current[ecsKey] = formRef; - } - }} - key={ecsKey} - osquerySchemaOptions={osquerySchemaOptions} - // eslint-disable-next-line react-perf/jsx-no-new-object-as-prop - defaultValue={{ - key: ecsKey, - value: ecsValue, - }} - onChange={handleUpdateRow(ecsKey)} - onDelete={handleDeleteRow} - isDisabled={!!euiFieldProps?.isDisabled} - /> - ))} - {!euiFieldProps?.isDisabled && ( - + ))} + {!euiFieldProps?.isDisabled && ( + { - if (formRef) { - formRefs.current.new = formRef; - } - }} - osquerySchemaOptions={osquerySchemaOptions} - onAdd={handleAddRow} - /> - )} - - ); -}; + if (formRef) { + formRefs.current.new = formRef; + } + }} + osquerySchemaOptions={osquerySchemaOptions} + onAdd={handleAddRow} + /> + )} + + ); + }, + (prevProps, nextProps) => + prevProps.field.value === nextProps.field.value && + prevProps.query === nextProps.query && + deepEqual(prevProps.euiFieldProps, nextProps.euiFieldProps) +); // eslint-disable-next-line import/no-default-export export default ECSMappingEditorField; diff --git a/x-pack/plugins/osquery/public/packs/use_create_pack.ts b/x-pack/plugins/osquery/public/packs/use_create_pack.ts index 05756afde40d83..41104361cc4a3b 100644 --- a/x-pack/plugins/osquery/public/packs/use_create_pack.ts +++ b/x-pack/plugins/osquery/public/packs/use_create_pack.ts @@ -29,7 +29,7 @@ export const useCreatePack = ({ withRedirect }: UseCreatePackProps) => { return useMutation( (payload) => - http.post('/internal/osquery/packs', { + http.post('/internal/osquery/packs', { body: JSON.stringify(payload), }), { diff --git a/x-pack/plugins/osquery/public/packs/use_pack_query_last_results.ts b/x-pack/plugins/osquery/public/packs/use_pack_query_last_results.ts index af3e5b23e80f80..cb84386dbe3eab 100644 --- a/x-pack/plugins/osquery/public/packs/use_pack_query_last_results.ts +++ b/x-pack/plugins/osquery/public/packs/use_pack_query_last_results.ts @@ -6,6 +6,7 @@ */ import { useQuery } from 'react-query'; +import moment from 'moment-timezone'; import { IndexPattern } from '../../../../../src/plugins/data/common'; import { useKibana } from '../common/lib/kibana'; @@ -46,13 +47,12 @@ export const usePackQueryLastResults = ({ }); const lastResultsResponse = await lastResultsSearchSource.fetch$().toPromise(); + const timestamp = lastResultsResponse.rawResponse?.hits?.hits[0]?.fields?.['@timestamp'][0]; - const responseId = lastResultsResponse.rawResponse?.hits?.hits[0]?._source?.response_id; - - if (responseId) { + if (timestamp) { const aggsSearchSource = await data.search.searchSource.create({ index: logsIndexPattern, - size: 0, + size: 1, aggs: { unique_agents: { cardinality: { field: 'agent.id' } }, }, @@ -61,13 +61,16 @@ export const usePackQueryLastResults = ({ bool: { filter: [ { - match_phrase: { - action_id: actionId, + range: { + '@timestamp': { + gte: moment(timestamp).subtract(interval, 'seconds').format(), + lte: moment(timestamp).format(), + }, }, }, { match_phrase: { - response_id: responseId, + action_id: actionId, }, }, ], @@ -81,7 +84,7 @@ export const usePackQueryLastResults = ({ '@timestamp': lastResultsResponse.rawResponse?.hits?.hits[0]?.fields?.['@timestamp'], // @ts-expect-error update types uniqueAgentsCount: aggsResponse.rawResponse.aggregations?.unique_agents?.value, - docCount: aggsResponse.rawResponse?.hits?.total, + docCount: aggsResponse?.rawResponse?.hits?.total, }; } diff --git a/x-pack/plugins/osquery/public/packs/use_packs.ts b/x-pack/plugins/osquery/public/packs/use_packs.ts index 9870cb481450f8..72dbf4115247df 100644 --- a/x-pack/plugins/osquery/public/packs/use_packs.ts +++ b/x-pack/plugins/osquery/public/packs/use_packs.ts @@ -22,7 +22,7 @@ export const usePacks = ({ return useQuery( [PACKS_ID, { pageIndex, pageSize, sortField, sortDirection }], async () => - http.get('/internal/osquery/packs', { + http.get('/internal/osquery/packs', { query: { pageIndex, pageSize, sortField, sortDirection }, }), { diff --git a/x-pack/plugins/osquery/public/packs/use_update_pack.ts b/x-pack/plugins/osquery/public/packs/use_update_pack.ts index d9aecbe9ac5982..d7a0ccf7269eab 100644 --- a/x-pack/plugins/osquery/public/packs/use_update_pack.ts +++ b/x-pack/plugins/osquery/public/packs/use_update_pack.ts @@ -32,7 +32,7 @@ export const useUpdatePack = ({ withRedirect, options }: UseUpdatePackProps) => return useMutation( // @ts-expect-error update types ({ id, ...payload }) => - http.put(`/internal/osquery/packs/${id}`, { + http.put(`/internal/osquery/packs/${id}`, { body: JSON.stringify(payload), }), { diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index 5b8143c874e2b6..d1d16730e79828 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -291,19 +291,9 @@ const ResultsTableComponent: React.FC = ({ setIsLive(() => { if (!agentIds?.length || expired) return false; - const uniqueAgentsRepliedCount = - // @ts-expect-error-type - allResultsData?.rawResponse.aggregations?.unique_agents.value ?? 0; - - return !!(uniqueAgentsRepliedCount !== agentIds?.length - aggregations.failed); + return !!(aggregations.totalResponded !== agentIds?.length); }), - [ - agentIds?.length, - aggregations.failed, - // @ts-expect-error-type - allResultsData?.rawResponse.aggregations?.unique_agents.value, - expired, - ] + [agentIds?.length, aggregations.failed, aggregations.totalResponded, expired] ); if (!hasActionResultsPrivileges) { @@ -328,7 +318,7 @@ const ResultsTableComponent: React.FC = ({ <> {isLive && } - {isFetched && !allResultsData?.edges.length ? ( + {isFetched && !allResultsData?.edges.length && !aggregations?.totalRowCount ? ( <> diff --git a/x-pack/plugins/osquery/public/routes/packs/list/index.tsx b/x-pack/plugins/osquery/public/routes/packs/list/index.tsx index 945677cade577b..6f084e9e6bf25a 100644 --- a/x-pack/plugins/osquery/public/routes/packs/list/index.tsx +++ b/x-pack/plugins/osquery/public/routes/packs/list/index.tsx @@ -27,6 +27,16 @@ const PacksPageComponent = () => {

+ + +

+ +

+
+
), [] diff --git a/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts index c736cdf9c35457..a8061fd9a5cce5 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts @@ -29,7 +29,7 @@ export const useCreateSavedQuery = ({ withRedirect }: UseCreateSavedQueryProps) return useMutation( (payload) => - http.post('/internal/osquery/saved_query', { + http.post('/internal/osquery/saved_query', { body: JSON.stringify(payload), }), { diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts index 22ed81a62a5b3d..9de40c759c2cf9 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts @@ -24,7 +24,8 @@ export const useSavedQueries = ({ return useQuery( [SAVED_QUERIES_ID, { pageIndex, pageSize, sortField, sortDirection }], () => - http.get('/internal/osquery/saved_query', { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + http.get('/internal/osquery/saved_query', { query: { pageIndex, pageSize, sortField, sortDirection }, }), { @@ -36,6 +37,7 @@ export const useSavedQueries = ({ toastMessage: error.body.message, }); }, + refetchOnWindowFocus: !!isLive, } ); }; diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts index 04d7a9b505372c..f05f38b8259ce6 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts @@ -26,9 +26,11 @@ export const useSavedQuery = ({ savedQueryId }: UseSavedQueryProps) => { return useQuery( [SAVED_QUERY_ID, { savedQueryId }], - () => http.get(`/internal/osquery/saved_query/${savedQueryId}`), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + () => http.get(`/internal/osquery/saved_query/${savedQueryId}`), { keepPreviousData: true, + refetchOnWindowFocus: false, onSuccess: (data) => { if (data.error) { setErrorToast(data.error, { diff --git a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts index b2e23163a74c81..cc35654d50d540 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts @@ -29,7 +29,7 @@ export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps) return useMutation( (payload) => - http.put(`/internal/osquery/saved_query/${savedQueryId}`, { + http.put(`/internal/osquery/saved_query/${savedQueryId}`, { body: JSON.stringify(payload), }), { diff --git a/x-pack/plugins/osquery/public/shared_imports.ts b/x-pack/plugins/osquery/public/shared_imports.ts index 8ffdc387bf76eb..227a276c41f184 100644 --- a/x-pack/plugins/osquery/public/shared_imports.ts +++ b/x-pack/plugins/osquery/public/shared_imports.ts @@ -5,27 +5,29 @@ * 2.0. */ -export { - getUseField, - getFieldValidityAndErrorMessage, +export type { FieldHook, FieldValidateResponse, - FIELD_TYPES, - Form, FormConfig, FormData, - FormDataProvider, FormHook, FormSchema, + ValidationError, + ValidationFunc, + ValidationFuncArg, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { + getUseField, + getFieldValidityAndErrorMessage, + FIELD_TYPES, + Form, + FormDataProvider, UseArray, UseField, UseMultiFields, useForm, useFormContext, useFormData, - ValidationError, - ValidationFunc, - ValidationFuncArg, VALIDATION_TYPES, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; @@ -37,4 +39,4 @@ export { JsonEditorField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers'; -export { ERROR_CODE } from '../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; +export type { ERROR_CODE } from '../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; diff --git a/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts b/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts index e53750080ef76c..07f02a892999cd 100644 --- a/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts +++ b/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts @@ -11,7 +11,7 @@ import path from 'path'; import { run } from '@kbn/dev-utils'; -const ECS_COLUMN_SCHEMA_FIELDS = ['field', 'type', 'description']; +const ECS_COLUMN_SCHEMA_FIELDS = ['field', 'type', 'normalization', 'example', 'description']; const RESTRICTED_FIELDS = [ 'agent.name', diff --git a/x-pack/plugins/osquery/server/index.ts b/x-pack/plugins/osquery/server/index.ts index 5deec805cc2824..03da89d72d7b91 100644 --- a/x-pack/plugins/osquery/server/index.ts +++ b/x-pack/plugins/osquery/server/index.ts @@ -21,4 +21,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new OsqueryPlugin(initializerContext); } -export { OsqueryPluginSetup, OsqueryPluginStart } from './types'; +export type { OsqueryPluginSetup, OsqueryPluginStart } from './types'; diff --git a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts index a633fe4923aebd..fb2c834f3c74d5 100644 --- a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts +++ b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts @@ -53,6 +53,11 @@ export const savedQueryType: SavedObjectsType = { hidden: false, namespaceType: 'multiple-isolated', mappings: savedQuerySavedObjectMappings, + management: { + defaultSearchField: 'id', + importableAndExportable: true, + getTitle: (savedObject) => savedObject.attributes.id, + }, }; export const packSavedObjectMappings: SavedObjectsType['mappings'] = { @@ -109,4 +114,9 @@ export const packType: SavedObjectsType = { hidden: false, namespaceType: 'multiple-isolated', mappings: packSavedObjectMappings, + management: { + defaultSearchField: 'name', + importableAndExportable: true, + getTitle: (savedObject) => savedObject.attributes.name, + }, }; diff --git a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts index 3308a8023dd9ea..a84ec5a262a64f 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts @@ -26,7 +26,7 @@ export const readSavedQueryRoute = (router: IRouter) => { const savedObjectsClient = context.core.savedObjects.client; const savedQuery = await savedObjectsClient.get<{ - ecs_mapping: Array<{ field: string; value: string }>; + ecs_mapping: Array<{ key: string; value: Record }>; }>(savedQuerySavedObjectType, request.params.id); if (savedQuery.attributes.ecs_mapping) { diff --git a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts index c0148087ee8c99..b34999204b8a3e 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts @@ -34,7 +34,8 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp schema.recordOf( schema.string(), schema.object({ - field: schema.string(), + field: schema.maybe(schema.string()), + value: schema.maybe(schema.string()), }) ) ), diff --git a/x-pack/plugins/osquery/server/routes/utils.ts b/x-pack/plugins/osquery/server/routes/utils.ts index 136cbc190e46c8..62464ec5d63367 100644 --- a/x-pack/plugins/osquery/server/routes/utils.ts +++ b/x-pack/plugins/osquery/server/routes/utils.ts @@ -5,22 +5,24 @@ * 2.0. */ -import { pick, reduce } from 'lodash'; +import { reduce } from 'lodash'; export const convertECSMappingToArray = (ecsMapping: Record | undefined) => ecsMapping ? Object.entries(ecsMapping).map((item) => ({ - value: item[0], - ...item[1], + key: item[0], + value: item[1], })) : undefined; -export const convertECSMappingToObject = (ecsMapping: Array<{ field: string; value: string }>) => +export const convertECSMappingToObject = ( + ecsMapping: Array<{ key: string; value: Record }> +) => reduce( ecsMapping, (acc, value) => { - acc[value.value] = pick(value, 'field'); + acc[value.key] = value.value; return acc; }, - {} as Record + {} as Record ); diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/query.action_results.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/query.action_results.dsl.ts index 109e2609119330..96d5ad60cd54cf 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/query.action_results.dsl.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/query.action_results.dsl.ts @@ -46,6 +46,11 @@ export const buildActionResultsQuery = ({ }, }, aggs: { + rows_count: { + sum: { + field: 'action_response.osquery.count', + }, + }, responses: { terms: { script: { diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx index a8ee5bc90c72e9..386565008b8ecd 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx @@ -9,7 +9,6 @@ import React, { FunctionComponent } from 'react'; import { EuiFieldText, EuiFormRow, - EuiPanel, EuiSpacer, EuiIcon, EuiToolTip, @@ -140,24 +139,22 @@ export const ContextTab: FunctionComponent = () => { } fullWidth > - - updatePayload({ query: nextQuery })} - options={{ - fontSize: 12, - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', - automaticLayout: true, - }} - /> - + updatePayload({ query: nextQuery })} + options={{ + fontSize: 12, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + }} + /> )} {['filter', 'score'].indexOf(context) !== -1 && ( @@ -179,24 +176,22 @@ export const ContextTab: FunctionComponent = () => { } fullWidth > - - updatePayload({ document: nextDocument })} - options={{ - fontSize: 12, - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', - automaticLayout: true, - }} - /> - + updatePayload({ document: nextDocument })} + options={{ + fontSize: 12, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + }} + /> )} diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx index e7a0bc560dac9c..258c6bcdb1bebd 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx @@ -11,7 +11,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, - EuiPanel, EuiTabbedContent, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -48,7 +47,7 @@ export const OutputPane: FunctionComponent = ({ isLoading, response }) => ); return ( - +
= ({ isLoading, response }) => }, ]} /> - +
); }; diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx index b749e3aa9b63d8..370d542665977b 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx @@ -6,15 +6,7 @@ */ import React, { FunctionComponent } from 'react'; -import { - EuiFormRow, - EuiPanel, - EuiSpacer, - EuiIcon, - EuiToolTip, - EuiLink, - EuiText, -} from '@elastic/eui'; +import { EuiFormRow, EuiSpacer, EuiIcon, EuiToolTip, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { monaco } from '@kbn/monaco'; import { i18n } from '@kbn/i18n'; @@ -58,31 +50,29 @@ export const ParametersTab: FunctionComponent = () => {
} > - - updatePayload({ parameters: nextParams })} - options={{ - fontSize: 12, - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', - automaticLayout: true, - }} - editorDidMount={(editor: monaco.editor.IStandaloneCodeEditor) => { - // Updating tab size for the editor - const model = editor.getModel(); - if (model) { - model.updateOptions({ tabSize: 2 }); - } - }} - /> - + updatePayload({ parameters: nextParams })} + options={{ + fontSize: 12, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + }} + editorDidMount={(editor: monaco.editor.IStandaloneCodeEditor) => { + // Updating tab size for the editor + const model = editor.getModel(); + if (model) { + model.updateOptions({ tabSize: 2 }); + } + }} + /> ); diff --git a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts index f182ff2f3302b5..11cde8ad03d575 100644 --- a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts +++ b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts @@ -31,7 +31,7 @@ export const useSubmitCode = (http: HttpSetup) => { const requestId = ++currentRequestIdRef.current; try { - const result = await http.post(`${API_BASE_PATH}/execute`, { + const result = await http.post(`${API_BASE_PATH}/execute`, { // Stringify the string, because http runs it through JSON.parse, and we want to actually // send a JSON string. body: JSON.stringify(formatRequestPayload(config, PayloadFormat.UGLY)), diff --git a/x-pack/plugins/painless_lab/public/styles/_index.scss b/x-pack/plugins/painless_lab/public/styles/_index.scss index 00197e744e95cd..78182787a63b61 100644 --- a/x-pack/plugins/painless_lab/public/styles/_index.scss +++ b/x-pack/plugins/painless_lab/public/styles/_index.scss @@ -17,11 +17,9 @@ $bottomBarHeight: $euiSize * 3; } .painlessLabRightPane { - border-right: none; - border-top: none; - border-bottom: none; - border-radius: 0; - padding-top: 0; + background-color: $euiColorEmptyShade; + padding: $euiSizeS; + border-left: $euiBorderThin; height: 100%; } diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts index cf859ff6913f59..4cfe1fb41a835d 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts @@ -7,4 +7,5 @@ export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; export { setupEnvironment } from './setup_environment'; -export { createRemoteClustersActions, RemoteClustersActions } from './remote_clusters_actions'; +export type { RemoteClustersActions } from './remote_clusters_actions'; +export { createRemoteClustersActions } from './remote_clusters_actions'; diff --git a/x-pack/plugins/remote_clusters/common/lib/index.ts b/x-pack/plugins/remote_clusters/common/lib/index.ts index b17283511b8b7c..fb1a4d33e7f7f1 100644 --- a/x-pack/plugins/remote_clusters/common/lib/index.ts +++ b/x-pack/plugins/remote_clusters/common/lib/index.ts @@ -5,10 +5,5 @@ * 2.0. */ -export { - deserializeCluster, - serializeCluster, - Cluster, - ClusterInfoEs, - ClusterPayloadEs, -} from './cluster_serialization'; +export type { Cluster, ClusterInfoEs, ClusterPayloadEs } from './cluster_serialization'; +export { deserializeCluster, serializeCluster } from './cluster_serialization'; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts index 6f3956a19f6a05..a695b95b8de859 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts @@ -10,7 +10,8 @@ export { validateProxy } from './validate_proxy'; export { validateSeeds } from './validate_seeds'; export { validateSeed } from './validate_seed'; export { validateServerName } from './validate_server_name'; -export { validateCluster, ClusterErrors } from './validate_cluster'; +export type { ClusterErrors } from './validate_cluster'; +export { validateCluster } from './validate_cluster'; export { isCloudUrlEnabled, validateCloudUrl, diff --git a/x-pack/plugins/remote_clusters/public/application/services/api_errors.ts b/x-pack/plugins/remote_clusters/public/application/services/api_errors.ts index 190c6c16e14773..1f10478bf3543a 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/api_errors.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/api_errors.ts @@ -8,7 +8,13 @@ import { IHttpFetchError } from 'kibana/public'; import { toasts, fatalError } from './notification'; -function createToastConfig(error: IHttpFetchError, errorTitle: string) { +interface CommonErrorBody { + statusCode: number; + message: string; + error: string; +} + +function createToastConfig(error: IHttpFetchError, errorTitle: string) { // Expect an error in the shape provided by http service. if (error && error.body) { const { error: errorString, statusCode, message } = error.body; @@ -19,7 +25,7 @@ function createToastConfig(error: IHttpFetchError, errorTitle: string) { } } -export function showApiWarning(error: IHttpFetchError, errorTitle: string) { +export function showApiWarning(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { @@ -31,7 +37,7 @@ export function showApiWarning(error: IHttpFetchError, errorTitle: string) { return fatalError.add(error, errorTitle); } -export function showApiError(error: IHttpFetchError, errorTitle: string) { +export function showApiError(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { diff --git a/x-pack/plugins/remote_clusters/public/application/services/index.ts b/x-pack/plugins/remote_clusters/public/application/services/index.ts index 471abaf32736ea..832d0af8103011 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/index.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/index.ts @@ -13,12 +13,7 @@ export { setBreadcrumbs } from './breadcrumb'; export { redirect } from './redirect'; -export { - setUserHasLeftApp, - getUserHasLeftApp, - registerRouter, - getRouter, - AppRouter, -} from './routing'; +export type { AppRouter } from './routing'; +export { setUserHasLeftApp, getUserHasLeftApp, registerRouter, getRouter } from './routing'; export { trackUiMetric, METRIC_TYPE } from './ui_metric'; diff --git a/x-pack/plugins/remote_clusters/public/index.ts b/x-pack/plugins/remote_clusters/public/index.ts index 18e718fabfc5de..f2f50a8825e11e 100644 --- a/x-pack/plugins/remote_clusters/public/index.ts +++ b/x-pack/plugins/remote_clusters/public/index.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext } from 'kibana/public'; import { RemoteClustersUIPlugin } from './plugin'; -export { RemoteClustersPluginSetup } from './plugin'; +export type { RemoteClustersPluginSetup } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => new RemoteClustersUIPlugin(initializerContext); diff --git a/x-pack/plugins/remote_clusters/public/types.ts b/x-pack/plugins/remote_clusters/public/types.ts index 15a34c84bb30c5..bcd162599ab773 100644 --- a/x-pack/plugins/remote_clusters/public/types.ts +++ b/x-pack/plugins/remote_clusters/public/types.ts @@ -23,6 +23,6 @@ export interface ClientConfigType { }; } -export { RegisterManagementAppArgs }; +export type { RegisterManagementAppArgs }; -export { I18nStart }; +export type { I18nStart }; diff --git a/x-pack/plugins/remote_clusters/server/index.ts b/x-pack/plugins/remote_clusters/server/index.ts index 33be5d88a3e4b3..9447f9d9209e32 100644 --- a/x-pack/plugins/remote_clusters/server/index.ts +++ b/x-pack/plugins/remote_clusters/server/index.ts @@ -9,6 +9,6 @@ import { PluginInitializerContext } from 'kibana/server'; import { RemoteClustersServerPlugin } from './plugin'; export { config } from './config'; -export { RemoteClustersPluginSetup } from './plugin'; +export type { RemoteClustersPluginSetup } from './plugin'; export const plugin = (ctx: PluginInitializerContext) => new RemoteClustersServerPlugin(ctx); diff --git a/x-pack/plugins/reporting/common/types/base.ts b/x-pack/plugins/reporting/common/types/base.ts new file mode 100644 index 00000000000000..44960c57f61c14 --- /dev/null +++ b/x-pack/plugins/reporting/common/types/base.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Ensure, SerializableRecord } from '@kbn/utility-types'; +import type { LayoutParams } from './layout'; + +export type JobId = string; + +export type BaseParams = Ensure< + { + layout?: LayoutParams; + objectType: string; + title: string; + browserTimezone: string; // to format dates in the user's time zone + version: string; // to handle any state migrations + }, + SerializableRecord +>; + +// base params decorated with encrypted headers that come into runJob functions +export interface BasePayload extends BaseParams { + headers: string; + spaceId?: string; + isDeprecated?: boolean; +} diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts b/x-pack/plugins/reporting/common/types/export_types/csv.ts similarity index 86% rename from x-pack/plugins/reporting/server/export_types/csv/types.d.ts rename to x-pack/plugins/reporting/common/types/export_types/csv.ts index fff6f0bcf95387..8249c129052d75 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts +++ b/x-pack/plugins/reporting/common/types/export_types/csv.ts @@ -5,8 +5,7 @@ * 2.0. */ -import type { FieldSpec } from 'src/plugins/data/common'; -import { BaseParams, BasePayload } from '../../types'; +import { BaseParams, BasePayload } from '../base'; export type RawValue = string | object | null | undefined; @@ -57,16 +56,6 @@ export interface SearchRequestDeprecatedCSV { | any; } -type FormatsMapDeprecatedCSV = Map< - string, - { - id: string; - params: { - pattern: string; - }; - } ->; - export interface SavedSearchGeneratorResultDeprecatedCSV { maxSizeReached: boolean; csvContainsFormulas?: boolean; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts b/x-pack/plugins/reporting/common/types/export_types/csv_searchsource.ts similarity index 81% rename from x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts rename to x-pack/plugins/reporting/common/types/export_types/csv_searchsource.ts index 170b03c2dfbff1..2ba88426e8786b 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts +++ b/x-pack/plugins/reporting/common/types/export_types/csv_searchsource.ts @@ -6,9 +6,7 @@ */ import type { SearchSourceFields } from 'src/plugins/data/common'; -import type { BaseParams, BasePayload } from '../../types'; - -export type RawValue = string | object | null | undefined; +import type { BaseParams, BasePayload } from '../base'; interface BaseParamsCSV { searchSource: SearchSourceFields; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts b/x-pack/plugins/reporting/common/types/export_types/csv_searchsource_immediate.ts similarity index 100% rename from x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts rename to x-pack/plugins/reporting/common/types/export_types/csv_searchsource_immediate.ts diff --git a/x-pack/plugins/reporting/common/types/export_types/index.ts b/x-pack/plugins/reporting/common/types/export_types/index.ts new file mode 100644 index 00000000000000..0ad47e74140317 --- /dev/null +++ b/x-pack/plugins/reporting/common/types/export_types/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './csv'; +export * from './csv_searchsource'; +export * from './csv_searchsource_immediate'; +export * from './png'; +export * from './png_v2'; +export * from './printable_pdf'; +export * from './printable_pdf_v2'; diff --git a/x-pack/plugins/reporting/server/export_types/png/types.d.ts b/x-pack/plugins/reporting/common/types/export_types/png.ts similarity index 84% rename from x-pack/plugins/reporting/server/export_types/png/types.d.ts rename to x-pack/plugins/reporting/common/types/export_types/png.ts index d266b5c8491852..3b850b5bd8b33b 100644 --- a/x-pack/plugins/reporting/server/export_types/png/types.d.ts +++ b/x-pack/plugins/reporting/common/types/export_types/png.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { LayoutParams } from '../../lib/layouts'; -import { BaseParams, BasePayload } from '../../types'; +import type { LayoutParams } from '../layout'; +import type { BaseParams, BasePayload } from '../base'; interface BaseParamsPNG { layout: LayoutParams; diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/types.d.ts b/x-pack/plugins/reporting/common/types/export_types/png_v2.ts similarity index 83% rename from x-pack/plugins/reporting/server/export_types/png_v2/types.d.ts rename to x-pack/plugins/reporting/common/types/export_types/png_v2.ts index 50c857b66934b2..c937d01ce0be1b 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/types.d.ts +++ b/x-pack/plugins/reporting/common/types/export_types/png_v2.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { LocatorParams } from '../../../common/types'; -import type { LayoutParams } from '../../lib/layouts'; -import type { BaseParams, BasePayload } from '../../types'; +import type { LocatorParams } from '../url'; +import type { LayoutParams } from '../layout'; +import type { BaseParams, BasePayload } from '../base'; // Job params: structure of incoming user request data export interface JobParamsPNGV2 extends BaseParams { diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts similarity index 74% rename from x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts rename to x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts index 8e4c45ad795067..a424706430f2c6 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts +++ b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { LayoutParams } from '../../lib/layouts'; -import { BaseParams, BasePayload } from '../../types'; +import type { LayoutParams } from '../layout'; +import type { BaseParams, BasePayload } from '../base'; interface BaseParamsPDF { layout: LayoutParams; @@ -17,6 +17,8 @@ interface BaseParamsPDF { // Job params: structure of incoming user request data, after being parsed from RISON export type JobParamsPDF = BaseParamsPDF & BaseParams; +export type JobAppParamsPDF = Omit; + // Job payload: structure of stored job data provided by create_job export interface TaskPayloadPDF extends BasePayload { layout: LayoutParams; @@ -24,8 +26,7 @@ export interface TaskPayloadPDF extends BasePayload { objects: Array<{ relativeUrl: string }>; } -type Legacy = Omit; -export interface JobParamsPDFLegacy extends Legacy { +export interface JobParamsPDFLegacy extends Omit { savedObjectId: string; queryString: string; } diff --git a/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts b/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts new file mode 100644 index 00000000000000..c9a7a2ce2331ab --- /dev/null +++ b/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorParams } from '../url'; +import type { LayoutParams } from '../layout'; +import type { BaseParams, BasePayload } from '../base'; + +interface BaseParamsPDFV2 { + layout: LayoutParams; + + /** + * This value is used to re-create the same visual state as when the report was requested as well as navigate to the correct page. + */ + locatorParams: LocatorParams[]; +} + +// Job params: structure of incoming user request data, after being parsed from RISON +export type JobParamsPDFV2 = BaseParamsPDFV2 & BaseParams; + +export type JobAppParamsPDFV2 = Omit; + +// Job payload: structure of stored job data provided by create_job +export interface TaskPayloadPDFV2 extends BasePayload, BaseParamsPDFV2 { + layout: LayoutParams; + /** + * The value of forceNow is injected server-side every time a given report is generated. + */ + forceNow: string; +} diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types/index.ts similarity index 75% rename from x-pack/plugins/reporting/common/types.ts rename to x-pack/plugins/reporting/common/types/index.ts index fe018feab0f8bd..75e8cb0af9698e 100644 --- a/x-pack/plugins/reporting/common/types.ts +++ b/x-pack/plugins/reporting/common/types/index.ts @@ -5,7 +5,20 @@ * 2.0. */ -import type { Ensure, SerializableRecord } from '@kbn/utility-types'; +import type { Size, LayoutParams } from './layout'; +import type { JobId, BaseParams, BasePayload } from './base'; + +export type { JobId, BaseParams, BasePayload }; +export type { Size, LayoutParams }; +export type { + DownloadReportFn, + IlmPolicyMigrationStatus, + IlmPolicyStatusResponse, + LocatorParams, + ManagementLinkFn, + UrlOrUrlLocatorTuple, +} from './url'; +export * from './export_types'; export interface PageSizeParams { pageMarginTop: number; @@ -21,22 +34,6 @@ export interface PdfImageSize { height?: number; } -export type Size = Ensure< - { - width: number; - height: number; - }, - SerializableRecord ->; - -export type LayoutParams = Ensure< - { - id: string; - dimensions?: Size; - }, - SerializableRecord ->; - export interface ReportDocumentHead { _id: string; _index: string; @@ -56,24 +53,6 @@ export interface TaskRunResult { warnings?: string[]; } -export type BaseParams = Ensure< - { - layout?: LayoutParams; - objectType: string; - title: string; - browserTimezone: string; // to format dates in the user's time zone - version: string; // to handle any state migrations - }, - SerializableRecord ->; - -// base params decorated with encrypted headers that come into runJob functions -export interface BasePayload extends BaseParams { - headers: string; - spaceId?: string; - isDeprecated?: boolean; -} - export interface ReportSource { /* * Required fields: populated in RequestHandler.enqueueJob when the request comes in to @@ -119,8 +98,6 @@ export interface ReportDocument extends ReportDocumentHead { _source: ReportSource; } -export type JobId = string; - /* * JobStatus: * - Begins as 'pending' @@ -173,28 +150,3 @@ export interface JobSummarySet { completed: JobSummary[]; failed: JobSummary[]; } - -type DownloadLink = string; -export type DownloadReportFn = (jobId: JobId) => DownloadLink; - -type ManagementLink = string; -export type ManagementLinkFn = () => ManagementLink; - -export interface LocatorParams< - P extends SerializableRecord = SerializableRecord & { forceNow?: string } -> { - id: string; - version: string; - params: P; -} - -export type IlmPolicyMigrationStatus = 'policy-not-found' | 'indices-not-managed-by-policy' | 'ok'; - -export interface IlmPolicyStatusResponse { - status: IlmPolicyMigrationStatus; -} - -type Url = string; -type UrlLocatorTuple = [url: Url, locatorParams: LocatorParams]; - -export type UrlOrUrlLocatorTuple = Url | UrlLocatorTuple; diff --git a/x-pack/plugins/reporting/common/types/layout.ts b/x-pack/plugins/reporting/common/types/layout.ts new file mode 100644 index 00000000000000..b22d6b59d0873e --- /dev/null +++ b/x-pack/plugins/reporting/common/types/layout.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 type { Ensure, SerializableRecord } from '@kbn/utility-types'; + +export type Size = Ensure< + { + width: number; + height: number; + }, + SerializableRecord +>; + +export type LayoutParams = Ensure< + { + id: string; + dimensions?: Size; + }, + SerializableRecord +>; diff --git a/x-pack/plugins/reporting/common/types/url.ts b/x-pack/plugins/reporting/common/types/url.ts new file mode 100644 index 00000000000000..dfb8ee9f908e3c --- /dev/null +++ b/x-pack/plugins/reporting/common/types/url.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 type { SerializableRecord } from '@kbn/utility-types'; +import type { JobId } from './base'; + +type DownloadLink = string; +export type DownloadReportFn = (jobId: JobId) => DownloadLink; + +type ManagementLink = string; +export type ManagementLinkFn = () => ManagementLink; + +export interface LocatorParams< + P extends SerializableRecord = SerializableRecord & { forceNow?: string } +> { + id: string; + version: string; + params: P; +} + +export type IlmPolicyMigrationStatus = 'policy-not-found' | 'indices-not-managed-by-policy' | 'ok'; + +export interface IlmPolicyStatusResponse { + status: IlmPolicyMigrationStatus; +} + +type Url = string; +type UrlLocatorTuple = [url: Url, locatorParams: LocatorParams]; + +export type UrlOrUrlLocatorTuple = Url | UrlLocatorTuple; diff --git a/x-pack/plugins/reporting/public/index.ts b/x-pack/plugins/reporting/public/index.ts index 2df236e6e3079e..a634f87140e731 100644 --- a/x-pack/plugins/reporting/public/index.ts +++ b/x-pack/plugins/reporting/public/index.ts @@ -18,6 +18,17 @@ export interface ReportingSetup { export type ReportingStart = ReportingSetup; export { constants } from '../common'; +export type { + JobParamsCSV, + JobParamsDownloadCSV, + JobParamsPNG, + JobParamsPNGV2, + JobAppParamsPDFV2, + JobParamsPDF, + JobParamsPDFV2, + JobAppParamsPDF, +} from '../common/types'; + export { ReportingAPIClient, ReportingPublicPlugin as Plugin }; export function plugin(initializerContext: PluginInitializerContext) { 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 a1f743eff791ce..be385f963c20db 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 @@ -103,7 +103,7 @@ export class ReportingAPIClient implements IReportingAPI { } public async deleteReport(jobId: string) { - return await this.http.delete(`${API_LIST_URL}/delete/${jobId}`, { + return await this.http.delete(`${API_LIST_URL}/delete/${jobId}`, { asSystemRequest: true, }); } @@ -124,7 +124,7 @@ export class ReportingAPIClient implements IReportingAPI { } public async total() { - return await this.http.get(`${API_LIST_URL}/count`, { + return await this.http.get(`${API_LIST_URL}/count`, { asSystemRequest: true, }); } @@ -222,13 +222,13 @@ export class ReportingAPIClient implements IReportingAPI { public getServerBasePath = () => this.http.basePath.serverBasePath; public verifyBrowser() { - return this.http.post(`${API_BASE_URL}/diagnose/browser`, { + return this.http.post(`${API_BASE_URL}/diagnose/browser`, { asSystemRequest: true, }); } public verifyScreenCapture() { - return this.http.post(`${API_BASE_URL}/diagnose/screenshot`, { + return this.http.post(`${API_BASE_URL}/diagnose/screenshot`, { asSystemRequest: true, }); } diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index fbfaeab9bc4f22..b55b7e636472c9 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -144,7 +144,7 @@ export class ReportingCsvPanelAction implements ActionDefinition this.isDownloading = false; const download = `${savedSearch.title}.csv`; - const blob = new Blob([rawResponse], { type: 'text/csv;charset=utf-8;' }); + const blob = new Blob([rawResponse as BlobPart], { type: 'text/csv;charset=utf-8;' }); // Hack for IE11 Support if (window.navigator.msSaveOrOpenBlob) { diff --git a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/index.ts b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/index.ts index 843a0b6747e4c1..94ee7f940b9175 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/index.ts +++ b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ReportingPanelContent, Props, ReportingPanelProps } from './reporting_panel_content'; +export type { Props, ReportingPanelProps } from './reporting_panel_content'; +export { ReportingPanelContent } from './reporting_panel_content'; diff --git a/x-pack/plugins/reporting/public/shared_imports.ts b/x-pack/plugins/reporting/public/shared_imports.ts index 1bca353e01e690..037dc5e374d25b 100644 --- a/x-pack/plugins/reporting/public/shared_imports.ts +++ b/x-pack/plugins/reporting/public/shared_imports.ts @@ -9,7 +9,8 @@ export type { SharePluginSetup, SharePluginStart, LocatorPublic } from 'src/plug export { AppNavLinkStatus } from '../../../../src/core/public'; -export { useRequest, UseRequestResponse } from '../../../../src/plugins/es_ui_shared/public'; +export type { UseRequestResponse } from '../../../../src/plugins/es_ui_shared/public'; +export { useRequest } from '../../../../src/plugins/es_ui_shared/public'; export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap b/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap new file mode 100644 index 00000000000000..a384550f184622 --- /dev/null +++ b/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap @@ -0,0 +1,197 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Reporting Config Schema context {"dev":false,"dist":false} produces correct config 1`] = ` +Object { + "capture": Object { + "browser": Object { + "autoDownload": true, + "chromium": Object { + "proxy": Object { + "enabled": false, + }, + }, + "type": "chromium", + }, + "loadDelay": "PT3S", + "maxAttempts": 1, + "networkPolicy": Object { + "enabled": true, + "rules": Array [ + Object { + "allow": true, + "host": undefined, + "protocol": "http:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "https:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "ws:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "wss:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "data:", + }, + Object { + "allow": false, + "host": undefined, + "protocol": undefined, + }, + ], + }, + "timeouts": Object { + "openUrl": "PT1M", + "renderComplete": "PT30S", + "waitForElements": "PT30S", + }, + "zoom": 2, + }, + "csv": Object { + "checkForFormulas": true, + "enablePanelActionDownload": true, + "escapeFormulaValues": false, + "maxSizeBytes": ByteSizeValue { + "valueInBytes": 10485760, + }, + "scroll": Object { + "duration": "30s", + "size": 500, + }, + "useByteOrderMarkEncoding": false, + }, + "enabled": true, + "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "kibanaServer": Object {}, + "poll": Object { + "jobCompletionNotifier": Object { + "interval": 10000, + "intervalErrorMultiplier": 5, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 5, + }, + }, + "queue": Object { + "indexInterval": "week", + "pollEnabled": true, + "pollInterval": "PT3S", + "pollIntervalErrorMultiplier": 10, + "timeout": "PT2M", + }, + "roles": Object { + "allow": Array [ + "reporting_user", + ], + "enabled": true, + }, +} +`; + +exports[`Reporting Config Schema context {"dev":false,"dist":true} produces correct config 1`] = ` +Object { + "capture": Object { + "browser": Object { + "autoDownload": false, + "chromium": Object { + "inspect": false, + "proxy": Object { + "enabled": false, + }, + }, + "type": "chromium", + }, + "loadDelay": "PT3S", + "maxAttempts": 3, + "networkPolicy": Object { + "enabled": true, + "rules": Array [ + Object { + "allow": true, + "host": undefined, + "protocol": "http:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "https:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "ws:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "wss:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "data:", + }, + Object { + "allow": false, + "host": undefined, + "protocol": undefined, + }, + ], + }, + "timeouts": Object { + "openUrl": "PT1M", + "renderComplete": "PT30S", + "waitForElements": "PT30S", + }, + "zoom": 2, + }, + "csv": Object { + "checkForFormulas": true, + "enablePanelActionDownload": true, + "escapeFormulaValues": false, + "maxSizeBytes": ByteSizeValue { + "valueInBytes": 10485760, + }, + "scroll": Object { + "duration": "30s", + "size": 500, + }, + "useByteOrderMarkEncoding": false, + }, + "enabled": true, + "kibanaServer": Object {}, + "poll": Object { + "jobCompletionNotifier": Object { + "interval": 10000, + "intervalErrorMultiplier": 5, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 5, + }, + }, + "queue": Object { + "indexInterval": "week", + "pollEnabled": true, + "pollInterval": "PT3S", + "pollIntervalErrorMultiplier": 10, + "timeout": "PT2M", + }, + "roles": Object { + "allow": Array [ + "reporting_user", + ], + "enabled": true, + }, +} +`; diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts index e042142a54a0fa..3c5ecdc1dab0b9 100644 --- a/x-pack/plugins/reporting/server/config/create_config.test.ts +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -5,27 +5,29 @@ * 2.0. */ -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import * as Rx from 'rxjs'; +import { CoreSetup, HttpServerInfo, PluginInitializerContext } from 'src/core/server'; import { coreMock } from 'src/core/server/mocks'; -import { LevelLogger } from '../lib'; -import { createMockConfigSchema } from '../test_helpers'; +import { LevelLogger } from '../lib/level_logger'; +import { createMockConfigSchema, createMockLevelLogger } from '../test_helpers'; +import { ReportingConfigType } from './'; import { createConfig$ } from './create_config'; +const createMockConfig = ( + mockInitContext: PluginInitializerContext +): Rx.Observable => mockInitContext.config.create(); + describe('Reporting server createConfig$', () => { let mockCoreSetup: CoreSetup; let mockInitContext: PluginInitializerContext; - let mockLogger: LevelLogger; + let mockLogger: jest.Mocked; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockInitContext = coreMock.createPluginInitializerContext( createMockConfigSchema({ kibanaServer: {} }) ); - mockLogger = { - warn: jest.fn(), - debug: jest.fn(), - clone: jest.fn().mockImplementation(() => mockLogger), - } as unknown as LevelLogger; + mockLogger = createMockLevelLogger(); }); afterEach(() => { @@ -37,21 +39,14 @@ describe('Reporting server createConfig$', () => { ...createMockConfigSchema({ kibanaServer: {} }), encryptionKey: undefined, }); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch(/\S{32,}/); // random 32 characters - expect(result.kibanaServer).toMatchInlineSnapshot(` - Object { - "hostname": "localhost", - "port": 80, - "protocol": "http", - } - `); - expect((mockLogger.warn as any).mock.calls.length).toBe(1); - expect((mockLogger.warn as any).mock.calls[0]).toMatchObject([ - 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.', - ]); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.' + ); }); it('uses the user-provided encryption key', async () => { @@ -60,10 +55,10 @@ describe('Reporting server createConfig$', () => { encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn).not.toHaveBeenCalled(); }); it('uses the user-provided encryption key, reporting kibanaServer settings to override server info', async () => { @@ -77,7 +72,7 @@ describe('Reporting server createConfig$', () => { }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result).toMatchInlineSnapshot(` @@ -108,7 +103,7 @@ describe('Reporting server createConfig$', () => { }, } `); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn).not.toHaveBeenCalled(); }); it('uses user-provided disableSandbox: false', async () => { @@ -118,11 +113,11 @@ describe('Reporting server createConfig$', () => { capture: { browser: { chromium: { disableSandbox: false } } }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn).not.toHaveBeenCalled(); }); it('uses user-provided disableSandbox: true', async () => { @@ -132,11 +127,11 @@ describe('Reporting server createConfig$', () => { capture: { browser: { chromium: { disableSandbox: true } } }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn).not.toHaveBeenCalled(); }); it('provides a default for disableSandbox', async () => { @@ -145,10 +140,47 @@ describe('Reporting server createConfig$', () => { encryptionKey: '888888888888888888888888888888888', }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn).not.toHaveBeenCalled(); }); + + it.each(['0', '0.0', '0.0.0', '0.0.0.0', '0000:0000:0000:0000:0000:0000:0000:0000', '::'])( + `apply failover logic when hostname is given as "%s"`, + async (hostname) => { + mockInitContext = coreMock.createPluginInitializerContext( + createMockConfigSchema({ + encryptionKey: 'aaaaaaaaaaaaabbbbbbbbbbbbaaaaaaaaa', + // overwrite settings added by createMockConfigSchema and apply the default settings + // TODO make createMockConfigSchema _not_ default xpack.reporting.kibanaServer.hostname to 'localhost' + kibanaServer: { + hostname: undefined, + port: undefined, + }, + }) + ); + mockCoreSetup.http.getServerInfo = jest.fn( + (): HttpServerInfo => ({ + name: 'cool server', + hostname, + port: 5601, + protocol: 'http', + }) + ); + + const mockConfig$ = createMockConfig(mockInitContext); + await expect( + createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise() + ).resolves.toHaveProperty( + 'kibanaServer', + expect.objectContaining({ + hostname: 'localhost', + port: 5601, + protocol: 'http', + }) + ); + } + ); }); diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts index b1d69d17334bee..5de54a43582abb 100644 --- a/x-pack/plugins/reporting/server/config/create_config.ts +++ b/x-pack/plugins/reporting/server/config/create_config.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import crypto from 'crypto'; -import { upperFirst } from 'lodash'; +import ipaddr from 'ipaddr.js'; +import { sum, upperFirst } from 'lodash'; import { Observable } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; import { CoreSetup } from 'src/core/server'; @@ -33,20 +33,29 @@ export function createConfig$( let encryptionKey = config.encryptionKey; if (encryptionKey === undefined) { logger.warn( - i18n.translate('xpack.reporting.serverConfig.randomEncryptionKey', { - defaultMessage: - 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' + - 'restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.', - }) + 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' + + 'restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.' ); encryptionKey = crypto.randomBytes(16).toString('hex'); } const { kibanaServer: reportingServer } = config; const serverInfo = core.http.getServerInfo(); - // kibanaServer.hostname, default to server.host - const kibanaServerHostname = reportingServer.hostname + // set kibanaServer.hostname, default to server.host, don't allow "0.0.0.0" as it breaks in Windows + let kibanaServerHostname = reportingServer.hostname ? reportingServer.hostname : serverInfo.hostname; + + if ( + ipaddr.isValid(kibanaServerHostname) && + !sum(ipaddr.parse(kibanaServerHostname).toByteArray()) + ) { + logger.warn( + `Found 'server.host: "0.0.0.0"' in Kibana configuration. Reporting is not able to use this as the Kibana server hostname.` + + ` To enable PNG/PDF Reporting to work, 'xpack.reporting.kibanaServer.hostname: localhost' is automatically set in the configuration.` + + ` You can prevent this message by adding 'xpack.reporting.kibanaServer.hostname: localhost' in kibana.yml.` + ); + kibanaServerHostname = 'localhost'; + } // kibanaServer.port, default to server.port const kibanaServerPort = reportingServer.port ? reportingServer.port : serverInfo.port; // kibanaServer.protocol, default to server.protocol @@ -66,36 +75,24 @@ export function createConfig$( mergeMap(async (config) => { if (config.capture.browser.chromium.disableSandbox != null) { // disableSandbox was set by user - return config; + return { ...config }; } // disableSandbox was not set by user, apply default for OS const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled(); const osName = [os.os, os.dist, os.release].filter(Boolean).map(upperFirst).join(' '); - logger.debug( - i18n.translate('xpack.reporting.serverConfig.osDetected', { - defaultMessage: `Running on OS: '{osName}'`, - values: { osName }, - }) - ); + logger.debug(`Running on OS: '{osName}'`); if (disableSandbox === true) { logger.warn( - i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxDisabled', { - defaultMessage: `Chromium sandbox provides an additional layer of protection, but is not supported for {osName} OS. Automatically setting '{configKey}: true'.`, - values: { - configKey: 'xpack.reporting.capture.browser.chromium.disableSandbox', - osName, - }, - }) + `Chromium sandbox provides an additional layer of protection, but is not supported for ${osName} OS.` + + ` Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.` ); } else { logger.info( - i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxEnabled', { - defaultMessage: `Chromium sandbox provides an additional layer of protection, and is supported for {osName} OS. Automatically enabling Chromium sandbox.`, - values: { osName }, - }) + `Chromium sandbox provides an additional layer of protection, and is supported for ${osName} OS.` + + ` Automatically enabling Chromium sandbox.` ); } diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index c20b5e16c7f04f..fa836fd47cde36 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -11,7 +11,8 @@ import { get } from 'lodash'; import { ConfigSchema, ReportingConfigType } from './schema'; export { buildConfig } from './config'; export { registerUiSettings } from './ui_settings'; -export { ConfigSchema, ReportingConfigType }; +export type { ReportingConfigType }; +export { ConfigSchema }; export const config: PluginConfigDescriptor = { exposeToBrowser: { poll: true, roles: true }, diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts index 6ad7d03bd1a8f9..c49490be87a15b 100644 --- a/x-pack/plugins/reporting/server/config/schema.test.ts +++ b/x-pack/plugins/reporting/server/config/schema.test.ts @@ -9,203 +9,11 @@ import { ConfigSchema } from './schema'; describe('Reporting Config Schema', () => { it(`context {"dev":false,"dist":false} produces correct config`, () => { - expect(ConfigSchema.validate({}, { dev: false, dist: false })).toMatchInlineSnapshot(` - Object { - "capture": Object { - "browser": Object { - "autoDownload": true, - "chromium": Object { - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, - "loadDelay": "PT3S", - "maxAttempts": 1, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "host": undefined, - "protocol": "http:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "https:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "ws:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "wss:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "data:", - }, - Object { - "allow": false, - "host": undefined, - "protocol": undefined, - }, - ], - }, - "timeouts": Object { - "openUrl": "PT1M", - "renderComplete": "PT30S", - "waitForElements": "PT30S", - }, - "zoom": 2, - }, - "csv": Object { - "checkForFormulas": true, - "enablePanelActionDownload": true, - "escapeFormulaValues": false, - "maxSizeBytes": ByteSizeValue { - "valueInBytes": 10485760, - }, - "scroll": Object { - "duration": "30s", - "size": 500, - }, - "useByteOrderMarkEncoding": false, - }, - "enabled": true, - "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "kibanaServer": Object {}, - "poll": Object { - "jobCompletionNotifier": Object { - "interval": 10000, - "intervalErrorMultiplier": 5, - }, - "jobsRefresh": Object { - "interval": 5000, - "intervalErrorMultiplier": 5, - }, - }, - "queue": Object { - "indexInterval": "week", - "pollEnabled": true, - "pollInterval": "PT3S", - "pollIntervalErrorMultiplier": 10, - "timeout": "PT2M", - }, - "roles": Object { - "allow": Array [ - "reporting_user", - ], - "enabled": true, - }, - } - `); + expect(ConfigSchema.validate({}, { dev: false, dist: false })).toMatchSnapshot(); }); it(`context {"dev":false,"dist":true} produces correct config`, () => { - expect(ConfigSchema.validate({}, { dev: false, dist: true })).toMatchInlineSnapshot(` - Object { - "capture": Object { - "browser": Object { - "autoDownload": false, - "chromium": Object { - "inspect": false, - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, - "loadDelay": "PT3S", - "maxAttempts": 3, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "host": undefined, - "protocol": "http:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "https:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "ws:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "wss:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "data:", - }, - Object { - "allow": false, - "host": undefined, - "protocol": undefined, - }, - ], - }, - "timeouts": Object { - "openUrl": "PT1M", - "renderComplete": "PT30S", - "waitForElements": "PT30S", - }, - "zoom": 2, - }, - "csv": Object { - "checkForFormulas": true, - "enablePanelActionDownload": true, - "escapeFormulaValues": false, - "maxSizeBytes": ByteSizeValue { - "valueInBytes": 10485760, - }, - "scroll": Object { - "duration": "30s", - "size": 500, - }, - "useByteOrderMarkEncoding": false, - }, - "enabled": true, - "kibanaServer": Object {}, - "poll": Object { - "jobCompletionNotifier": Object { - "interval": 10000, - "intervalErrorMultiplier": 5, - }, - "jobsRefresh": Object { - "interval": 5000, - "intervalErrorMultiplier": 5, - }, - }, - "queue": Object { - "indexInterval": "week", - "pollEnabled": true, - "pollInterval": "PT3S", - "pollIntervalErrorMultiplier": 10, - "timeout": "PT2M", - }, - "roles": Object { - "allow": Array [ - "reporting_user", - ], - "enabled": true, - }, - } - `); + expect(ConfigSchema.validate({}, { dev: false, dist: true })).toMatchSnapshot(); }); it('allows Duration values for certain keys', () => { @@ -288,11 +96,27 @@ describe('Reporting Config Schema', () => { `); }); - it(`logs the proper validation messages`, () => { - // kibanaServer - const throwValidationErr = () => ConfigSchema.validate({ kibanaServer: { hostname: '0' } }); - expect(throwValidationErr).toThrowError( - `[kibanaServer.hostname]: value must be a valid hostname (see RFC 1123).` - ); - }); + it.each(['0', '0.0', '0.0.0'])( + `fails to validate "kibanaServer.hostname" with an invalid hostname: "%s"`, + (address) => { + expect(() => + ConfigSchema.validate({ + kibanaServer: { hostname: address }, + }) + ).toThrowError(`[kibanaServer.hostname]: value must be a valid hostname (see RFC 1123).`); + } + ); + + it.each(['0.0.0.0', '0000:0000:0000:0000:0000:0000:0000:0000', '::'])( + `fails to validate "kibanaServer.hostname" hostname as zero: "%s"`, + (address) => { + expect(() => + ConfigSchema.validate({ + kibanaServer: { hostname: address }, + }) + ).toThrowError( + `[kibanaServer.hostname]: cannot use '0.0.0.0' as Kibana host name, consider using the default (localhost) instead` + ); + } + ); }); diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts index 5b15260be06cb7..4c56fc4c6db60a 100644 --- a/x-pack/plugins/reporting/server/config/schema.ts +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -6,10 +6,22 @@ */ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; +import ipaddr from 'ipaddr.js'; +import { sum } from 'lodash'; import moment from 'moment'; const KibanaServerSchema = schema.object({ - hostname: schema.maybe(schema.string({ hostname: true })), + hostname: schema.maybe( + schema.string({ + hostname: true, + validate(value) { + if (ipaddr.isValid(value) && !sum(ipaddr.parse(value).toByteArray())) { + // prevent setting a hostname that fails in Chromium on Windows + return `cannot use '0.0.0.0' as Kibana host name, consider using the default (localhost) instead`; + } + }, + }) + ), port: schema.maybe(schema.number()), protocol: schema.maybe( schema.string({ diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts index e4575f98753156..355a83c13a37e6 100644 --- a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts +++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts @@ -19,8 +19,11 @@ import { ReportingCore } from '../'; import { deprecations } from '../lib/deprecations'; const REPORTING_USER_ROLE_NAME = 'reporting_user'; -const getDocumentationUrl = (branch: string) => - `https://www.elastic.co/guide/en/kibana/${branch}/kibana-privileges.html`; +const getDocumentationUrl = (branch: string) => { + // TODO: remove when docs support "main" + const docBranch = branch === 'main' ? 'master' : branch; + return `https://www.elastic.co/guide/en/kibana/${docBranch}/kibana-privileges.html`; +}; interface ExtraDependencies { reportingCore: ReportingCore; diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.ts b/x-pack/plugins/reporting/server/export_types/csv/types.ts new file mode 100644 index 00000000000000..5531f2d670128f --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { + RawValue, + JobParamsDeprecatedCSV, + TaskPayloadDeprecatedCSV, + SearchRequestDeprecatedCSV, + SavedSearchGeneratorResultDeprecatedCSV, +} from '../../../common/types'; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.ts new file mode 100644 index 00000000000000..57e154eb2b26fc --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.ts @@ -0,0 +1,8 @@ +/* + * 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 type { RawValue, JobParamsCSV, TaskPayloadCSV } from '../../../common/types'; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.ts new file mode 100644 index 00000000000000..1475c0cc2cf634 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { + FakeRequest, + JobParamsDownloadCSV, + SavedObjectServiceError, +} from '../../../common/types'; diff --git a/x-pack/plugins/reporting/server/export_types/png/types.ts b/x-pack/plugins/reporting/server/export_types/png/types.ts new file mode 100644 index 00000000000000..ccfca04b024990 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/png/types.ts @@ -0,0 +1,8 @@ +/* + * 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 type { JobParamsPNG, TaskPayloadPNG } from '../../../common/types'; diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/types.ts b/x-pack/plugins/reporting/server/export_types/png_v2/types.ts new file mode 100644 index 00000000000000..3773bfae2b3ff7 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/png_v2/types.ts @@ -0,0 +1,8 @@ +/* + * 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 type { JobParamsPNGV2, TaskPayloadPNGV2 } from '../../../common/types'; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.ts new file mode 100644 index 00000000000000..763fb8942b4707 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.ts @@ -0,0 +1,8 @@ +/* + * 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 type { JobParamsPDF, TaskPayloadPDF, JobParamsPDFLegacy } from '../../../common/types'; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/types.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/types.ts index a629eea9f21f7e..ba8427feda9145 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/types.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/types.ts @@ -5,27 +5,4 @@ * 2.0. */ -import { LocatorParams } from '../../../common/types'; -import { LayoutParams } from '../../lib/layouts'; -import { BaseParams, BasePayload } from '../../types'; - -interface BaseParamsPDFV2 { - layout: LayoutParams; - - /** - * This value is used to re-create the same visual state as when the report was requested as well as navigate to the correct page. - */ - locatorParams: LocatorParams[]; -} - -// Job params: structure of incoming user request data, after being parsed from RISON -export type JobParamsPDFV2 = BaseParamsPDFV2 & BaseParams; - -// Job payload: structure of stored job data provided by create_job -export interface TaskPayloadPDFV2 extends BasePayload, BaseParamsPDFV2 { - layout: LayoutParams; - /** - * The value of forceNow is injected server-side every time a given report is generated. - */ - forceNow: string; -} +export type { JobParamsPDFV2, TaskPayloadPDFV2 } from '../../../common/types'; diff --git a/x-pack/plugins/reporting/server/index.ts b/x-pack/plugins/reporting/server/index.ts index bc6529eb90782c..2c851a9dffc1bf 100644 --- a/x-pack/plugins/reporting/server/index.ts +++ b/x-pack/plugins/reporting/server/index.ts @@ -13,10 +13,20 @@ export const plugin = (initContext: PluginInitializerContext ({ openUrl: { diff --git a/x-pack/plugins/reporting/server/lib/store/index.ts b/x-pack/plugins/reporting/server/lib/store/index.ts index 9ba8d44f19e653..e5f1c65e47948e 100644 --- a/x-pack/plugins/reporting/server/lib/store/index.ts +++ b/x-pack/plugins/reporting/server/lib/store/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { ReportDocument } from '../../../common/types'; +export type { ReportDocument } from '../../../common/types'; export { Report } from './report'; export { SavedReport } from './saved_report'; export { ReportingStore } from './store'; diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts index 2feb162fbf37ea..2f802334eb6ff5 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.ts @@ -17,8 +17,8 @@ import { } from '../../../common/types'; import type { ReportTaskParams } from '../tasks'; -export { ReportDocument }; -export { ReportApiJSON, ReportSource }; +export type { ReportDocument }; +export type { ReportApiJSON, ReportSource }; const puid = new Puid(); export const MIGRATION_VERSION = '7.14.0'; diff --git a/x-pack/plugins/reporting/server/lib/tasks/index.ts b/x-pack/plugins/reporting/server/lib/tasks/index.ts index f464383c0b5334..6be8299d1d26a3 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/index.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/index.ts @@ -14,7 +14,7 @@ export const REPORTING_MONITOR_TYPE = 'reports:monitor'; export { ExecuteReportTask } from './execute_report'; export { MonitorReportsTask } from './monitor_reports'; -export { TaskRunResult }; +export type { TaskRunResult }; export interface ReportTaskParams { id: string; diff --git a/x-pack/plugins/reporting/server/routes/lib/request_handler.ts b/x-pack/plugins/reporting/server/routes/lib/request_handler.ts index a87f5c2913031b..2100c4c3c43acd 100644 --- a/x-pack/plugins/reporting/server/routes/lib/request_handler.ts +++ b/x-pack/plugins/reporting/server/routes/lib/request_handler.ts @@ -6,6 +6,7 @@ */ import Boom from '@hapi/boom'; +import { i18n } from '@kbn/i18n'; import { KibanaRequest, KibanaResponseFactory } from 'kibana/server'; import { ReportingCore } from '../..'; import { API_BASE_URL } from '../../../common/constants'; @@ -153,7 +154,13 @@ export class RequestHandler { }); } - // unknown error, can't convert to 4xx - throw err; + return this.res.customError({ + statusCode: 500, + body: + err?.message || + i18n.translate('xpack.reporting.errorHandler.unknownError', { + defaultMessage: 'Unknown error', + }), + }); } } diff --git a/x-pack/plugins/reporting/server/routes/management/jobs.test.ts b/x-pack/plugins/reporting/server/routes/management/jobs.test.ts index 02a0ddc94a043c..a54be44258ed37 100644 --- a/x-pack/plugins/reporting/server/routes/management/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/management/jobs.test.ts @@ -178,6 +178,36 @@ describe('GET /api/reporting/jobs/download', () => { await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(401); }); + it(`returns job's info`, async () => { + mockEsClient.search.mockResolvedValueOnce({ + body: getHits({ + jobtype: 'base64EncodedJobType', + payload: {}, // payload is irrelevant + }), + } as any); + + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/info/test').expect(200); + }); + + it(`returns 403 if a user cannot view a job's info`, async () => { + mockEsClient.search.mockResolvedValueOnce({ + body: getHits({ + jobtype: 'customForbiddenJobType', + payload: {}, // payload is irrelevant + }), + } as any); + + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/info/test').expect(403); + }); + it('when a job is incomplete', async () => { mockEsClient.search.mockResolvedValueOnce({ body: getHits({ diff --git a/x-pack/plugins/reporting/server/routes/management/jobs.ts b/x-pack/plugins/reporting/server/routes/management/jobs.ts index 7ed197ce533094..d0b30f94ac3374 100644 --- a/x-pack/plugins/reporting/server/routes/management/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/management/jobs.ts @@ -6,20 +6,21 @@ */ import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { ReportingCore } from '../../'; import { - kibanaResponseFactory, IKibanaResponse, + kibanaResponseFactory, ResponseError, } from '../../../../../../src/core/server'; -import { ReportingCore } from '../../'; import { ROUTE_TAG_CAN_REDIRECT } from '../../../../security/server'; import { API_BASE_URL } from '../../../common/constants'; import { Report } from '../../lib/store'; +import type { ReportApiJSON } from '../../lib/store/report'; import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; import { jobsQueryFactory } from '../lib/jobs_query'; import { deleteJobResponseHandler, downloadJobResponseHandler } from '../lib/job_response_handler'; import { handleUnavailable } from '../lib/request_handler'; -import type { ReportApiJSON } from '../../lib/store/report'; const MAIN_ENTRY = `${API_BASE_URL}/jobs`; @@ -111,8 +112,11 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { if (!jobTypes.includes(jobType)) { // Bad result - return kibanaResponseFactory.unauthorized({ - body: `Sorry, you are not authorized to view ${jobType} info`, + return kibanaResponseFactory.forbidden({ + body: i18n.translate('xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage', { + defaultMessage: 'Sorry, you are not authorized to view {jobType} info', + values: { jobType }, + }), }); } diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts index 1a8bfe7b702089..a6e6be47bdfcdd 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts @@ -16,7 +16,6 @@ export function createMockLevelLogger() { const logger = new LevelLogger(loggingSystemMock.create()) as jest.Mocked; - logger.clone.mockImplementation(createMockLevelLogger); // logger.debug.mockImplementation(consoleLogger('debug')); // uncomment this to see debug logs in jest tests logger.info.mockImplementation(consoleLogger('info')); logger.warn.mockImplementation(consoleLogger('warn')); @@ -24,5 +23,7 @@ export function createMockLevelLogger() { logger.error.mockImplementation(consoleLogger('error')); logger.trace.mockImplementation(consoleLogger('trace')); + logger.clone.mockImplementation(() => logger); + return logger; } diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index 84fa6fb5b10d6e..af9a973b0bb451 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -57,7 +57,7 @@ export type ReportingUser = { username: AuthenticatedUser['username'] } | false; export type CaptureConfig = ReportingConfigType['capture']; export type ScrollConfig = ReportingConfigType['csv']['scroll']; -export { BaseParams, BasePayload }; +export type { BaseParams, BasePayload }; // default fn type for CreateJobFnFactory export type CreateJobFn = ( diff --git a/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts b/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts index 2749e165e15681..a2adfd7a385292 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts +++ b/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts @@ -8,7 +8,14 @@ import { IHttpFetchError } from 'src/core/public'; import { getNotifications, getFatalErrors } from '../../kibana_services'; -function createToastConfig(error: IHttpFetchError, errorTitle: string) { +function createToastConfig( + error: IHttpFetchError<{ + statusCode: number; + message: string; + error: string; + }>, + errorTitle: string +) { if (error && error.body) { // Error body shape is defined by the API. const { error: errorString, statusCode, message } = error.body; @@ -20,7 +27,14 @@ function createToastConfig(error: IHttpFetchError, errorTitle: string) { } } -export function showApiWarning(error: IHttpFetchError, errorTitle: string) { +export function showApiWarning( + error: IHttpFetchError<{ + statusCode: number; + message: string; + error: string; + }>, + errorTitle: string +) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { @@ -32,7 +46,14 @@ export function showApiWarning(error: IHttpFetchError, errorTitle: string) { return getFatalErrors().add(error, errorTitle); } -export function showApiError(error: IHttpFetchError, errorTitle: string) { +export function showApiError( + error: IHttpFetchError<{ + statusCode: number; + message: string; + error: string; + }>, + errorTitle: string +) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index 1c59e56c0466ab..bd95e7e9425278 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -50,6 +50,11 @@ export const technicalRuleFieldMap = { array: false, required: false, }, + [Fields.ALERT_RISK_SCORE]: { + type: 'float', + array: false, + required: false, + }, [Fields.ALERT_WORKFLOW_STATUS]: { type: 'keyword', array: false, diff --git a/x-pack/plugins/rule_registry/common/field_map/types.ts b/x-pack/plugins/rule_registry/common/field_map/types.ts index 3ff68315e93a66..d4bdd34656ec16 100644 --- a/x-pack/plugins/rule_registry/common/field_map/types.ts +++ b/x-pack/plugins/rule_registry/common/field_map/types.ts @@ -6,5 +6,5 @@ */ export interface FieldMap { - [key: string]: { type: string; required?: boolean; array?: boolean }; + [key: string]: { type: string; required?: boolean; array?: boolean; path?: string }; } diff --git a/x-pack/plugins/rule_registry/server/index.ts b/x-pack/plugins/rule_registry/server/index.ts index d6c5b61706415c..d52cd0ebf608c9 100644 --- a/x-pack/plugins/rule_registry/server/index.ts +++ b/x-pack/plugins/rule_registry/server/index.ts @@ -12,9 +12,9 @@ import { PluginInitializerContext } from 'src/core/server'; import { RuleRegistryPlugin } from './plugin'; export type { RuleRegistryPluginSetupContract, RuleRegistryPluginStartContract } from './plugin'; -export { IRuleDataService, RuleDataPluginService } from './rule_data_plugin_service'; +export type { IRuleDataService, RuleDataPluginService } from './rule_data_plugin_service'; export { RuleDataClient } from './rule_data_client'; -export { IRuleDataClient } from './rule_data_client/types'; +export type { IRuleDataClient } from './rule_data_client/types'; export type { RacRequestHandlerContext, RacApiRequestHandlerContext, @@ -24,14 +24,15 @@ export type { export * from './config'; export * from './rule_data_plugin_service'; export * from './rule_data_client'; +export * from './alert_data_client/audit_events'; export { createLifecycleRuleTypeFactory } from './utils/create_lifecycle_rule_type_factory'; -export { +export type { LifecycleRuleExecutor, LifecycleAlertService, LifecycleAlertServices, - createLifecycleExecutor, } from './utils/create_lifecycle_executor'; +export { createLifecycleExecutor } from './utils/create_lifecycle_executor'; export { createPersistenceRuleTypeWrapper } from './utils/create_persistence_rule_type_wrapper'; export * from './utils/persistence_types'; export type { AlertsClient } from './alert_data_client/alerts_client'; diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts index 041dfdeed42e04..3798506eeacd17 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts @@ -315,6 +315,7 @@ export class ResourceInstaller { // @ts-expect-error rollover_alias: primaryNamespacedAlias, }, + 'index.mapping.total_fields.limit': 1100, }, mappings: { dynamic: false, diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts index 7dea0f91724760..e575b49d17766d 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts @@ -30,7 +30,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper .getWriter({ namespace: options.spaceId }) .bulk({ body: alerts.flatMap((alert) => [ - { index: {} }, + { index: { _id: alert.id } }, { [VERSION]: ruleDataClient.kibanaVersion, ...commonRuleFields, diff --git a/x-pack/plugins/runtime_fields/public/components/index.ts b/x-pack/plugins/runtime_fields/public/components/index.ts index 8fbec39b4abfbd..47db4087dab6ef 100644 --- a/x-pack/plugins/runtime_fields/public/components/index.ts +++ b/x-pack/plugins/runtime_fields/public/components/index.ts @@ -5,11 +5,10 @@ * 2.0. */ -export { RuntimeFieldForm, FormState as RuntimeFieldFormState } from './runtime_field_form'; +export type { FormState as RuntimeFieldFormState } from './runtime_field_form'; +export { RuntimeFieldForm } from './runtime_field_form'; export { RuntimeFieldEditor } from './runtime_field_editor'; -export { - RuntimeFieldEditorFlyoutContent, - RuntimeFieldEditorFlyoutContentProps, -} from './runtime_field_editor_flyout_content'; +export type { RuntimeFieldEditorFlyoutContentProps } from './runtime_field_editor_flyout_content'; +export { RuntimeFieldEditorFlyoutContent } from './runtime_field_editor_flyout_content'; diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/index.ts b/x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/index.ts index 7ac0165fc9d411..e626cdb5b2312b 100644 --- a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/index.ts +++ b/x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/index.ts @@ -5,7 +5,5 @@ * 2.0. */ -export { - RuntimeFieldEditorFlyoutContent, - Props as RuntimeFieldEditorFlyoutContentProps, -} from './runtime_field_editor_flyout_content'; +export type { Props as RuntimeFieldEditorFlyoutContentProps } from './runtime_field_editor_flyout_content'; +export { RuntimeFieldEditorFlyoutContent } from './runtime_field_editor_flyout_content'; diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/index.ts b/x-pack/plugins/runtime_fields/public/components/runtime_field_form/index.ts index bc8d6d8bdc1b34..a8f29539c4f2c3 100644 --- a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/index.ts +++ b/x-pack/plugins/runtime_fields/public/components/runtime_field_form/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { RuntimeFieldForm, FormState } from './runtime_field_form'; +export type { FormState } from './runtime_field_form'; +export { RuntimeFieldForm } from './runtime_field_form'; diff --git a/x-pack/plugins/runtime_fields/public/index.ts b/x-pack/plugins/runtime_fields/public/index.ts index 1a3c6943c3a5cd..ece935d0d55719 100644 --- a/x-pack/plugins/runtime_fields/public/index.ts +++ b/x-pack/plugins/runtime_fields/public/index.ts @@ -7,14 +7,10 @@ import { RuntimeFieldsPlugin } from './plugin'; -export { - RuntimeFieldEditorFlyoutContent, - RuntimeFieldEditorFlyoutContentProps, - RuntimeFieldEditor, - RuntimeFieldFormState, -} from './components'; +export type { RuntimeFieldEditorFlyoutContentProps, RuntimeFieldFormState } from './components'; +export { RuntimeFieldEditorFlyoutContent, RuntimeFieldEditor } from './components'; export { RUNTIME_FIELD_OPTIONS } from './constants'; -export { RuntimeField, RuntimeType, PluginSetup as RuntimeFieldsSetup } from './types'; +export type { RuntimeField, RuntimeType, PluginSetup as RuntimeFieldsSetup } from './types'; export function plugin() { return new RuntimeFieldsPlugin(); diff --git a/x-pack/plugins/runtime_fields/public/shared_imports.ts b/x-pack/plugins/runtime_fields/public/shared_imports.ts index a6dd23440176a9..61b517a4e11171 100644 --- a/x-pack/plugins/runtime_fields/public/shared_imports.ts +++ b/x-pack/plugins/runtime_fields/public/shared_imports.ts @@ -5,15 +5,17 @@ * 2.0. */ +export type { + FormSchema, + FormHook, + ValidationFunc, + FieldConfig, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { useForm, useFormData, Form, - FormSchema, UseField, - FormHook, - ValidationFunc, - FieldConfig, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers'; diff --git a/x-pack/plugins/runtime_fields/public/test_utils.ts b/x-pack/plugins/runtime_fields/public/test_utils.ts index d3b02257c2ff2c..1c052cd666e568 100644 --- a/x-pack/plugins/runtime_fields/public/test_utils.ts +++ b/x-pack/plugins/runtime_fields/public/test_utils.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { registerTestBed, TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +export { registerTestBed } from '@kbn/test/jest'; diff --git a/x-pack/plugins/saved_objects_tagging/common/index.ts b/x-pack/plugins/saved_objects_tagging/common/index.ts index 500e656014e9a5..a513822adb5e05 100644 --- a/x-pack/plugins/saved_objects_tagging/common/index.ts +++ b/x-pack/plugins/saved_objects_tagging/common/index.ts @@ -5,11 +5,12 @@ * 2.0. */ -export { TagsCapabilities, getTagsCapabilities } from './capabilities'; +export type { TagsCapabilities } from './capabilities'; +export { getTagsCapabilities } from './capabilities'; export { tagFeatureId, tagSavedObjectTypeName, tagManagementSectionId } from './constants'; -export { TagWithRelations, TagAttributes, Tag, ITagsClient, TagSavedObject } from './types'; +export type { TagWithRelations, TagAttributes, Tag, ITagsClient, TagSavedObject } from './types'; +export type { TagValidation } from './validation'; export { - TagValidation, validateTagColor, validateTagName, validateTagDescription, diff --git a/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/index.ts b/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/index.ts index 2dc3449199cf63..d785d241edb6c3 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -export { - getAssignFlyoutOpener, +export type { AssignFlyoutOpener, GetAssignFlyoutOpenerOptions, OpenAssignFlyoutOptions, } from './open_assign_flyout'; +export { getAssignFlyoutOpener } from './open_assign_flyout'; diff --git a/x-pack/plugins/saved_objects_tagging/public/components/base/index.ts b/x-pack/plugins/saved_objects_tagging/public/components/base/index.ts index 34500b978df00e..29c75be50a3926 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/base/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/components/base/index.ts @@ -5,7 +5,11 @@ * 2.0. */ -export { TagBadge, TagBadgeProps } from './tag_badge'; -export { TagList, TagListProps } from './tag_list'; -export { TagSelector, TagSelectorProps } from './tag_selector'; -export { TagSearchBarOption, TagSearchBarOptionProps } from './tag_searchbar_option'; +export type { TagBadgeProps } from './tag_badge'; +export { TagBadge } from './tag_badge'; +export type { TagListProps } from './tag_list'; +export { TagList } from './tag_list'; +export type { TagSelectorProps } from './tag_selector'; +export { TagSelector } from './tag_selector'; +export type { TagSearchBarOptionProps } from './tag_searchbar_option'; +export { TagSearchBarOption } from './tag_searchbar_option'; diff --git a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/index.ts b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/index.ts index 0436e8c57e59be..ac6994453443eb 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { getCreateModalOpener, getEditModalOpener, CreateModalOpener } from './open_modal'; +export type { CreateModalOpener } from './open_modal'; +export { getCreateModalOpener, getEditModalOpener } from './open_modal'; diff --git a/x-pack/plugins/saved_objects_tagging/public/components/index.ts b/x-pack/plugins/saved_objects_tagging/public/components/index.ts index c6142d8552bccd..5c7c491df9bd4e 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/components/index.ts @@ -5,13 +5,10 @@ * 2.0. */ -export { - TagSelector, +export type { TagSelectorProps, - TagList, TagListProps, - TagBadge, TagBadgeProps, - TagSearchBarOption, TagSearchBarOptionProps, } from './base'; +export { TagSelector, TagList, TagBadge, TagSearchBarOption } from './base'; diff --git a/x-pack/plugins/saved_objects_tagging/public/index.ts b/x-pack/plugins/saved_objects_tagging/public/index.ts index c110e629f0ca3a..1da147f4f168a2 100644 --- a/x-pack/plugins/saved_objects_tagging/public/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/index.ts @@ -8,8 +8,8 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { SavedObjectTaggingPlugin } from './plugin'; -export { SavedObjectTaggingPluginStart } from './types'; -export { Tag } from '../common'; +export type { SavedObjectTaggingPluginStart } from './types'; +export type { Tag } from '../common'; export const plugin = (initializerContext: PluginInitializerContext) => new SavedObjectTaggingPlugin(initializerContext); diff --git a/x-pack/plugins/saved_objects_tagging/public/management/actions/index.ts b/x-pack/plugins/saved_objects_tagging/public/management/actions/index.ts index a18ef5afd445f1..5503cec4af9dc3 100644 --- a/x-pack/plugins/saved_objects_tagging/public/management/actions/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/management/actions/index.ts @@ -14,7 +14,7 @@ import { getDeleteAction } from './delete'; import { getEditAction } from './edit'; import { getAssignAction } from './assign'; -export { TagAction } from './types'; +export type { TagAction } from './types'; interface GetActionsOptions { core: CoreStart; diff --git a/x-pack/plugins/saved_objects_tagging/public/services/assignments/index.ts b/x-pack/plugins/saved_objects_tagging/public/services/assignments/index.ts index 8b26da9cc5674e..763c70a93ab410 100644 --- a/x-pack/plugins/saved_objects_tagging/public/services/assignments/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/services/assignments/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ITagAssignmentService, TagAssignmentService } from './assignment_service'; +export type { ITagAssignmentService } from './assignment_service'; +export { TagAssignmentService } from './assignment_service'; diff --git a/x-pack/plugins/saved_objects_tagging/public/services/index.ts b/x-pack/plugins/saved_objects_tagging/public/services/index.ts index 1909aaea5c4ee8..29d5ad7a7e0276 100644 --- a/x-pack/plugins/saved_objects_tagging/public/services/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/services/index.ts @@ -5,13 +5,12 @@ * 2.0. */ -export { +export type { ITagInternalClient, - TagsCache, ITagsCache, - TagsClient, ITagsChangeListener, - isServerValidationError, TagServerValidationError, } from './tags'; -export { TagAssignmentService, ITagAssignmentService } from './assignments'; +export { TagsCache, TagsClient, isServerValidationError } from './tags'; +export type { ITagAssignmentService } from './assignments'; +export { TagAssignmentService } from './assignments'; diff --git a/x-pack/plugins/saved_objects_tagging/public/services/tags/index.ts b/x-pack/plugins/saved_objects_tagging/public/services/tags/index.ts index 4b4e6d6c841231..b69f7124b0c8f7 100644 --- a/x-pack/plugins/saved_objects_tagging/public/services/tags/index.ts +++ b/x-pack/plugins/saved_objects_tagging/public/services/tags/index.ts @@ -5,6 +5,9 @@ * 2.0. */ -export { TagsClient, ITagInternalClient } from './tags_client'; -export { TagsCache, ITagsChangeListener, ITagsCache } from './tags_cache'; -export { isServerValidationError, TagServerValidationError } from './errors'; +export type { ITagInternalClient } from './tags_client'; +export { TagsClient } from './tags_client'; +export type { ITagsChangeListener, ITagsCache } from './tags_cache'; +export { TagsCache } from './tags_cache'; +export type { TagServerValidationError } from './errors'; +export { isServerValidationError } from './errors'; diff --git a/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_cache.ts b/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_cache.ts index 1d8fda1e5912d5..15d207aca47c05 100644 --- a/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_cache.ts +++ b/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_cache.ts @@ -11,7 +11,7 @@ import { takeUntil } from 'rxjs/operators'; import { ITagsCache } from '../../../../../../src/plugins/saved_objects_tagging_oss/public'; import { Tag, TagAttributes } from '../../../common/types'; -export { ITagsCache }; +export type { ITagsCache }; export interface ITagsChangeListener { onDelete: (id: string) => void; diff --git a/x-pack/plugins/saved_objects_tagging/server/services/assignments/index.ts b/x-pack/plugins/saved_objects_tagging/server/services/assignments/index.ts index c465f89369a1e1..7bf69b6c02304d 100644 --- a/x-pack/plugins/saved_objects_tagging/server/services/assignments/index.ts +++ b/x-pack/plugins/saved_objects_tagging/server/services/assignments/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { AssignmentService, IAssignmentService } from './assignment_service'; +export type { IAssignmentService } from './assignment_service'; +export { AssignmentService } from './assignment_service'; export { AssignmentError } from './errors'; diff --git a/x-pack/plugins/saved_objects_tagging/server/services/index.ts b/x-pack/plugins/saved_objects_tagging/server/services/index.ts index 695d90e496f603..36c5e32b816c69 100644 --- a/x-pack/plugins/saved_objects_tagging/server/services/index.ts +++ b/x-pack/plugins/saved_objects_tagging/server/services/index.ts @@ -6,4 +6,5 @@ */ export { TagsClient, savedObjectToTag, TagValidationError } from './tags'; -export { IAssignmentService, AssignmentService, AssignmentError } from './assignments'; +export type { IAssignmentService } from './assignments'; +export { AssignmentService, AssignmentError } from './assignments'; diff --git a/x-pack/plugins/searchprofiler/common/index.ts b/x-pack/plugins/searchprofiler/common/index.ts index 5b4f922231af30..154846bb3daa6c 100644 --- a/x-pack/plugins/searchprofiler/common/index.ts +++ b/x-pack/plugins/searchprofiler/common/index.ts @@ -7,4 +7,4 @@ export { PLUGIN } from './constants'; -export { LicenseStatus } from './types'; +export type { LicenseStatus } from './types'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/index.ts b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/index.ts index a48ee9be0c0d1a..3e1b4c208f1578 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/index.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { HighlightDetailsFlyout, Props } from './highlight_details_flyout'; +export type { Props } from './highlight_details_flyout'; +export { HighlightDetailsFlyout } from './highlight_details_flyout'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/index.ts b/x-pack/plugins/searchprofiler/public/application/components/index.ts index 86f7e2b6af5574..ea5ba226fc5f60 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/index.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/index.ts @@ -7,7 +7,8 @@ export { SearchProfilerTabs } from './searchprofiler_tabs'; export { LicenseWarningNotice } from './license_warning_notice'; -export { ProfileTree, OnHighlightChangeArgs } from './profile_tree'; +export type { OnHighlightChangeArgs } from './profile_tree'; +export { ProfileTree } from './profile_tree'; export { HighlightDetailsFlyout } from './highlight_details_flyout'; export { ProfileLoadingPlaceholder } from './profile_loading_placeholder'; export { EmptyTreePlaceHolder } from './empty_tree_placeholder'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/percentage_badge/_percentage_badge.scss b/x-pack/plugins/searchprofiler/public/application/components/percentage_badge/_percentage_badge.scss index 8c9f5d8ff62538..f0dfb46cff0a0c 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/percentage_badge/_percentage_badge.scss +++ b/x-pack/plugins/searchprofiler/public/application/components/percentage_badge/_percentage_badge.scss @@ -3,7 +3,7 @@ display: block; background-image: linear-gradient( - to left, + to right, $color 0%, $color var(--prfDevToolProgressPercentage, auto), $euiColorLightestShade var(--prfDevToolProgressPercentage, auto), diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts index 3d90bf26705251..5d8be480411769 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { Editor, EditorInstance } from './editor'; +export type { EditorInstance } from './editor'; +export { Editor } from './editor'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/index.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/index.ts index eb1eb29d7d09b1..8768c5b8b85f0c 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/index.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/index.ts @@ -6,4 +6,4 @@ */ export { ProfileTree } from './profile_tree'; -export { OnHighlightChangeArgs } from './highlight_context'; +export type { OnHighlightChangeArgs } from './highlight_context'; diff --git a/x-pack/plugins/searchprofiler/public/application/hooks/use_request_profile.ts b/x-pack/plugins/searchprofiler/public/application/hooks/use_request_profile.ts index aba51c5aedeccd..c27ca90e6e2f28 100644 --- a/x-pack/plugins/searchprofiler/public/application/hooks/use_request_profile.ts +++ b/x-pack/plugins/searchprofiler/public/application/hooks/use_request_profile.ts @@ -66,7 +66,10 @@ export const useRequestProfile = () => { } try { - const resp = await http.post('../api/searchprofiler/profile', { + const resp = await http.post< + | { ok: true; resp: { profile: { shards: ShardSerialized[] } } } + | { ok: false; err: { msg: string } } + >('../api/searchprofiler/profile', { body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json' }, }); diff --git a/x-pack/plugins/searchprofiler/public/application/store/index.ts b/x-pack/plugins/searchprofiler/public/application/store/index.ts index ce5a6fe832b8cc..ed350a47181d08 100644 --- a/x-pack/plugins/searchprofiler/public/application/store/index.ts +++ b/x-pack/plugins/searchprofiler/public/application/store/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { useStore, State } from './store'; -export { Action } from './reducer'; +export type { State } from './store'; +export { useStore } from './store'; +export type { Action } from './reducer'; diff --git a/x-pack/plugins/security/common/licensing/index.ts b/x-pack/plugins/security/common/licensing/index.ts index ec82007524fa4f..48329aeb999250 100644 --- a/x-pack/plugins/security/common/licensing/index.ts +++ b/x-pack/plugins/security/common/licensing/index.ts @@ -5,6 +5,7 @@ * 2.0. */ -export { SecurityLicenseService, SecurityLicense } from './license_service'; +export type { SecurityLicense } from './license_service'; +export { SecurityLicenseService } from './license_service'; -export { LoginLayout, SecurityLicenseFeatures } from './license_features'; +export type { LoginLayout, SecurityLicenseFeatures } from './license_features'; diff --git a/x-pack/plugins/security/common/licensing/license_features.ts b/x-pack/plugins/security/common/licensing/license_features.ts index ac80c89ae7be39..aa0bd7f1f11101 100644 --- a/x-pack/plugins/security/common/licensing/license_features.ts +++ b/x-pack/plugins/security/common/licensing/license_features.ts @@ -44,12 +44,6 @@ export interface SecurityLicenseFeatures { */ readonly allowAuditLogging: boolean; - /** - * Indicates whether we allow logging of legacy audit events. - * @deprecated - */ - readonly allowLegacyAuditLogging: boolean; - /** * Indicates whether we allow users to define document level security in roles. */ diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index cdc80c1a038f15..ca38a623bba37f 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -28,7 +28,6 @@ describe('license features', function () { allowRbac: false, allowSubFeaturePrivileges: false, allowAuditLogging: false, - allowLegacyAuditLogging: false, }); }); @@ -51,7 +50,6 @@ describe('license features', function () { allowRbac: false, allowSubFeaturePrivileges: false, allowAuditLogging: false, - allowLegacyAuditLogging: false, }); }); @@ -73,7 +71,6 @@ describe('license features', function () { Object { "allowAccessAgreement": false, "allowAuditLogging": false, - "allowLegacyAuditLogging": false, "allowLogin": false, "allowRbac": false, "allowRoleDocumentLevelSecurity": false, @@ -95,7 +92,6 @@ describe('license features', function () { Object { "allowAccessAgreement": true, "allowAuditLogging": true, - "allowLegacyAuditLogging": true, "allowLogin": true, "allowRbac": true, "allowRoleDocumentLevelSecurity": true, @@ -134,7 +130,6 @@ describe('license features', function () { allowRbac: true, allowSubFeaturePrivileges: false, allowAuditLogging: false, - allowLegacyAuditLogging: false, }); expect(getFeatureSpy).toHaveBeenCalledTimes(1); expect(getFeatureSpy).toHaveBeenCalledWith('security'); @@ -160,32 +155,6 @@ describe('license features', function () { allowRbac: false, allowSubFeaturePrivileges: false, allowAuditLogging: false, - allowLegacyAuditLogging: false, - }); - }); - - it('should allow all basic features for standard license', () => { - const mockRawLicense = licenseMock.createLicense({ - license: { mode: 'standard', type: 'standard' }, - features: { security: { isEnabled: true, isAvailable: true } }, - }); - - const serviceSetup = new SecurityLicenseService().setup({ - license$: of(mockRawLicense), - }); - expect(serviceSetup.license.isLicenseAvailable()).toEqual(true); - expect(serviceSetup.license.getFeatures()).toEqual({ - showLogin: true, - allowLogin: true, - showLinks: true, - showRoleMappingsManagement: false, - allowAccessAgreement: false, - allowRoleDocumentLevelSecurity: false, - allowRoleFieldLevelSecurity: false, - allowRbac: true, - allowSubFeaturePrivileges: false, - allowAuditLogging: false, - allowLegacyAuditLogging: true, }); }); @@ -210,7 +179,6 @@ describe('license features', function () { allowRbac: true, allowSubFeaturePrivileges: true, allowAuditLogging: true, - allowLegacyAuditLogging: true, }); }); @@ -235,7 +203,6 @@ describe('license features', function () { allowRbac: true, allowSubFeaturePrivileges: true, allowAuditLogging: true, - allowLegacyAuditLogging: true, }); }); }); diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 51093428e84a03..fdc99c3923983a 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -81,7 +81,6 @@ export class SecurityLicenseService { showRoleMappingsManagement: false, allowAccessAgreement: false, allowAuditLogging: false, - allowLegacyAuditLogging: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, @@ -101,7 +100,6 @@ export class SecurityLicenseService { showRoleMappingsManagement: false, allowAccessAgreement: false, allowAuditLogging: false, - allowLegacyAuditLogging: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, @@ -109,7 +107,6 @@ export class SecurityLicenseService { }; } - const isLicenseStandardOrBetter = rawLicense.hasAtLeast('standard'); const isLicenseGoldOrBetter = rawLicense.hasAtLeast('gold'); const isLicensePlatinumOrBetter = rawLicense.hasAtLeast('platinum'); return { @@ -119,7 +116,6 @@ export class SecurityLicenseService { showRoleMappingsManagement: isLicenseGoldOrBetter, allowAccessAgreement: isLicenseGoldOrBetter, allowAuditLogging: isLicenseGoldOrBetter, - allowLegacyAuditLogging: isLicenseStandardOrBetter, allowSubFeaturePrivileges: isLicenseGoldOrBetter, // Only platinum and trial licenses are compliant with field- and document-level security. allowRoleDocumentLevelSecurity: isLicensePlatinumOrBetter, diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index 082e6bdc12cd07..bc1666af3200cc 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -5,17 +5,18 @@ * 2.0. */ -export { ApiKey, ApiKeyToInvalidate, ApiKeyRoleDescriptors } from './api_key'; -export { User, EditUser, getUserDisplayName } from './user'; -export { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; -export { AuthenticationProvider, shouldProviderUseLoginForm } from './authentication_provider'; -export { BuiltinESPrivileges } from './builtin_es_privileges'; -export { RawKibanaPrivileges, RawKibanaFeaturePrivileges } from './raw_kibana_privileges'; -export { FeaturesPrivileges } from './features_privileges'; +export type { ApiKey, ApiKeyToInvalidate, ApiKeyRoleDescriptors } from './api_key'; +export type { User, EditUser } from './user'; +export { getUserDisplayName } from './user'; +export type { AuthenticatedUser } from './authenticated_user'; +export { canUserChangePassword } from './authenticated_user'; +export type { AuthenticationProvider } from './authentication_provider'; +export { shouldProviderUseLoginForm } from './authentication_provider'; +export type { BuiltinESPrivileges } from './builtin_es_privileges'; +export type { RawKibanaPrivileges, RawKibanaFeaturePrivileges } from './raw_kibana_privileges'; +export type { FeaturesPrivileges } from './features_privileges'; +export type { Role, RoleIndexPrivilege, RoleKibanaPrivilege } from './role'; export { - Role, - RoleIndexPrivilege, - RoleKibanaPrivilege, copyRole, isRoleDeprecated, isRoleReadOnly, @@ -26,14 +27,14 @@ export { prepareRoleClone, getExtendedRoleDeprecationNotice, } from './role'; -export { +export type { InlineRoleTemplate, StoredRoleTemplate, InvalidRoleTemplate, RoleTemplate, RoleMapping, } from './role_mapping'; -export { +export type { PrivilegeDeprecationsRolesByFeatureIdRequest, PrivilegeDeprecationsRolesByFeatureIdResponse, PrivilegeDeprecationsService, diff --git a/x-pack/plugins/security/public/authentication/index.ts b/x-pack/plugins/security/public/authentication/index.ts index 50d6b0c74376e7..dd7cb006d879e3 100644 --- a/x-pack/plugins/security/public/authentication/index.ts +++ b/x-pack/plugins/security/public/authentication/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -export { - AuthenticationService, +export type { AuthenticationServiceSetup, AuthenticationServiceStart, } from './authentication_service'; +export { AuthenticationService } from './authentication_service'; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index e8d5f739821ed6..55925e142ff24b 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -7,15 +7,20 @@ import type { PluginInitializer, PluginInitializerContext } from 'src/core/public'; -import type { PluginSetupDependencies, PluginStartDependencies } from './plugin'; -import { SecurityPlugin, SecurityPluginSetup, SecurityPluginStart } from './plugin'; +import type { + PluginSetupDependencies, + PluginStartDependencies, + SecurityPluginSetup, + SecurityPluginStart, +} from './plugin'; +import { SecurityPlugin } from './plugin'; -export { SecurityPluginSetup, SecurityPluginStart }; -export { AuthenticatedUser } from '../common/model'; -export { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing'; -export { UserMenuLink, SecurityNavControlServiceStart } from '../public/nav_control'; +export type { SecurityPluginSetup, SecurityPluginStart }; +export type { AuthenticatedUser } from '../common/model'; +export type { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing'; +export type { UserMenuLink, SecurityNavControlServiceStart } from '../public/nav_control'; -export { AuthenticationServiceStart, AuthenticationServiceSetup } from './authentication'; +export type { AuthenticationServiceStart, AuthenticationServiceSetup } from './authentication'; export const plugin: PluginInitializer< SecurityPluginSetup, diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx index 72fd79805f9703..6e3de061fd191b 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx @@ -5,13 +5,7 @@ * 2.0. */ -import { - fireEvent, - render, - waitFor, - waitForElementToBeRemoved, - within, -} from '@testing-library/react'; +import { render } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React from 'react'; @@ -22,60 +16,75 @@ import { Providers } from '../api_keys_management_app'; import { apiKeysAPIClientMock } from '../index.mock'; import { APIKeysGridPage } from './api_keys_grid_page'; -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ - htmlIdGenerator: () => () => `id-${Math.random()}`, -})); +/* + * Note to engineers + * we moved these 4 tests below to "x-pack/test/functional/apps/api_keys/home_page.ts": + * 1-"creates API key when submitting form, redirects back and displays base64" + * 2-"creates API key with optional expiration, redirects back and displays base64" + * 3-"deletes multiple api keys using bulk select" + * 4-"deletes api key using cta button" + * to functional tests to avoid flakyness + */ -jest.setTimeout(15000); +describe('APIKeysGridPage', () => { + // We are spying on the console.error to avoid react to throw error + // in our test "displays error when fetching API keys fails" + // since we are using EuiErrorBoundary and react will console.error any errors + const consoleWarnMock = jest.spyOn(console, 'error').mockImplementation(); -const coreStart = coreMock.createStart(); + const coreStart = coreMock.createStart(); + const apiClientMock = apiKeysAPIClientMock.create(); + const { authc } = securityMock.createSetup(); -const apiClientMock = apiKeysAPIClientMock.create(); -apiClientMock.checkPrivileges.mockResolvedValue({ - areApiKeysEnabled: true, - canManage: true, - isAdmin: true, -}); -apiClientMock.getApiKeys.mockResolvedValue({ - apiKeys: [ - { - creation: 1571322182082, - expiration: 1571408582082, - id: '0QQZ2m0BO2XZwgJFuWTT', - invalidated: false, - name: 'first-api-key', - realm: 'reserved', - username: 'elastic', - }, - { - creation: 1571322182082, - expiration: 1571408582082, - id: 'BO2XZwgJFuWTT0QQZ2m0', - invalidated: false, - name: 'second-api-key', - realm: 'reserved', - username: 'elastic', - }, - ], -}); + beforeEach(() => { + apiClientMock.checkPrivileges.mockClear(); + apiClientMock.getApiKeys.mockClear(); + coreStart.http.get.mockClear(); + coreStart.http.post.mockClear(); + authc.getCurrentUser.mockClear(); -const authc = securityMock.createSetup().authc; -authc.getCurrentUser.mockResolvedValue( - mockAuthenticatedUser({ - username: 'jdoe', - full_name: '', - email: '', - enabled: true, - roles: ['superuser'], - }) -); + apiClientMock.checkPrivileges.mockResolvedValue({ + areApiKeysEnabled: true, + canManage: true, + isAdmin: true, + }); + apiClientMock.getApiKeys.mockResolvedValue({ + apiKeys: [ + { + creation: 1571322182082, + expiration: 1571408582082, + id: '0QQZ2m0BO2XZwgJFuWTT', + invalidated: false, + name: 'first-api-key', + realm: 'reserved', + username: 'elastic', + }, + { + creation: 1571322182082, + expiration: 1571408582082, + id: 'BO2XZwgJFuWTT0QQZ2m0', + invalidated: false, + name: 'second-api-key', + realm: 'reserved', + username: 'elastic', + }, + ], + }); -// FLAKY: https://github.com/elastic/kibana/issues/97085 -describe.skip('APIKeysGridPage', () => { + authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser({ + username: 'jdoe', + full_name: '', + email: '', + enabled: true, + roles: ['superuser'], + }) + ); + }); it('loads and displays API keys', async () => { const history = createMemoryHistory({ initialEntries: ['/'] }); - const { getByText } = render( + const { findByText } = render( { ); - await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); - getByText(/first-api-key/); - getByText(/second-api-key/); + expect(await findByText(/Loading API keys/)).not.toBeInTheDocument(); + await findByText(/first-api-key/); + await findByText(/second-api-key/); + }); + + afterAll(() => { + // Let's make sure we restore everything just in case + consoleWarnMock.mockRestore(); }); it('displays callout when API keys are disabled', async () => { @@ -98,7 +112,7 @@ describe.skip('APIKeysGridPage', () => { isAdmin: true, }); - const { getByText } = render( + const { findByText } = render( { ); - await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); - getByText(/API keys not enabled/); + expect(await findByText(/Loading API keys/)).not.toBeInTheDocument(); + await findByText(/API keys not enabled/); }); it('displays error when user does not have required permissions', async () => { @@ -120,7 +134,7 @@ describe.skip('APIKeysGridPage', () => { isAdmin: false, }); - const { getByText } = render( + const { findByText } = render( { ); - await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); - getByText(/You need permission to manage API keys/); + expect(await findByText(/Loading API keys/)).not.toBeInTheDocument(); + await findByText(/You need permission to manage API keys/); }); it('displays error when fetching API keys fails', async () => { apiClientMock.getApiKeys.mockRejectedValueOnce({ - body: { error: 'Internal Server Error', message: '', statusCode: 500 }, - }); - const history = createMemoryHistory({ initialEntries: ['/'] }); - - const { getByText } = render( - - - - ); - - await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); - getByText(/Could not load API keys/); - }); - - it('creates API key when submitting form, redirects back and displays base64', async () => { - const history = createMemoryHistory({ initialEntries: ['/create'] }); - coreStart.http.get.mockResolvedValue([{ name: 'superuser' }]); - coreStart.http.post.mockResolvedValue({ id: '1D', api_key: 'AP1_K3Y' }); - - const { findByRole, findByDisplayValue } = render( - - - - ); - expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role'); - - const dialog = await findByRole('dialog'); - - fireEvent.click(await findByRole('button', { name: 'Create API key' })); - - const alert = await findByRole('alert'); - within(alert).getByText(/Enter a name/i); - - fireEvent.change(await within(dialog).findByLabelText('Name'), { - target: { value: 'Test' }, - }); - - fireEvent.click(await findByRole('button', { name: 'Create API key' })); - - await waitFor(() => { - expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key', { - body: JSON.stringify({ name: 'Test' }), - }); - expect(history.location.pathname).toBe('/'); - }); - - await findByDisplayValue(btoa('1D:AP1_K3Y')); - }); - - it('creates API key with optional expiration, redirects back and displays base64', async () => { - const history = createMemoryHistory({ initialEntries: ['/create'] }); - coreStart.http.get.mockResolvedValue([{ name: 'superuser' }]); - coreStart.http.post.mockResolvedValue({ id: '1D', api_key: 'AP1_K3Y' }); - - const { findByRole, findByDisplayValue } = render( - - - - ); - expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role'); - - const dialog = await findByRole('dialog'); - - fireEvent.change(await within(dialog).findByLabelText('Name'), { - target: { value: 'Test' }, - }); - - fireEvent.click(await within(dialog).findByLabelText('Expire after time')); - - fireEvent.click(await findByRole('button', { name: 'Create API key' })); - - const alert = await findByRole('alert'); - within(alert).getByText(/Enter a valid duration or disable this option\./i); - - fireEvent.change(await within(dialog).findByLabelText('Lifetime (days)'), { - target: { value: '12' }, - }); - - fireEvent.click(await findByRole('button', { name: 'Create API key' })); - - await waitFor(() => { - expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key', { - body: JSON.stringify({ name: 'Test', expiration: '12d' }), - }); - expect(history.location.pathname).toBe('/'); + body: { + error: 'Internal Server Error', + message: 'Internal Server Error', + statusCode: 500, + }, }); - - await findByDisplayValue(btoa('1D:AP1_K3Y')); - }); - - it('deletes api key using cta button', async () => { - const history = createMemoryHistory({ initialEntries: ['/'] }); - - const { findByRole, findAllByLabelText } = render( - - - - ); - - const [deleteButton] = await findAllByLabelText(/Delete/i); - fireEvent.click(deleteButton); - - const dialog = await findByRole('dialog'); - fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete API key' })); - - await waitFor(() => { - expect(apiClientMock.invalidateApiKeys).toHaveBeenLastCalledWith( - [{ id: '0QQZ2m0BO2XZwgJFuWTT', name: 'first-api-key' }], - true - ); - }); - }); - - it('deletes multiple api keys using bulk select', async () => { const history = createMemoryHistory({ initialEntries: ['/'] }); - const { findByRole, findAllByRole } = render( + const { findByText } = render( { ); - const deleteCheckboxes = await findAllByRole('checkbox', { name: 'Select this row' }); - deleteCheckboxes.forEach((checkbox) => fireEvent.click(checkbox)); - fireEvent.click(await findByRole('button', { name: 'Delete API keys' })); - - const dialog = await findByRole('dialog'); - fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete API keys' })); - - await waitFor(() => { - expect(apiClientMock.invalidateApiKeys).toHaveBeenLastCalledWith( - [ - { id: '0QQZ2m0BO2XZwgJFuWTT', name: 'first-api-key' }, - { id: 'BO2XZwgJFuWTT0QQZ2m0', name: 'second-api-key' }, - ], - true - ); - }); + expect(await findByText(/Loading API keys/)).not.toBeInTheDocument(); + await findByText(/Could not load API keys/); }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index dcf2a7bfe51653..a4843e4637d8bf 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -164,6 +164,7 @@ export class APIKeysGridPage extends Component { {...reactRouterNavigate(this.props.history, '/create')} fill iconType="plusInCircleFilled" + data-test-subj="apiKeysCreatePromptButton" > { {...reactRouterNavigate(this.props.history, '/create')} fill iconType="plusInCircleFilled" + data-test-subj="apiKeysCreateTableButton" > { color: 'danger', onClick: (item) => invalidateApiKeyPrompt([{ id: item.id, name: item.name }], this.onApiKeysInvalidated), + 'data-test-subj': 'apiKeysTableDeleteAction', }, ], }, diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx index e1ffc3b4b35150..f2fa6f7de468e5 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx @@ -202,6 +202,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ isInvalid={form.touched.name && !!form.errors.name} inputRef={firstFieldRef} fullWidth + data-test-subj="apiKeyNameInput" /> @@ -258,6 +259,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ )} checked={!!form.values.customExpiration} onChange={(e) => form.setValue('customExpiration', e.target.checked)} + data-test-subj="apiKeyCustomExpirationSwitch" /> {form.values.customExpiration && ( <> @@ -284,6 +286,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ defaultValue={form.values.expiration} isInvalid={form.touched.expiration && !!form.errors.expiration} fullWidth + data-test-subj="apiKeyCustomExpirationInput" /> diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts index dc99861ce0a8db..3af1e9d37c17d0 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { InvalidateProvider, InvalidateApiKeys } from './invalidate_provider'; +export type { InvalidateApiKeys } from './invalidate_provider'; +export { InvalidateProvider } from './invalidate_provider'; diff --git a/x-pack/plugins/security/public/management/role_mappings/model/index.ts b/x-pack/plugins/security/public/management/role_mappings/model/index.ts index 25e66bda350349..d80eb4d7d6943f 100644 --- a/x-pack/plugins/security/public/management/role_mappings/model/index.ts +++ b/x-pack/plugins/security/public/management/role_mappings/model/index.ts @@ -11,6 +11,7 @@ export { Rule } from './rule'; export { RuleGroup } from './rule_group'; export { ExceptAllRule } from './except_all_rule'; export { ExceptAnyRule } from './except_any_rule'; -export { FieldRule, FieldRuleValue } from './field_rule'; +export type { FieldRuleValue } from './field_rule'; +export { FieldRule } from './field_rule'; export { generateRulesFromRaw } from './rule_builder'; export { RuleBuilderError } from './rule_builder_error'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index 4c71dcd935ff96..10e1729dbd34bf 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -224,7 +224,7 @@ function useRole( function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup) { const [spaces, setSpaces] = useState<{ enabled: boolean; list: Space[] } | null>(null); useEffect(() => { - http.get('/api/spaces/space').then( + http.get('/api/spaces/space').then( (fetchedSpaces) => setSpaces({ enabled: true, list: fetchedSpaces }), (err: IHttpFetchError) => { // Spaces plugin can be disabled and hence this endpoint can be unavailable. diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx index 5b5e74cb2c618f..b8e98042b1cffe 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx @@ -5,13 +5,7 @@ * 2.0. */ -import { - fireEvent, - render, - waitFor, - waitForElementToBeRemoved, - within, -} from '@testing-library/react'; +import { fireEvent, render, waitFor, within } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React from 'react'; @@ -34,13 +28,28 @@ const userMock = { roles: ['superuser'], }; -// Failing: See https://github.com/elastic/kibana/issues/115473 +// FLAKY: https://github.com/elastic/kibana/issues/115473 +// FLAKY: https://github.com/elastic/kibana/issues/115474 +// FLAKY: https://github.com/elastic/kibana/issues/116889 +// FLAKY: https://github.com/elastic/kibana/issues/117081 +// FLAKY: https://github.com/elastic/kibana/issues/116891 +// FLAKY: https://github.com/elastic/kibana/issues/116890 describe.skip('EditUserPage', () => { - it('warns when viewing deactivated user', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; + const coreStart = coreMock.createStart(); + let history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); + const authc = securityMock.createSetup().authc; + + beforeEach(() => { + history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); + authc.getCurrentUser.mockClear(); + coreStart.http.delete.mockClear(); + coreStart.http.get.mockClear(); + coreStart.http.post.mockClear(); + coreStart.notifications.toasts.addDanger.mockClear(); + coreStart.notifications.toasts.addSuccess.mockClear(); + }); + it('warns when viewing deactivated user', async () => { coreStart.http.get.mockResolvedValueOnce({ ...userMock, enabled: false, @@ -57,10 +66,6 @@ describe.skip('EditUserPage', () => { }); it('warns when viewing deprecated user', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce({ ...userMock, metadata: { @@ -82,14 +87,10 @@ describe.skip('EditUserPage', () => { fireEvent.click(await findByRole('button', { name: 'Back to users' })); - await waitFor(() => expect(history.location.pathname).toBe('/')); + expect(history.location.pathname).toBe('/'); }); it('warns when viewing built-in user', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce({ ...userMock, metadata: { _reserved: true, _deprecated: false }, @@ -106,14 +107,10 @@ describe.skip('EditUserPage', () => { fireEvent.click(await findByRole('button', { name: 'Back to users' })); - await waitFor(() => expect(history.location.pathname).toBe('/')); + expect(history.location.pathname).toBe('/'); }); it('warns when selecting deprecated role', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce({ ...userMock, enabled: false, @@ -140,10 +137,6 @@ describe.skip('EditUserPage', () => { }); it('updates user when submitting form and redirects back', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); coreStart.http.post.mockResolvedValueOnce({}); @@ -175,10 +168,6 @@ describe.skip('EditUserPage', () => { }); it('warns when user form submission fails', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); coreStart.http.post.mockRejectedValueOnce(new Error('Error message')); @@ -214,10 +203,6 @@ describe.skip('EditUserPage', () => { }); it('changes password of other user when submitting form and closes dialog', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); authc.getCurrentUser.mockResolvedValueOnce( @@ -225,24 +210,23 @@ describe.skip('EditUserPage', () => { ); coreStart.http.post.mockResolvedValueOnce({}); - const { getByRole, findByRole } = render( + const { findByRole } = render( ); fireEvent.click(await findByRole('button', { name: 'Change password' })); - - const dialog = getByRole('dialog'); + const dialog = await findByRole('dialog'); fireEvent.change(await within(dialog).findByLabelText('New password'), { target: { value: 'changeme' }, }); - fireEvent.change(within(dialog).getByLabelText('Confirm password'), { + fireEvent.change(await within(dialog).findByLabelText('Confirm password'), { target: { value: 'changeme' }, }); - fireEvent.click(within(dialog).getByRole('button', { name: 'Change password' })); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Change password' })); - await waitForElementToBeRemoved(() => getByRole('dialog')); + expect(await findByRole('dialog')).not.toBeInTheDocument(); expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/password', { body: JSON.stringify({ newPassword: 'changeme', @@ -251,23 +235,18 @@ describe.skip('EditUserPage', () => { }); it('changes password of current user when submitting form and closes dialog', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); authc.getCurrentUser.mockResolvedValueOnce(mockAuthenticatedUser(userMock)); coreStart.http.post.mockResolvedValueOnce({}); - const { getByRole, findByRole } = render( + const { findByRole } = render( ); fireEvent.click(await findByRole('button', { name: 'Change password' })); - const dialog = await findByRole('dialog'); fireEvent.change(await within(dialog).findByLabelText('Current password'), { target: { value: '123456' }, @@ -280,7 +259,7 @@ describe.skip('EditUserPage', () => { }); fireEvent.click(await within(dialog).findByRole('button', { name: 'Change password' })); - await waitForElementToBeRemoved(() => getByRole('dialog')); + expect(await findByRole('dialog')).not.toBeInTheDocument(); expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/password', { body: JSON.stringify({ newPassword: 'changeme', @@ -290,10 +269,6 @@ describe.skip('EditUserPage', () => { }); it('warns when change password form submission fails', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); authc.getCurrentUser.mockResolvedValueOnce( @@ -308,7 +283,6 @@ describe.skip('EditUserPage', () => { ); fireEvent.click(await findByRole('button', { name: 'Change password' })); - const dialog = await findByRole('dialog'); fireEvent.change(await within(dialog).findByLabelText('New password'), { target: { value: 'changeme' }, @@ -327,10 +301,6 @@ describe.skip('EditUserPage', () => { }); it('validates change password form', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); authc.getCurrentUser.mockResolvedValueOnce(mockAuthenticatedUser(userMock)); @@ -343,21 +313,17 @@ describe.skip('EditUserPage', () => { ); fireEvent.click(await findByRole('button', { name: 'Change password' })); - const dialog = await findByRole('dialog'); fireEvent.click(await within(dialog).findByRole('button', { name: 'Change password' })); - await within(dialog).findByText(/Enter your current password/i); await within(dialog).findByText(/Enter a new password/i); fireEvent.change(await within(dialog).findByLabelText('Current password'), { target: { value: 'changeme' }, }); - fireEvent.change(await within(dialog).findByLabelText('New password'), { target: { value: '111' }, }); - await within(dialog).findAllByText(/Password must be at least 6 characters/i); fireEvent.change(await within(dialog).findByLabelText('New password'), { @@ -366,44 +332,34 @@ describe.skip('EditUserPage', () => { fireEvent.change(await within(dialog).findByLabelText('Confirm password'), { target: { value: '111' }, }); - await within(dialog).findAllByText(/Passwords do not match/i); }); it('deactivates user when confirming and closes dialog', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); coreStart.http.post.mockResolvedValueOnce({}); - const { getByRole, findByRole } = render( + const { findByRole } = render( ); fireEvent.click(await findByRole('button', { name: 'Deactivate user' })); + const dialog = await findByRole('dialog'); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Deactivate user' })); - const dialog = getByRole('dialog'); - fireEvent.click(within(dialog).getByRole('button', { name: 'Deactivate user' })); - - await waitForElementToBeRemoved(() => getByRole('dialog')); + expect(await findByRole('dialog')).not.toBeInTheDocument(); expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/_disable'); }); it('activates user when confirming and closes dialog', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce({ ...userMock, enabled: false }); coreStart.http.get.mockResolvedValueOnce([]); coreStart.http.post.mockResolvedValueOnce({}); - const { getByRole, findAllByRole } = render( + const { findByRole, findAllByRole } = render( @@ -411,36 +367,30 @@ describe.skip('EditUserPage', () => { const [enableButton] = await findAllByRole('button', { name: 'Activate user' }); fireEvent.click(enableButton); + const dialog = await findByRole('dialog'); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Activate user' })); - const dialog = getByRole('dialog'); - fireEvent.click(within(dialog).getByRole('button', { name: 'Activate user' })); - - await waitForElementToBeRemoved(() => getByRole('dialog')); + expect(await findByRole('dialog')).not.toBeInTheDocument(); expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/_enable'); }); it('deletes user when confirming and redirects back', async () => { - const coreStart = coreMock.createStart(); - const history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); - const authc = securityMock.createSetup().authc; - coreStart.http.get.mockResolvedValueOnce(userMock); coreStart.http.get.mockResolvedValueOnce([]); coreStart.http.delete.mockResolvedValueOnce({}); - const { getByRole, findByRole } = render( + const { findByRole } = render( ); fireEvent.click(await findByRole('button', { name: 'Delete user' })); + const dialog = await findByRole('dialog'); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete user' })); - const dialog = getByRole('dialog'); - fireEvent.click(within(dialog).getByRole('button', { name: 'Delete user' })); - - expect(coreStart.http.delete).toHaveBeenLastCalledWith('/internal/security/users/jdoe'); await waitFor(() => { + expect(coreStart.http.delete).toHaveBeenLastCalledWith('/internal/security/users/jdoe'); expect(history.location.pathname).toBe('/'); }); }); diff --git a/x-pack/plugins/security/public/nav_control/index.ts b/x-pack/plugins/security/public/nav_control/index.ts index 5ec306fa971706..95331b7504070c 100644 --- a/x-pack/plugins/security/public/nav_control/index.ts +++ b/x-pack/plugins/security/public/nav_control/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { SecurityNavControlService, SecurityNavControlServiceStart } from './nav_control_service'; +export type { SecurityNavControlServiceStart } from './nav_control_service'; +export { SecurityNavControlService } from './nav_control_service'; export type { UserMenuLink } from './nav_control_component'; diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index 5ab79e72d72748..8e2b2a9a1f2228 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; -exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; diff --git a/x-pack/plugins/security/server/anonymous_access/index.ts b/x-pack/plugins/security/server/anonymous_access/index.ts index 2d86cb3a3e3b4f..6e41ab1bae7809 100644 --- a/x-pack/plugins/security/server/anonymous_access/index.ts +++ b/x-pack/plugins/security/server/anonymous_access/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { AnonymousAccessService, AnonymousAccessServiceStart } from './anonymous_access_service'; +export type { AnonymousAccessServiceStart } from './anonymous_access_service'; +export { AnonymousAccessService } from './anonymous_access_service'; diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index a4025b619365ff..9b275c4126a737 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -7,6 +7,7 @@ import type { EcsEventOutcome, EcsEventType, KibanaRequest, LogMeta } from 'src/core/server'; +import type { AuthenticationProvider } from '../../common/model'; import type { AuthenticationResult } from '../authentication/authentication_result'; /** @@ -130,6 +131,32 @@ export function userLoginEvent({ }; } +export interface AccessAgreementAcknowledgedParams { + username: string; + provider: AuthenticationProvider; +} + +export function accessAgreementAcknowledgedEvent({ + username, + provider, +}: AccessAgreementAcknowledgedParams): AuditEvent { + return { + message: `${username} acknowledged access agreement using ${provider.type} provider [name=${provider.name}].`, + event: { + action: 'access_agreement_acknowledged', + category: ['authentication'], + }, + user: { + name: username, + }, + kibana: { + space_id: undefined, // Ensure this does not get populated by audit service + authentication_provider: provider.name, + authentication_type: provider.type, + }, + }; +} + export enum SavedObjectAction { CREATE = 'saved_object_create', GET = 'saved_object_get', diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts index 07fa761dc9d851..493490a8e8b9f5 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -67,7 +67,6 @@ describe('#setup', () => { ).toMatchInlineSnapshot(` Object { "asScoped": [Function], - "getLogger": [Function], } `); audit.stop(); diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts index 1878138ea25924..fb03669ca0fc5c 100644 --- a/x-pack/plugins/security/server/audit/audit_service.ts +++ b/x-pack/plugins/security/server/audit/audit_service.ts @@ -25,20 +25,12 @@ import { httpRequestEvent } from './audit_events'; export const ECS_VERSION = '1.6.0'; export const RECORD_USAGE_INTERVAL = 60 * 60 * 1000; // 1 hour -/** - * @deprecated - */ -export interface LegacyAuditLogger { - log: (eventType: string, message: string, data?: Record) => void; -} - export interface AuditLogger { log: (event: AuditEvent | undefined) => void; } export interface AuditServiceSetup { asScoped: (request: KibanaRequest) => AuditLogger; - getLogger: (id?: string) => LegacyAuditLogger; } interface AuditServiceSetupParams { @@ -57,11 +49,11 @@ interface AuditServiceSetupParams { } export class AuditService { - private ecsLogger: Logger; + private logger: Logger; private usageIntervalId?: NodeJS.Timeout; - constructor(logger: Logger) { - this.ecsLogger = logger.get('ecs'); + constructor(_logger: Logger) { + this.logger = _logger.get('ecs'); } setup({ @@ -152,22 +144,12 @@ export class AuditService { }; if (filterEvent(meta, config.ignore_filters)) { const { message, ...eventMeta } = meta; - this.ecsLogger.info(message, eventMeta); + this.logger.info(message, eventMeta); } }; return { log }; }; - /** - * @deprecated - * Use `audit.asScoped(request)` method instead to create an audit logger - */ - const getLogger = (id?: string): LegacyAuditLogger => { - return { - log: (eventType: string, message: string, data?: Record) => {}, - }; - }; - http.registerOnPostAuth((request, response, t) => { if (request.auth.isAuthenticated) { asScoped(request).log(httpRequestEvent({ request })); @@ -175,7 +157,7 @@ export class AuditService { return t.next(); }); - return { asScoped, getLogger }; + return { asScoped }; } stop() { diff --git a/x-pack/plugins/security/server/audit/index.mock.ts b/x-pack/plugins/security/server/audit/index.mock.ts index 15fb4c42c35167..ce6885aee50def 100644 --- a/x-pack/plugins/security/server/audit/index.mock.ts +++ b/x-pack/plugins/security/server/audit/index.mock.ts @@ -6,17 +6,6 @@ */ import type { AuditService } from './audit_service'; -import type { SecurityAuditLogger } from './security_audit_logger'; - -export const securityAuditLoggerMock = { - create() { - return { - savedObjectsAuthorizationFailure: jest.fn(), - savedObjectsAuthorizationSuccess: jest.fn(), - accessAgreementAcknowledged: jest.fn(), - } as unknown as jest.Mocked; - }, -}; export const auditServiceMock = { create() { diff --git a/x-pack/plugins/security/server/audit/index.ts b/x-pack/plugins/security/server/audit/index.ts index c42022bc76aa9d..b859e773552a3e 100644 --- a/x-pack/plugins/security/server/audit/index.ts +++ b/x-pack/plugins/security/server/audit/index.ts @@ -5,14 +5,15 @@ * 2.0. */ -export { AuditService, AuditServiceSetup, AuditLogger, LegacyAuditLogger } from './audit_service'; +export type { AuditServiceSetup, AuditLogger } from './audit_service'; +export { AuditService } from './audit_service'; +export type { AuditEvent } from './audit_events'; export { - AuditEvent, userLoginEvent, + accessAgreementAcknowledgedEvent, httpRequestEvent, savedObjectEvent, spaceAuditEvent, SavedObjectAction, SpaceAuditAction, } from './audit_events'; -export { SecurityAuditLogger } from './security_audit_logger'; diff --git a/x-pack/plugins/security/server/audit/security_audit_logger.test.ts b/x-pack/plugins/security/server/audit/security_audit_logger.test.ts deleted file mode 100644 index aa32ee36b75bba..00000000000000 --- a/x-pack/plugins/security/server/audit/security_audit_logger.test.ts +++ /dev/null @@ -1,114 +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 { SecurityAuditLogger } from './security_audit_logger'; - -const createMockAuditLogger = () => { - return { - log: jest.fn(), - }; -}; - -describe(`#savedObjectsAuthorizationFailure`, () => { - test('logs via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - const types = ['foo-type-1', 'foo-type-2']; - const spaceIds = ['foo-space', 'bar-space']; - const missing = [ - { - spaceId: 'foo-space', - privilege: `saved_object:${types[0]}/${action}`, - }, - { - spaceId: 'foo-space', - privilege: `saved_object:${types[1]}/${action}`, - }, - ]; - const args = { - foo: 'bar', - baz: 'quz', - }; - - securityAuditLogger.savedObjectsAuthorizationFailure( - username, - action, - types, - spaceIds, - missing, - args - ); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'saved_objects_authorization_failure', - expect.any(String), - { - username, - action, - types, - spaceIds, - missing, - args, - } - ); - expect(auditLogger.log.mock.calls[0][1]).toMatchInlineSnapshot( - `"foo-user unauthorized to [foo-action] [foo-type-1,foo-type-2] in [foo-space,bar-space]: missing [(foo-space)saved_object:foo-type-1/foo-action,(foo-space)saved_object:foo-type-2/foo-action]"` - ); - }); -}); - -describe(`#savedObjectsAuthorizationSuccess`, () => { - test('logs via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - const types = ['foo-type-1', 'foo-type-2']; - const spaceIds = ['foo-space', 'bar-space']; - const args = { - foo: 'bar', - baz: 'quz', - }; - - securityAuditLogger.savedObjectsAuthorizationSuccess(username, action, types, spaceIds, args); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'saved_objects_authorization_success', - expect.any(String), - { - username, - action, - types, - spaceIds, - args, - } - ); - expect(auditLogger.log.mock.calls[0][1]).toMatchInlineSnapshot( - `"foo-user authorized to [foo-action] [foo-type-1,foo-type-2] in [foo-space,bar-space]"` - ); - }); -}); - -describe(`#accessAgreementAcknowledged`, () => { - test('logs via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(auditLogger); - const username = 'foo-user'; - const provider = { type: 'saml', name: 'saml1' }; - - securityAuditLogger.accessAgreementAcknowledged(username, provider); - - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - 'access_agreement_acknowledged', - 'foo-user acknowledged access agreement (saml/saml1).', - { username, provider } - ); - }); -}); diff --git a/x-pack/plugins/security/server/audit/security_audit_logger.ts b/x-pack/plugins/security/server/audit/security_audit_logger.ts deleted file mode 100644 index 280cc233fca17c..00000000000000 --- a/x-pack/plugins/security/server/audit/security_audit_logger.ts +++ /dev/null @@ -1,82 +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 type { AuthenticationProvider } from '../../common/model'; -import type { LegacyAuditLogger } from './audit_service'; - -/** - * @deprecated - */ -export class SecurityAuditLogger { - constructor(private readonly logger: LegacyAuditLogger) {} - - /** - * @deprecated - */ - savedObjectsAuthorizationFailure( - username: string, - action: string, - types: string[], - spaceIds: string[], - missing: Array<{ spaceId?: string; privilege: string }>, - args?: Record - ) { - const typesString = types.join(','); - const spacesString = spaceIds.length ? ` in [${spaceIds.join(',')}]` : ''; - const missingString = missing - .map(({ spaceId, privilege }) => `${spaceId ? `(${spaceId})` : ''}${privilege}`) - .join(','); - this.logger.log( - 'saved_objects_authorization_failure', - `${username} unauthorized to [${action}] [${typesString}]${spacesString}: missing [${missingString}]`, - { - username, - action, - types, - spaceIds, - missing, - args, - } - ); - } - - /** - * @deprecated - */ - savedObjectsAuthorizationSuccess( - username: string, - action: string, - types: string[], - spaceIds: string[], - args?: Record - ) { - const typesString = types.join(','); - const spacesString = spaceIds.length ? ` in [${spaceIds.join(',')}]` : ''; - this.logger.log( - 'saved_objects_authorization_success', - `${username} authorized to [${action}] [${typesString}]${spacesString}`, - { - username, - action, - types, - spaceIds, - args, - } - ); - } - - /** - * @deprecated - */ - accessAgreementAcknowledged(username: string, provider: AuthenticationProvider) { - this.logger.log( - 'access_agreement_acknowledged', - `${username} acknowledged access agreement (${provider.type}/${provider.name}).`, - { username, provider } - ); - } -} diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap index 8e6d6a26777876..62200cb288e09d 100644 --- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We couldn't log you in

We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

"`; +exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We couldn't log you in

We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

"`; diff --git a/x-pack/plugins/security/server/authentication/api_keys/index.ts b/x-pack/plugins/security/server/authentication/api_keys/index.ts index b14d09d5597009..44fe48f04debbf 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/index.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/index.ts @@ -5,11 +5,11 @@ * 2.0. */ -export { - APIKeys, +export type { CreateAPIKeyResult, InvalidateAPIKeyResult, CreateAPIKeyParams, InvalidateAPIKeysParams, GrantAPIKeyResult, } from './api_keys'; +export { APIKeys } from './api_keys'; diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index 3a11a21cfe1c36..ea95f308eb0462 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -35,8 +35,8 @@ import type { SecurityLicense } from '../../common/licensing'; import { licenseMock } from '../../common/licensing/index.mock'; import type { AuthenticatedUser } from '../../common/model'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; -import type { AuditServiceSetup, SecurityAuditLogger } from '../audit'; -import { auditServiceMock, securityAuditLoggerMock } from '../audit/index.mock'; +import type { AuditServiceSetup } from '../audit'; +import { auditServiceMock } from '../audit/index.mock'; import type { ConfigType } from '../config'; import { ConfigSchema, createConfig } from '../config'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; @@ -57,7 +57,6 @@ describe('AuthenticationService', () => { buildNumber: number; }; let mockStartAuthenticationParams: { - legacyAuditLogger: jest.Mocked; audit: jest.Mocked; config: ConfigType; loggers: LoggerFactory; @@ -86,7 +85,6 @@ describe('AuthenticationService', () => { const coreStart = coreMock.createStart(); mockStartAuthenticationParams = { - legacyAuditLogger: securityAuditLoggerMock.create(), audit: auditServiceMock.create(), config: createConfig( ConfigSchema.validate({ diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 538bc26e6ffe34..ac66e62e97c8af 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -19,7 +19,7 @@ import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../common/constants'; import type { SecurityLicense } from '../../common/licensing'; import type { AuthenticatedUser } from '../../common/model'; import { shouldProviderUseLoginForm } from '../../common/model'; -import type { AuditServiceSetup, SecurityAuditLogger } from '../audit'; +import type { AuditServiceSetup } from '../audit'; import type { ConfigType } from '../config'; import { getDetailedErrorMessage, getErrorStatusCode } from '../errors'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; @@ -44,7 +44,6 @@ interface AuthenticationServiceStartParams { http: Pick; config: ConfigType; clusterClient: IClusterClient; - legacyAuditLogger: SecurityAuditLogger; audit: AuditServiceSetup; featureUsageService: SecurityFeatureUsageServiceStart; session: PublicMethodsOf; @@ -224,7 +223,6 @@ export class AuthenticationService { clusterClient, featureUsageService, http, - legacyAuditLogger, loggers, session, }: AuthenticationServiceStartParams): InternalAuthenticationServiceStart { @@ -250,7 +248,6 @@ export class AuthenticationService { this.session = session; this.authenticator = new Authenticator({ - legacyAuditLogger, audit, loggers, clusterClient, diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 4e35b84a93119a..cc232298bc8788 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -27,7 +27,7 @@ import { import type { SecurityLicenseFeatures } from '../../common/licensing'; import { licenseMock } from '../../common/licensing/index.mock'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; -import { auditServiceMock, securityAuditLoggerMock } from '../audit/index.mock'; +import { auditServiceMock } from '../audit/index.mock'; import { ConfigSchema, createConfig } from '../config'; import { securityFeatureUsageServiceMock } from '../feature_usage/index.mock'; import type { SessionValue } from '../session_management'; @@ -48,7 +48,6 @@ function getMockOptions({ selector?: AuthenticatorOptions['config']['authc']['selector']; } = {}) { return { - legacyAuditLogger: securityAuditLoggerMock.create(), audit: auditServiceMock.create(), getCurrentUser: jest.fn(), clusterClient: elasticsearchServiceMock.createClusterClient(), @@ -1890,10 +1889,15 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessionValue: SessionValue; + const auditLogger = { + log: jest.fn(), + }; + beforeEach(() => { mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } } } }); mockSessionValue = sessionMock.createValue({ state: { authorization: 'Basic xxx' } }); mockOptions.session.get.mockResolvedValue(mockSessionValue); + mockOptions.audit.asScoped.mockReturnValue(auditLogger); mockOptions.getCurrentUser.mockReturnValue(mockAuthenticatedUser()); mockOptions.license.getFeatures.mockReturnValue({ allowAccessAgreement: true, @@ -1953,13 +1957,11 @@ describe('Authenticator', () => { accessAgreementAcknowledged: true, }); - expect(mockOptions.legacyAuditLogger.accessAgreementAcknowledged).toHaveBeenCalledTimes(1); - expect(mockOptions.legacyAuditLogger.accessAgreementAcknowledged).toHaveBeenCalledWith( - 'user', - { - type: 'basic', - name: 'basic1', - } + expect(auditLogger.log).toHaveBeenCalledTimes(1); + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: { action: 'access_agreement_acknowledged', category: ['authentication'] }, + }) ); expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).toHaveBeenCalledTimes( diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index f0ade974e4d56e..5608a355803385 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -19,8 +19,8 @@ import { import type { SecurityLicense } from '../../common/licensing'; import type { AuthenticatedUser, AuthenticationProvider } from '../../common/model'; import { shouldProviderUseLoginForm } from '../../common/model'; -import type { AuditServiceSetup, SecurityAuditLogger } from '../audit'; -import { userLoginEvent } from '../audit'; +import type { AuditServiceSetup } from '../audit'; +import { accessAgreementAcknowledgedEvent, userLoginEvent } from '../audit'; import type { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; @@ -77,7 +77,6 @@ export interface ProviderLoginAttempt { } export interface AuthenticatorOptions { - legacyAuditLogger: SecurityAuditLogger; audit: AuditServiceSetup; featureUsageService: SecurityFeatureUsageServiceStart; getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; @@ -465,9 +464,12 @@ export class Authenticator { accessAgreementAcknowledged: true, }); - this.options.legacyAuditLogger.accessAgreementAcknowledged( - currentUser.username, - existingSessionValue.provider + const auditLogger = this.options.audit.asScoped(request); + auditLogger.log( + accessAgreementAcknowledgedEvent({ + username: currentUser.username, + provider: existingSessionValue.provider, + }) ); this.options.featureUsageService.recordPreAccessAgreementUsage(); diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts index 1e46d2aaf560ed..f6f6f1da369527 100644 --- a/x-pack/plugins/security/server/authentication/index.ts +++ b/x-pack/plugins/security/server/authentication/index.ts @@ -6,11 +6,11 @@ */ export { canRedirectRequest } from './can_redirect_request'; -export { - AuthenticationService, +export type { AuthenticationServiceStart, InternalAuthenticationServiceStart, } from './authentication_service'; +export { AuthenticationService } from './authentication_service'; export { AuthenticationResult } from './authentication_result'; export { DeauthenticationResult } from './deauthentication_result'; export { diff --git a/x-pack/plugins/security/server/authentication/providers/index.ts b/x-pack/plugins/security/server/authentication/providers/index.ts index c589371331eafa..31f89889b38928 100644 --- a/x-pack/plugins/security/server/authentication/providers/index.ts +++ b/x-pack/plugins/security/server/authentication/providers/index.ts @@ -5,11 +5,8 @@ * 2.0. */ -export { - BaseAuthenticationProvider, - AuthenticationProviderOptions, - AuthenticationProviderSpecificOptions, -} from './base'; +export type { AuthenticationProviderOptions, AuthenticationProviderSpecificOptions } from './base'; +export { BaseAuthenticationProvider } from './base'; export { AnonymousAuthenticationProvider } from './anonymous'; export { BasicAuthenticationProvider } from './basic'; export { KerberosAuthenticationProvider } from './kerberos'; diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index 97cfcd47ade8d1..8b882c9a6b442c 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; diff --git a/x-pack/plugins/security/server/authorization/authorization_service.tsx b/x-pack/plugins/security/server/authorization/authorization_service.tsx index 72f2c9843daece..004c82d2c3f3f9 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.tsx +++ b/x-pack/plugins/security/server/authorization/authorization_service.tsx @@ -50,7 +50,7 @@ import { validateFeaturePrivileges } from './validate_feature_privileges'; import { validateReservedPrivileges } from './validate_reserved_privileges'; export { Actions } from './actions'; -export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges'; +export type { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges'; interface AuthorizationServiceSetupParams { packageVersion: string; diff --git a/x-pack/plugins/security/server/authorization/index.ts b/x-pack/plugins/security/server/authorization/index.ts index 221baa85a65f63..265ec2dce5a31e 100644 --- a/x-pack/plugins/security/server/authorization/index.ts +++ b/x-pack/plugins/security/server/authorization/index.ts @@ -6,11 +6,12 @@ */ export { Actions } from './actions'; -export { - AuthorizationService, +export type { AuthorizationServiceSetup, AuthorizationServiceSetupInternal, } from './authorization_service'; -export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges'; -export { CheckPrivilegesPayload } from './types'; -export { transformElasticsearchRoleToRole, ElasticsearchRole } from './roles'; +export { AuthorizationService } from './authorization_service'; +export type { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges'; +export type { CheckPrivilegesPayload } from './types'; +export type { ElasticsearchRole } from './roles'; +export { transformElasticsearchRoleToRole } from './roles'; diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts index 81d13390523014..d46f30ef53ab4d 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/index.ts @@ -14,13 +14,13 @@ import { FeaturePrivilegeApiBuilder } from './api'; import { FeaturePrivilegeAppBuilder } from './app'; import { FeaturePrivilegeCasesBuilder } from './cases'; import { FeaturePrivilegeCatalogueBuilder } from './catalogue'; -import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; +import type { FeaturePrivilegeBuilder } from './feature_privilege_builder'; import { FeaturePrivilegeManagementBuilder } from './management'; import { FeaturePrivilegeNavlinkBuilder } from './navlink'; import { FeaturePrivilegeSavedObjectBuilder } from './saved_object'; import { FeaturePrivilegeUIBuilder } from './ui'; -export { FeaturePrivilegeBuilder }; +export type { FeaturePrivilegeBuilder }; export const featurePrivilegeBuilderFactory = (actions: Actions): FeaturePrivilegeBuilder => { const builders = [ diff --git a/x-pack/plugins/security/server/authorization/privileges/index.ts b/x-pack/plugins/security/server/authorization/privileges/index.ts index 31c9cf2713c9d8..7e98730abd71ee 100644 --- a/x-pack/plugins/security/server/authorization/privileges/index.ts +++ b/x-pack/plugins/security/server/authorization/privileges/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { privilegesFactory, PrivilegesService } from './privileges'; +export type { PrivilegesService } from './privileges'; +export { privilegesFactory } from './privileges'; diff --git a/x-pack/plugins/security/server/authorization/roles/index.ts b/x-pack/plugins/security/server/authorization/roles/index.ts index a5047a1872c092..205c339b45fbc8 100644 --- a/x-pack/plugins/security/server/authorization/roles/index.ts +++ b/x-pack/plugins/security/server/authorization/roles/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { transformElasticsearchRoleToRole, ElasticsearchRole } from './elasticsearch_role'; +export type { ElasticsearchRole } from './elasticsearch_role'; +export { transformElasticsearchRoleToRole } from './elasticsearch_role'; diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts index 3c674de97ad8ef..7a85e614e4b623 100644 --- a/x-pack/plugins/security/server/config_deprecations.test.ts +++ b/x-pack/plugins/security/server/config_deprecations.test.ts @@ -191,68 +191,6 @@ describe('Config Deprecations', () => { `); }); - it('warns when using the legacy audit logger', () => { - const config = { - xpack: { - security: { - audit: { - enabled: true, - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.xpack.security.audit.appender).not.toBeDefined(); - expect(messages).toMatchInlineSnapshot(` - Array [ - "The legacy audit logger is deprecated in favor of the new ECS-compliant audit logger.", - ] - `); - }); - - it('does not warn when using the ECS audit logger', () => { - const config = { - xpack: { - security: { - audit: { - enabled: true, - appender: { - type: 'file', - fileName: './audit.log', - }, - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated).toEqual(config); - expect(messages).toHaveLength(0); - }); - - it('does not warn about using the legacy logger when using the ECS audit logger, even when using the deprecated ECS appender config', () => { - const config = { - xpack: { - security: { - audit: { - enabled: true, - appender: { - type: 'file', - path: './audit.log', - }, - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.xpack.security.audit.appender.path).not.toBeDefined(); - expect(migrated.xpack.security.audit.appender.fileName).toEqual('./audit.log'); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"xpack.security.audit.appender.path\\" has been replaced by \\"xpack.security.audit.appender.fileName\\"", - ] - `); - }); - it(`warns that 'authorization.legacyFallback.enabled' is unused`, () => { const config = { xpack: { diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts index 055818a159a791..8b778950036b5c 100644 --- a/x-pack/plugins/security/server/config_deprecations.ts +++ b/x-pack/plugins/security/server/config_deprecations.ts @@ -30,36 +30,12 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ unused('authorization.legacyFallback.enabled', { level: 'warning' }), unused('authc.saml.maxRedirectURLSize', { level: 'warning' }), - // Deprecation warning for the legacy audit logger. - (settings, fromPath, addDeprecation, { branch }) => { - const auditLoggingEnabled = settings?.xpack?.security?.audit?.enabled ?? false; - const legacyAuditLoggerEnabled = !settings?.xpack?.security?.audit?.appender; - if (auditLoggingEnabled && legacyAuditLoggerEnabled) { - addDeprecation({ - configPath: 'xpack.security.audit.appender', - title: i18n.translate('xpack.security.deprecations.auditLoggerTitle', { - defaultMessage: 'The legacy audit logger is deprecated', - }), - message: i18n.translate('xpack.security.deprecations.auditLoggerMessage', { - defaultMessage: - 'The legacy audit logger is deprecated in favor of the new ECS-compliant audit logger.', - }), - documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/security-settings-kb.html#audit-logging-settings`, - correctiveActions: { - manualSteps: [ - i18n.translate('xpack.security.deprecations.auditLogger.manualStepOneMessage', { - defaultMessage: - 'Declare an audit logger "appender" via "xpack.security.audit.appender" to enable the ECS audit logger.', - }), - ], - }, - }); - } - }, // Deprecation warning for the old array-based format of `xpack.security.authc.providers`. (settings, _fromPath, addDeprecation, { branch }) => { if (Array.isArray(settings?.xpack?.security?.authc?.providers)) { + // TODO: remove when docs support "main" + const docsBranch = branch === 'main' ? 'master' : 'main'; addDeprecation({ configPath: 'xpack.security.authc.providers', title: i18n.translate('xpack.security.deprecations.authcProvidersTitle', { @@ -69,7 +45,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ defaultMessage: 'Use the new object format instead of an array of provider types.', }), level: 'warning', - documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/security-settings-kb.html#authentication-security-settings`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/security-settings-kb.html#authentication-security-settings`, correctiveActions: { manualSteps: [ i18n.translate('xpack.security.deprecations.authcProviders.manualSteps1', { @@ -85,6 +61,9 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ } }, (settings, _fromPath, addDeprecation, { branch }) => { + // TODO: remove when docs support "main" + const docsBranch = branch === 'main' ? 'master' : 'main'; + const hasProviderType = (providerType: string) => { const providers = settings?.xpack?.security?.authc?.providers; if (Array.isArray(providers)) { @@ -112,7 +91,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ values: { tokenProvider }, }), level: 'warning', - documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/security-settings-kb.html#authentication-security-settings`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/security-settings-kb.html#authentication-security-settings`, correctiveActions: { manualSteps: [ i18n.translate('xpack.security.deprecations.basicAndTokenProviders.manualSteps1', { @@ -126,6 +105,8 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ } }, (settings, _fromPath, addDeprecation, { branch }) => { + // TODO: remove when docs support "main" + const docsBranch = branch === 'main' ? 'master' : 'main'; const samlProviders = (settings?.xpack?.security?.authc?.providers?.saml ?? {}) as Record< string, any @@ -145,7 +126,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ defaultMessage: 'This setting is no longer used.', }), level: 'warning', - documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/security-settings-kb.html#authentication-security-settings`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/security-settings-kb.html#authentication-security-settings`, correctiveActions: { manualSteps: [ i18n.translate('xpack.security.deprecations.maxRedirectURLSize.manualSteps1', { diff --git a/x-pack/plugins/security/server/deprecations/kibana_user_role.ts b/x-pack/plugins/security/server/deprecations/kibana_user_role.ts index ba32446611a629..9746597aa95b81 100644 --- a/x-pack/plugins/security/server/deprecations/kibana_user_role.ts +++ b/x-pack/plugins/security/server/deprecations/kibana_user_role.ts @@ -98,13 +98,16 @@ async function getUsersDeprecations( return []; } + // TODO: remove when docs support "main" + const docsBranch = packageInfo.branch === 'main' ? 'master' : packageInfo.branch; + return [ { title: getDeprecationTitle(), message: getDeprecationMessage(), level: 'warning', deprecationType: 'feature', - documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${packageInfo.branch}/built-in-roles.html`, + documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${docsBranch}/built-in-roles.html`, correctiveActions: { api: { method: 'POST', @@ -159,13 +162,16 @@ async function getRoleMappingsDeprecations( return []; } + // TODO: remove when docs support "main" + const docsBranch = packageInfo.branch === 'main' ? 'master' : packageInfo.branch; + return [ { title: getDeprecationTitle(), message: getDeprecationMessage(), level: 'warning', deprecationType: 'feature', - documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${packageInfo.branch}/built-in-roles.html`, + documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${docsBranch}/built-in-roles.html`, correctiveActions: { api: { method: 'POST', @@ -193,6 +199,9 @@ async function getRoleMappingsDeprecations( function deprecationError(packageInfo: PackageInfo, error: Error): DeprecationsDetails[] { const title = getDeprecationTitle(); + // TODO: remove when docs support "main" + const docsBranch = packageInfo.branch === 'main' ? 'master' : packageInfo.branch; + if (getErrorStatusCode(error) === 403) { return [ { @@ -202,7 +211,7 @@ function deprecationError(packageInfo: PackageInfo, error: Error): DeprecationsD message: i18n.translate('xpack.security.deprecations.kibanaUser.forbiddenErrorMessage', { defaultMessage: 'You do not have enough permissions to fix this deprecation.', }), - documentationUrl: `https://www.elastic.co/guide/en/kibana/${packageInfo.branch}/xpack-security.html#_required_permissions_7`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/xpack-security.html#_required_permissions_7`, correctiveActions: { manualSteps: [ i18n.translate( diff --git a/x-pack/plugins/security/server/elasticsearch/index.ts b/x-pack/plugins/security/server/elasticsearch/index.ts index 1c3bdd3054dd74..46acb874f7fc28 100644 --- a/x-pack/plugins/security/server/elasticsearch/index.ts +++ b/x-pack/plugins/security/server/elasticsearch/index.ts @@ -8,8 +8,8 @@ import type { AuthenticatedUser } from '../../common/model'; export type AuthenticationInfo = Omit; -export { - ElasticsearchService, +export type { ElasticsearchServiceStart, OnlineStatusRetryScheduler, } from './elasticsearch_service'; +export { ElasticsearchService } from './elasticsearch_service'; diff --git a/x-pack/plugins/security/server/feature_usage/index.ts b/x-pack/plugins/security/server/feature_usage/index.ts index 81d4336ef7bbbf..b58dacfa97d3a8 100644 --- a/x-pack/plugins/security/server/feature_usage/index.ts +++ b/x-pack/plugins/security/server/feature_usage/index.ts @@ -5,7 +5,5 @@ * 2.0. */ -export { - SecurityFeatureUsageService, - SecurityFeatureUsageServiceStart, -} from './feature_usage_service'; +export type { SecurityFeatureUsageServiceStart } from './feature_usage_service'; +export { SecurityFeatureUsageService } from './feature_usage_service'; diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index b3bc85676b07ad..10b1e8167c14aa 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -29,11 +29,11 @@ export type { } from './authentication'; export type { CheckPrivilegesPayload } from './authorization'; export type AuthorizationServiceSetup = SecurityPluginStart['authz']; -export { LegacyAuditLogger, AuditLogger, AuditEvent } from './audit'; +export type { AuditLogger, AuditEvent } from './audit'; export type { SecurityPluginSetup, SecurityPluginStart }; export type { AuthenticatedUser } from '../common/model'; export { ROUTE_TAG_CAN_REDIRECT } from './routes/tags'; -export { AuditServiceSetup } from './audit'; +export type { AuditServiceSetup } from './audit'; export const config: PluginConfigDescriptor> = { schema: ConfigSchema, diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 4784e14a11fb48..3d43129b638098 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -67,7 +67,6 @@ describe('Security Plugin', () => { Object { "audit": Object { "asScoped": [Function], - "getLogger": [Function], }, "authc": Object { "getCurrentUser": [Function], diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 0ebdae44c865c8..80082e9b7dbce3 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -31,7 +31,7 @@ import { SecurityLicenseService } from '../common/licensing'; import type { AnonymousAccessServiceStart } from './anonymous_access'; import { AnonymousAccessService } from './anonymous_access'; import type { AuditServiceSetup } from './audit'; -import { AuditService, SecurityAuditLogger } from './audit'; +import { AuditService } from './audit'; import type { AuthenticationServiceStart, InternalAuthenticationServiceStart, @@ -278,7 +278,6 @@ export class SecurityPlugin }); setupSavedObjects({ - legacyAuditLogger: new SecurityAuditLogger(this.auditSetup.getLogger()), audit: this.auditSetup, authz: this.authorizationSetup, savedObjects: core.savedObjects, @@ -307,7 +306,6 @@ export class SecurityPlugin return Object.freeze({ audit: { asScoped: this.auditSetup.asScoped, - getLogger: this.auditSetup.getLogger, }, authc: { getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request) }, authz: { @@ -355,7 +353,6 @@ export class SecurityPlugin config, featureUsageService: this.featureUsageServiceStart, http: core.http, - legacyAuditLogger: new SecurityAuditLogger(this.auditSetup!.getLogger()), loggers: this.initializerContext.logger, session, }); diff --git a/x-pack/plugins/security/server/prompt_page.tsx b/x-pack/plugins/security/server/prompt_page.tsx index eb26e1a4380aea..853ef723ddb98e 100644 --- a/x-pack/plugins/security/server/prompt_page.tsx +++ b/x-pack/plugins/security/server/prompt_page.tsx @@ -53,7 +53,7 @@ export function PromptPage({ const regularBundlePath = `${basePath.serverBasePath}/${buildNumber}/bundles`; const styleSheetPaths = [ `${regularBundlePath}/kbn-ui-shared-deps-src/${UiSharedDepsSrc.cssDistFilename}`, - `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename('v8')}`, `${basePath.serverBasePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, `${basePath.serverBasePath}/ui/legacy_light_theme.css`, ]; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts index e090cd26dc39fc..cb5bb8a91152c0 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts @@ -5,5 +5,6 @@ * 2.0. */ -export { ElasticsearchRole, transformElasticsearchRoleToRole } from '../../../../authorization'; +export type { ElasticsearchRole } from '../../../../authorization'; +export { transformElasticsearchRoleToRole } from '../../../../authorization'; export { getPutPayloadSchema, transformPutPayloadToElasticsearchRole } from './put_payload'; diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index 6def1b7d77df3d..ee728c29bdc92e 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -171,7 +171,6 @@ describe('Login view routes', () => { showRoleMappingsManagement: true, allowSubFeaturePrivileges: true, allowAuditLogging: true, - allowLegacyAuditLogging: true, showLogin: true, }); diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index c9c467f1f84658..07bfe97f9de7db 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -8,13 +8,12 @@ import type { CoreSetup } from 'src/core/server'; import { SavedObjectsClient } from '../../../../../src/core/server'; -import type { AuditServiceSetup, SecurityAuditLogger } from '../audit'; +import type { AuditServiceSetup } from '../audit'; import type { AuthorizationServiceSetupInternal } from '../authorization'; import type { SpacesService } from '../plugin'; import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper'; interface SetupSavedObjectsParams { - legacyAuditLogger: SecurityAuditLogger; audit: AuditServiceSetup; authz: Pick< AuthorizationServiceSetupInternal, @@ -38,7 +37,6 @@ export { } from './ensure_authorized'; export function setupSavedObjects({ - legacyAuditLogger, audit, authz, savedObjects, @@ -59,7 +57,6 @@ export function setupSavedObjects({ return authz.mode.useRbacForRequest(request) ? new SecureSavedObjectsClientWrapper({ actions: authz.actions, - legacyAuditLogger, auditLogger: audit.asScoped(request), baseClient: client, checkSavedObjectsPrivilegesAsCurrentUser: diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 0d7d8cfa480fac..d890861849cfef 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -18,7 +18,7 @@ import type { import { httpServerMock, savedObjectsClientMock } from 'src/core/server/mocks'; import type { AuditEvent } from '../audit'; -import { auditServiceMock, securityAuditLoggerMock } from '../audit/index.mock'; +import { auditServiceMock } from '../audit/index.mock'; import { Actions } from '../authorization'; import type { SavedObjectActions } from '../authorization/actions/saved_object'; import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper'; @@ -65,7 +65,6 @@ const createSecureSavedObjectsClientWrapperOptions = () => { checkSavedObjectsPrivilegesAsCurrentUser: jest.fn(), errors, getSpacesService, - legacyAuditLogger: securityAuditLoggerMock.create(), auditLogger: auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()), forbiddenError, generalError, @@ -81,8 +80,6 @@ const expectGeneralError = async (fn: Function, args: Record) => { clientOpts.generalError ); expect(clientOpts.errors.decorateGeneralError).toHaveBeenCalledTimes(1); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }; /** @@ -98,51 +95,12 @@ const expectForbiddenError = async (fn: Function, args: Record, act await expect(fn.bind(client)(...Object.values(args))).rejects.toThrowError( clientOpts.forbiddenError ); - const getCalls = ( - clientOpts.actions.savedObject.get as jest.MockedFunction - ).mock.calls; - const actions = clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mock.calls[0][0]; - const spaceId = args.options?.namespaces - ? args.options?.namespaces[0] - : args.options?.namespace || 'default'; - - const ACTION = getCalls[0][1]; - const types = getCalls.map((x) => x[0]); - const missing = [{ spaceId, privilege: actions[0] }]; // if there was more than one type, only the first type was unauthorized - const spaceIds = [spaceId]; expect(clientOpts.errors.decorateForbiddenError).toHaveBeenCalledTimes(1); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith( - USERNAME, - action ?? ACTION, - types, - spaceIds, - missing, - args - ); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }; const expectSuccess = async (fn: Function, args: Record, action?: string) => { - const result = await fn.bind(client)(...Object.values(args)); - const getCalls = ( - clientOpts.actions.savedObject.get as jest.MockedFunction - ).mock.calls; - const ACTION = getCalls[0][1]; - const types = getCalls.map((x) => x[0]); - const spaceIds = args.options?.namespaces || [args.options?.namespace || 'default']; - - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith( - USERNAME, - action ?? ACTION, - types, - spaceIds, - args - ); - return result; + return await fn.bind(client)(...Object.values(args)); }; const expectPrivilegeCheck = async ( @@ -795,15 +753,6 @@ describe('#find', () => { const result = await client.find(options); expect(clientOpts.baseClient.find).not.toHaveBeenCalled(); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith( - USERNAME, - 'find', - [type1], - options.namespaces, - [{ spaceId: 'some-ns', privilege: 'mock-saved_object:foo/find' }], - { options } - ); expect(result).toEqual({ page: 1, per_page: 20, total: 0, saved_objects: [] }); }); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index b0428be87a4f21..6c6220cb4ab750 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { PublicMethodsOf } from '@kbn/utility-types'; import type { SavedObjectReferenceWithContext, SavedObjectsBaseOptions, @@ -32,7 +31,7 @@ import type { import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '../../../../../src/core/server'; import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../common/constants'; -import type { AuditLogger, SecurityAuditLogger } from '../audit'; +import type { AuditLogger } from '../audit'; import { SavedObjectAction, savedObjectEvent } from '../audit'; import type { Actions, CheckSavedObjectsPrivileges } from '../authorization'; import type { CheckPrivilegesResponse } from '../authorization/types'; @@ -50,7 +49,6 @@ import { interface SecureSavedObjectsClientWrapperOptions { actions: Actions; - legacyAuditLogger: SecurityAuditLogger; auditLogger: AuditLogger; baseClient: SavedObjectsClientContract; errors: SavedObjectsClientContract['errors']; @@ -84,7 +82,6 @@ interface LegacyEnsureAuthorizedTypeResult { export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContract { private readonly actions: Actions; - private readonly legacyAuditLogger: PublicMethodsOf; private readonly auditLogger: AuditLogger; private readonly baseClient: SavedObjectsClientContract; private readonly checkSavedObjectsPrivilegesAsCurrentUser: CheckSavedObjectsPrivileges; @@ -93,7 +90,6 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra constructor({ actions, - legacyAuditLogger, auditLogger, baseClient, checkSavedObjectsPrivilegesAsCurrentUser, @@ -102,7 +98,6 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra }: SecureSavedObjectsClientWrapperOptions) { this.errors = errors; this.actions = actions; - this.legacyAuditLogger = legacyAuditLogger; this.auditLogger = auditLogger; this.baseClient = baseClient; this.checkSavedObjectsPrivilegesAsCurrentUser = checkSavedObjectsPrivilegesAsCurrentUser; @@ -900,7 +895,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra namespaceOrNamespaces: undefined | string | Array, options: LegacyEnsureAuthorizedOptions = {} ): Promise { - const { args, auditAction = action, requireFullAuthorization = true } = options; + const { requireFullAuthorization = true } = options; const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; const actionsToTypesMap = new Map( types.map((type) => [this.actions.savedObject.get(type, action), type]) @@ -908,10 +903,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra const actions = Array.from(actionsToTypesMap.keys()); const result = await this.checkPrivileges(actions, namespaceOrNamespaces); - const { hasAllRequested, username, privileges } = result; - const spaceIds = uniq( - privileges.kibana.map(({ resource }) => resource).filter((x) => x !== undefined) - ).sort() as string[]; + const { hasAllRequested, privileges } = result; const missingPrivileges = this.getMissingPrivileges(privileges); const typeMap = privileges.kibana.reduce>( @@ -930,43 +922,16 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra new Map() ); - const logAuthorizationFailure = () => { - this.legacyAuditLogger.savedObjectsAuthorizationFailure( - username, - auditAction, - types, - spaceIds, - missingPrivileges, - args - ); - }; - const logAuthorizationSuccess = (typeArray: string[], spaceIdArray: string[]) => { - this.legacyAuditLogger.savedObjectsAuthorizationSuccess( - username, - auditAction, - typeArray, - spaceIdArray, - args - ); - }; - if (hasAllRequested) { - logAuthorizationSuccess(types, spaceIds); return { typeMap, status: 'fully_authorized' }; } else if (!requireFullAuthorization) { const isPartiallyAuthorized = privileges.kibana.some(({ authorized }) => authorized); if (isPartiallyAuthorized) { - for (const [type, { isGloballyAuthorized, authorizedSpaces }] of typeMap.entries()) { - // generate an individual audit record for each authorized type - logAuthorizationSuccess([type], isGloballyAuthorized ? spaceIds : authorizedSpaces); - } return { typeMap, status: 'partially_authorized' }; } else { - logAuthorizationFailure(); return { typeMap, status: 'unauthorized' }; } } else { - logAuthorizationFailure(); const targetTypes = uniq( missingPrivileges.map(({ privilege }) => actionsToTypesMap.get(privilege)).sort() ).join(','); diff --git a/x-pack/plugins/security/server/session_management/index.ts b/x-pack/plugins/security/server/session_management/index.ts index a9ac23ad6354be..09787ed419854d 100644 --- a/x-pack/plugins/security/server/session_management/index.ts +++ b/x-pack/plugins/security/server/session_management/index.ts @@ -5,8 +5,7 @@ * 2.0. */ -export { Session, SessionValue } from './session'; -export { - SessionManagementServiceStart, - SessionManagementService, -} from './session_management_service'; +export type { SessionValue } from './session'; +export { Session } from './session'; +export type { SessionManagementServiceStart } from './session_management_service'; +export { SessionManagementService } from './session_management_service'; diff --git a/x-pack/plugins/security/server/spaces/legacy_audit_logger.test.ts b/x-pack/plugins/security/server/spaces/legacy_audit_logger.test.ts deleted file mode 100644 index 055e944a173d2b..00000000000000 --- a/x-pack/plugins/security/server/spaces/legacy_audit_logger.test.ts +++ /dev/null @@ -1,94 +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 { LegacySpacesAuditLogger } from './legacy_audit_logger'; - -const createMockAuditLogger = () => { - return { - log: jest.fn(), - }; -}; - -describe(`#savedObjectsAuthorizationFailure`, () => { - test('logs auth failure with spaceIds via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new LegacySpacesAuditLogger(auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - const spaceIds = ['foo-space-1', 'foo-space-2']; - - securityAuditLogger.spacesAuthorizationFailure(username, action, spaceIds); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'spaces_authorization_failure', - expect.stringContaining(`${username} unauthorized to ${action} ${spaceIds.join(',')} spaces`), - { - username, - action, - spaceIds, - } - ); - }); - - test('logs auth failure without spaceIds via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new LegacySpacesAuditLogger(auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - - securityAuditLogger.spacesAuthorizationFailure(username, action); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'spaces_authorization_failure', - expect.stringContaining(`${username} unauthorized to ${action} spaces`), - { - username, - action, - } - ); - }); -}); - -describe(`#savedObjectsAuthorizationSuccess`, () => { - test('logs auth success with spaceIds via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new LegacySpacesAuditLogger(auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - const spaceIds = ['foo-space-1', 'foo-space-2']; - - securityAuditLogger.spacesAuthorizationSuccess(username, action, spaceIds); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'spaces_authorization_success', - expect.stringContaining(`${username} authorized to ${action} ${spaceIds.join(',')} spaces`), - { - username, - action, - spaceIds, - } - ); - }); - - test('logs auth success without spaceIds via auditLogger', () => { - const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new LegacySpacesAuditLogger(auditLogger); - const username = 'foo-user'; - const action = 'foo-action'; - - securityAuditLogger.spacesAuthorizationSuccess(username, action); - - expect(auditLogger.log).toHaveBeenCalledWith( - 'spaces_authorization_success', - expect.stringContaining(`${username} authorized to ${action} spaces`), - { - username, - action, - } - ); - }); -}); diff --git a/x-pack/plugins/security/server/spaces/legacy_audit_logger.ts b/x-pack/plugins/security/server/spaces/legacy_audit_logger.ts deleted file mode 100644 index 3ac4859eb7c8af..00000000000000 --- a/x-pack/plugins/security/server/spaces/legacy_audit_logger.ts +++ /dev/null @@ -1,52 +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 type { LegacyAuditLogger } from '../audit'; - -/** - * @deprecated will be removed in 8.0 - */ -export class LegacySpacesAuditLogger { - private readonly auditLogger: LegacyAuditLogger; - - /** - * @deprecated will be removed in 8.0 - */ - constructor(auditLogger: LegacyAuditLogger = { log() {} }) { - this.auditLogger = auditLogger; - } - - /** - * @deprecated will be removed in 8.0 - */ - public spacesAuthorizationFailure(username: string, action: string, spaceIds?: string[]) { - this.auditLogger.log( - 'spaces_authorization_failure', - `${username} unauthorized to ${action}${spaceIds ? ' ' + spaceIds.join(',') : ''} spaces`, - { - username, - action, - spaceIds, - } - ); - } - - /** - * @deprecated will be removed in 8.0 - */ - public spacesAuthorizationSuccess(username: string, action: string, spaceIds?: string[]) { - this.auditLogger.log( - 'spaces_authorization_success', - `${username} authorized to ${action}${spaceIds ? ' ' + spaceIds.join(',') : ''} spaces`, - { - username, - action, - spaceIds, - } - ); - } -} diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index 10261b98b8f0a7..7f62948251d7cc 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -23,7 +23,6 @@ import type { } from '../authorization'; import { authorizationMock } from '../authorization/index.mock'; import type { CheckPrivilegesResponse } from '../authorization/types'; -import type { LegacySpacesAuditLogger } from './legacy_audit_logger'; import { getAliasId, LEGACY_URL_ALIAS_TYPE, @@ -70,10 +69,6 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { }); authorization.mode.useRbacForRequest.mockReturnValue(securityEnabled); - const legacyAuditLogger = { - spacesAuthorizationFailure: jest.fn(), - spacesAuthorizationSuccess: jest.fn(), - } as unknown as jest.Mocked; const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); const request = httpServerMock.createKibanaRequest(); @@ -89,7 +84,6 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { request, authorization, auditLogger, - legacyAuditLogger, errors ); return { @@ -98,7 +92,6 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { request, baseClient, auditLogger, - legacyAuditLogger, forbiddenError, }; }; @@ -111,49 +104,6 @@ const expectNoAuthorizationCheck = ( expect(authorization.checkSavedObjectsPrivilegesWithRequest).not.toHaveBeenCalled(); }; -const expectNoAuditLogging = (auditLogger: jest.Mocked) => { - expect(auditLogger.spacesAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.spacesAuthorizationSuccess).not.toHaveBeenCalled(); -}; - -const expectForbiddenAuditLogging = ( - auditLogger: jest.Mocked, - username: string, - operation: string, - spaceId?: string -) => { - expect(auditLogger.spacesAuthorizationFailure).toHaveBeenCalledTimes(1); - if (spaceId) { - expect(auditLogger.spacesAuthorizationFailure).toHaveBeenCalledWith(username, operation, [ - spaceId, - ]); - } else { - expect(auditLogger.spacesAuthorizationFailure).toHaveBeenCalledWith(username, operation); - } - - expect(auditLogger.spacesAuthorizationSuccess).not.toHaveBeenCalled(); -}; - -const expectSuccessAuditLogging = ( - auditLogger: jest.Mocked, - username: string, - operation: string, - spaceIds?: string[] -) => { - expect(auditLogger.spacesAuthorizationSuccess).toHaveBeenCalledTimes(1); - if (spaceIds) { - expect(auditLogger.spacesAuthorizationSuccess).toHaveBeenCalledWith( - username, - operation, - spaceIds - ); - } else { - expect(auditLogger.spacesAuthorizationSuccess).toHaveBeenCalledWith(username, operation); - } - - expect(auditLogger.spacesAuthorizationFailure).not.toHaveBeenCalled(); -}; - const expectAuditEvent = ( auditLogger: AuditLogger, action: string, @@ -209,7 +159,7 @@ describe('SecureSpacesClientWrapper', () => { ]; it('delegates to base client when security is not enabled', async () => { - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger } = setup({ + const { wrapper, baseClient, authorization, auditLogger } = setup({ securityEnabled: false, }); @@ -218,7 +168,6 @@ describe('SecureSpacesClientWrapper', () => { expect(baseClient.getAll).toHaveBeenCalledWith({ purpose: 'any' }); expect(response).toEqual(spaces); expectNoAuthorizationCheck(authorization); - expectNoAuditLogging(legacyAuditLogger); expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', { type: 'space', id: spaces[0].id, @@ -269,10 +218,9 @@ describe('SecureSpacesClientWrapper', () => { describe(`with purpose='${scenario.purpose}'`, () => { test(`throws Boom.forbidden when user isn't authorized for any spaces`, async () => { const username = 'some-user'; - const { authorization, wrapper, baseClient, request, auditLogger, legacyAuditLogger } = - setup({ - securityEnabled: true, - }); + const { authorization, wrapper, baseClient, request, auditLogger } = setup({ + securityEnabled: true, + }); const privileges = scenario.expectedPrivilege(authorization); @@ -303,16 +251,14 @@ describe('SecureSpacesClientWrapper', () => { { kibana: privileges } ); - expectForbiddenAuditLogging(legacyAuditLogger, username, 'getAll'); expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'failure'); }); test(`returns spaces that the user is authorized for`, async () => { const username = 'some-user'; - const { authorization, wrapper, baseClient, request, auditLogger, legacyAuditLogger } = - setup({ - securityEnabled: true, - }); + const { authorization, wrapper, baseClient, request, auditLogger } = setup({ + securityEnabled: true, + }); const privileges = scenario.expectedPrivilege(authorization); @@ -342,7 +288,6 @@ describe('SecureSpacesClientWrapper', () => { { kibana: privileges } ); - expectSuccessAuditLogging(legacyAuditLogger, username, 'getAll', [spaces[0].id]); expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', { type: 'space', id: spaces[0].id, @@ -354,7 +299,7 @@ describe('SecureSpacesClientWrapper', () => { describe('#get', () => { it('delegates to base client when security is not enabled', async () => { - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger } = setup({ + const { wrapper, baseClient, authorization, auditLogger } = setup({ securityEnabled: false, }); @@ -363,7 +308,6 @@ describe('SecureSpacesClientWrapper', () => { expect(baseClient.get).toHaveBeenCalledWith('default'); expect(response).toEqual(spaces[0]); expectNoAuthorizationCheck(authorization); - expectNoAuditLogging(legacyAuditLogger); expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'success', { type: 'space', id: spaces[0].id, @@ -374,11 +318,9 @@ describe('SecureSpacesClientWrapper', () => { const username = 'some_user'; const spaceId = 'default'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -404,7 +346,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.login, }); - expectForbiddenAuditLogging(legacyAuditLogger, username, 'get', spaceId); expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'failure', { type: 'space', id: spaces[0].id, @@ -415,11 +356,9 @@ describe('SecureSpacesClientWrapper', () => { const username = 'some_user'; const spaceId = 'default'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -444,7 +383,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.login, }); - expectSuccessAuditLogging(legacyAuditLogger, username, 'get', [spaceId]); expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'success', { type: 'space', id: spaceId, @@ -460,7 +398,7 @@ describe('SecureSpacesClientWrapper', () => { }); it('delegates to base client when security is not enabled', async () => { - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger } = setup({ + const { wrapper, baseClient, authorization, auditLogger } = setup({ securityEnabled: false, }); @@ -469,7 +407,6 @@ describe('SecureSpacesClientWrapper', () => { expect(baseClient.create).toHaveBeenCalledWith(space); expect(response).toEqual(space); expectNoAuthorizationCheck(authorization); - expectNoAuditLogging(legacyAuditLogger); expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'unknown', { type: 'space', id: space.id, @@ -479,11 +416,9 @@ describe('SecureSpacesClientWrapper', () => { test(`throws a forbidden error when unauthorized`, async () => { const username = 'some_user'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -507,7 +442,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.space.manage, }); - expectForbiddenAuditLogging(legacyAuditLogger, username, 'create'); expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'failure', { type: 'space', id: space.id, @@ -517,11 +451,9 @@ describe('SecureSpacesClientWrapper', () => { it('creates the space when authorized', async () => { const username = 'some_user'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -546,7 +478,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.space.manage, }); - expectSuccessAuditLogging(legacyAuditLogger, username, 'create'); expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'unknown', { type: 'space', id: space.id, @@ -562,7 +493,7 @@ describe('SecureSpacesClientWrapper', () => { }); it('delegates to base client when security is not enabled', async () => { - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger } = setup({ + const { wrapper, baseClient, authorization, auditLogger } = setup({ securityEnabled: false, }); @@ -571,7 +502,6 @@ describe('SecureSpacesClientWrapper', () => { expect(baseClient.update).toHaveBeenCalledWith(space.id, space); expect(response).toEqual(space.id); expectNoAuthorizationCheck(authorization); - expectNoAuditLogging(legacyAuditLogger); expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'unknown', { type: 'space', id: space.id, @@ -581,11 +511,9 @@ describe('SecureSpacesClientWrapper', () => { test(`throws a forbidden error when unauthorized`, async () => { const username = 'some_user'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -609,7 +537,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.space.manage, }); - expectForbiddenAuditLogging(legacyAuditLogger, username, 'update'); expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'failure', { type: 'space', id: space.id, @@ -619,11 +546,9 @@ describe('SecureSpacesClientWrapper', () => { it('updates the space when authorized', async () => { const username = 'some_user'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -648,7 +573,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.space.manage, }); - expectSuccessAuditLogging(legacyAuditLogger, username, 'update'); expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'unknown', { type: 'space', id: space.id, @@ -664,7 +588,7 @@ describe('SecureSpacesClientWrapper', () => { }); it('delegates to base client when security is not enabled', async () => { - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger } = setup({ + const { wrapper, baseClient, authorization, auditLogger } = setup({ securityEnabled: false, }); @@ -672,7 +596,6 @@ describe('SecureSpacesClientWrapper', () => { expect(baseClient.delete).toHaveBeenCalledTimes(1); expect(baseClient.delete).toHaveBeenCalledWith(space.id); expectNoAuthorizationCheck(authorization); - expectNoAuditLogging(legacyAuditLogger); expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'unknown', { type: 'space', id: space.id, @@ -682,11 +605,9 @@ describe('SecureSpacesClientWrapper', () => { test(`throws a forbidden error when unauthorized`, async () => { const username = 'some_user'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -710,7 +631,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.space.manage, }); - expectForbiddenAuditLogging(legacyAuditLogger, username, 'delete'); expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'failure', { type: 'space', id: space.id, @@ -720,11 +640,9 @@ describe('SecureSpacesClientWrapper', () => { it('deletes the space when authorized', async () => { const username = 'some_user'; - const { wrapper, baseClient, authorization, auditLogger, legacyAuditLogger, request } = setup( - { - securityEnabled: true, - } - ); + const { wrapper, baseClient, authorization, auditLogger, request } = setup({ + securityEnabled: true, + }); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -747,7 +665,6 @@ describe('SecureSpacesClientWrapper', () => { kibana: authorization.actions.space.manage, }); - expectSuccessAuditLogging(legacyAuditLogger, username, 'delete'); expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'unknown', { type: 'space', id: space.id, diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index e2b57d01fe11cd..e433e9c366df51 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -23,7 +23,6 @@ import type { AuthorizationServiceSetup } from '../authorization'; import type { SecurityPluginSetup } from '../plugin'; import type { EnsureAuthorizedDependencies, EnsureAuthorizedOptions } from '../saved_objects'; import { ensureAuthorized, isAuthorizedForObjectInAllSpaces } from '../saved_objects'; -import type { LegacySpacesAuditLogger } from './legacy_audit_logger'; const PURPOSE_PRIVILEGE_MAP: Record< GetAllSpacesPurpose, @@ -52,7 +51,6 @@ export class SecureSpacesClientWrapper implements ISpacesClient { private readonly request: KibanaRequest, private readonly authorization: AuthorizationServiceSetup, private readonly auditLogger: AuditLogger, - private readonly legacyAuditLogger: LegacySpacesAuditLogger, private readonly errors: SavedObjectsClientContract['errors'] ) {} @@ -89,7 +87,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient { ); // Check all privileges against all spaces - const { username, privileges } = await checkPrivileges.atSpaces(spaceIds, { + const { privileges } = await checkPrivileges.atSpaces(spaceIds, { kibana: Object.values(allPrivileges).flat(), }); @@ -132,7 +130,6 @@ export class SecureSpacesClientWrapper implements ISpacesClient { if (authorizedSpaces.length === 0) { const error = Boom.forbidden(); - this.legacyAuditLogger.spacesAuthorizationFailure(username, 'getAll'); this.auditLogger.log( spaceAuditEvent({ action: SpaceAuditAction.FIND, @@ -143,9 +140,6 @@ export class SecureSpacesClientWrapper implements ISpacesClient { throw error; // Note: there is a catch for this in `SpacesSavedObjectsClient.find`; if we get rid of this error, remove that too } - const authorizedSpaceIds = authorizedSpaces.map((space) => space.id); - - this.legacyAuditLogger.spacesAuthorizationSuccess(username, 'getAll', authorizedSpaceIds); authorizedSpaces.forEach(({ id }) => this.auditLogger.log( spaceAuditEvent({ @@ -164,7 +158,6 @@ export class SecureSpacesClientWrapper implements ISpacesClient { await this.ensureAuthorizedAtSpace( id, this.authorization.actions.login, - 'get', `Unauthorized to get ${id} space` ); } catch (error) { @@ -196,7 +189,6 @@ export class SecureSpacesClientWrapper implements ISpacesClient { try { await this.ensureAuthorizedGlobally( this.authorization.actions.space.manage, - 'create', 'Unauthorized to create spaces' ); } catch (error) { @@ -227,7 +219,6 @@ export class SecureSpacesClientWrapper implements ISpacesClient { try { await this.ensureAuthorizedGlobally( this.authorization.actions.space.manage, - 'update', 'Unauthorized to update spaces' ); } catch (error) { @@ -258,7 +249,6 @@ export class SecureSpacesClientWrapper implements ISpacesClient { try { await this.ensureAuthorizedGlobally( this.authorization.actions.space.manage, - 'delete', 'Unauthorized to delete spaces' ); } catch (error) { @@ -362,33 +352,22 @@ export class SecureSpacesClientWrapper implements ISpacesClient { return ensureAuthorized(ensureAuthorizedDependencies, types, actions, namespaces, options); } - private async ensureAuthorizedGlobally(action: string, method: string, forbiddenMessage: string) { + private async ensureAuthorizedGlobally(action: string, forbiddenMessage: string) { const checkPrivileges = this.authorization.checkPrivilegesWithRequest(this.request); - const { username, hasAllRequested } = await checkPrivileges.globally({ kibana: action }); + const { hasAllRequested } = await checkPrivileges.globally({ kibana: action }); - if (hasAllRequested) { - this.legacyAuditLogger.spacesAuthorizationSuccess(username, method); - } else { - this.legacyAuditLogger.spacesAuthorizationFailure(username, method); + if (!hasAllRequested) { throw Boom.forbidden(forbiddenMessage); } } - private async ensureAuthorizedAtSpace( - spaceId: string, - action: string, - method: string, - forbiddenMessage: string - ) { + private async ensureAuthorizedAtSpace(spaceId: string, action: string, forbiddenMessage: string) { const checkPrivileges = this.authorization.checkPrivilegesWithRequest(this.request); - const { username, hasAllRequested } = await checkPrivileges.atSpace(spaceId, { + const { hasAllRequested } = await checkPrivileges.atSpace(spaceId, { kibana: action, }); - if (hasAllRequested) { - this.legacyAuditLogger.spacesAuthorizationSuccess(username, method, [spaceId]); - } else { - this.legacyAuditLogger.spacesAuthorizationFailure(username, method, [spaceId]); + if (!hasAllRequested) { throw Boom.forbidden(forbiddenMessage); } } diff --git a/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts b/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts index a6849c7ea9a869..89f0f81dcc5f9c 100644 --- a/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts +++ b/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts @@ -18,8 +18,6 @@ describe('setupSpacesClient', () => { const audit = auditServiceMock.create(); setupSpacesClient({ authz, audit }); - - expect(audit.getLogger).not.toHaveBeenCalled(); }); it('configures the repository factory, wrapper, and audit logger', () => { @@ -31,7 +29,6 @@ describe('setupSpacesClient', () => { expect(spaces.spacesClient.registerClientWrapper).toHaveBeenCalledTimes(1); expect(spaces.spacesClient.setClientRepositoryFactory).toHaveBeenCalledTimes(1); - expect(audit.getLogger).toHaveBeenCalledTimes(1); }); it('creates a factory that creates an internal repository when RBAC is used for the request', () => { diff --git a/x-pack/plugins/security/server/spaces/setup_spaces_client.ts b/x-pack/plugins/security/server/spaces/setup_spaces_client.ts index b518a1cff2c0fd..0d2f90290425f6 100644 --- a/x-pack/plugins/security/server/spaces/setup_spaces_client.ts +++ b/x-pack/plugins/security/server/spaces/setup_spaces_client.ts @@ -9,7 +9,6 @@ import { SavedObjectsClient } from '../../../../../src/core/server'; import type { SpacesPluginSetup } from '../../../spaces/server'; import type { AuditServiceSetup } from '../audit'; import type { AuthorizationServiceSetup } from '../authorization'; -import { LegacySpacesAuditLogger } from './legacy_audit_logger'; import { SecureSpacesClientWrapper } from './secure_spaces_client_wrapper'; interface Deps { @@ -31,8 +30,6 @@ export const setupSpacesClient = ({ audit, authz, spaces }: Deps) => { return savedObjectsStart.createScopedRepository(request, ['space']); }); - const spacesAuditLogger = new LegacySpacesAuditLogger(audit.getLogger()); - spacesClient.registerClientWrapper( (request, baseClient) => new SecureSpacesClientWrapper( @@ -40,7 +37,6 @@ export const setupSpacesClient = ({ audit, authz, spaces }: Deps) => { request, authz, audit.asScoped(request), - spacesAuditLogger, SavedObjectsClient.errors ) ); diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts index aeeeb9c888914a..72ef6bc5817b2a 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts @@ -33,7 +33,6 @@ describe('Security UsageCollector', () => { license.getFeatures.mockReturnValue({ allowAccessAgreement, allowAuditLogging, - allowLegacyAuditLogging: allowAuditLogging, allowRbac, } as SecurityLicenseFeatures); return license; @@ -343,19 +342,17 @@ describe('Security UsageCollector', () => { }); describe('audit logging', () => { - it('reports when ECS audit logging is enabled', async () => { + it('reports when audit logging is enabled', async () => { const config = createSecurityConfig( ConfigSchema.validate({ audit: { enabled: true, - appender: { type: 'console', layout: { type: 'json' } }, }, }) ); const usageCollection = usageCollectionPluginMock.createSetupContract(); const license = createSecurityLicense({ isLicenseAvailable: true, - allowLegacyAuditLogging: true, allowAuditLogging: true, }); registerSecurityUsageCollector({ usageCollection, config, license }); @@ -367,7 +364,6 @@ describe('Security UsageCollector', () => { expect(usage).toEqual({ ...DEFAULT_USAGE, auditLoggingEnabled: true, - auditLoggingType: 'ecs', }); }); @@ -376,6 +372,7 @@ describe('Security UsageCollector', () => { ConfigSchema.validate({ audit: { enabled: true, + appender: { type: 'console', layout: { type: 'json' } }, }, }) ); @@ -390,7 +387,6 @@ describe('Security UsageCollector', () => { expect(usage).toEqual({ ...DEFAULT_USAGE, auditLoggingEnabled: false, - auditLoggingType: undefined, }); }); }); diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts index 66f414109d7bbf..9b3826372bce23 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts @@ -12,7 +12,6 @@ import type { ConfigType } from '../config'; interface Usage { auditLoggingEnabled: boolean; - auditLoggingType?: 'ecs' | 'legacy'; loginSelectorEnabled: boolean; accessAgreementEnabled: boolean; authProviderCount: number; @@ -62,13 +61,6 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens 'Indicates if audit logging is both enabled and supported by the current license.', }, }, - auditLoggingType: { - type: 'keyword', - _meta: { - description: - 'If auditLoggingEnabled is true, indicates what type is enabled (ECS or legacy).', - }, - }, loginSelectorEnabled: { type: 'boolean', _meta: { @@ -132,8 +124,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens }, }, fetch: () => { - const { allowRbac, allowAccessAgreement, allowAuditLogging, allowLegacyAuditLogging } = - license.getFeatures(); + const { allowRbac, allowAccessAgreement, allowAuditLogging } = license.getFeatures(); if (!allowRbac) { return { auditLoggingEnabled: false, @@ -148,17 +139,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens }; } - const legacyAuditLoggingEnabled = allowLegacyAuditLogging && config.audit.enabled; - const ecsAuditLoggingEnabled = - allowAuditLogging && config.audit.enabled && config.audit.appender != null; - - let auditLoggingType: Usage['auditLoggingType']; - if (ecsAuditLoggingEnabled) { - auditLoggingType = 'ecs'; - } else if (legacyAuditLoggingEnabled) { - auditLoggingType = 'legacy'; - } - + const auditLoggingEnabled = allowAuditLogging && config.audit.enabled; const loginSelectorEnabled = config.authc.selector.enabled; const authProviderCount = config.authc.sortedProviders.length; const enabledAuthProviders = [ @@ -183,8 +164,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens const sessionCleanupInMinutes = config.session.cleanupInterval?.asMinutes() ?? 0; return { - auditLoggingEnabled: legacyAuditLoggingEnabled || ecsAuditLoggingEnabled, - auditLoggingType, + auditLoggingEnabled, loginSelectorEnabled, accessAgreementEnabled, authProviderCount, diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 10cde80df48057..5b846751d26df7 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -5,14 +5,20 @@ * 2.0. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ENABLE_ITOM } from '../../actions/server/constants/connectors'; import type { TransformConfigSchema } from './transforms/types'; import { ENABLE_CASE_CONNECTOR } from '../../cases/common'; import { METADATA_TRANSFORMS_PATTERN } from './endpoint/constants'; +/** + * as const + * + * The const assertion ensures that type widening does not occur + * https://mariusschulz.com/blog/literal-type-widening-in-typescript + * Please follow this convention when adding to this file + */ + export const APP_ID = 'securitySolution' as const; -export const APP_UI_ID = 'securitySolutionUI'; +export const APP_UI_ID = 'securitySolutionUI' as const; export const CASES_FEATURE_ID = 'securitySolutionCases' as const; export const SERVER_APP_ID = 'siem' as const; export const APP_NAME = 'Security' as const; @@ -26,6 +32,8 @@ export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; export const DEFAULT_DARK_MODE = 'theme:darkMode' as const; export const DEFAULT_INDEX_KEY = 'securitySolution:defaultIndex' as const; export const DEFAULT_NUMBER_FORMAT = 'format:number:defaultPattern' as const; +export const DEFAULT_DATA_VIEW_ID = 'security-solution' as const; +export const DEFAULT_TIME_FIELD = '@timestamp' as const; export const DEFAULT_TIME_RANGE = 'timepicker:timeDefaults' as const; export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults' as const; export const DEFAULT_APP_TIME_RANGE = 'securitySolution:timeDefaults' as const; @@ -51,7 +59,6 @@ export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges' as const export const DEFAULT_TRANSFORMS = 'securitySolution:transforms' as const; export const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled' as const; export const GLOBAL_HEADER_HEIGHT = 96 as const; // px -export const GLOBAL_HEADER_HEIGHT_WITH_GLOBAL_BANNER = 128 as const; // px export const FILTERS_GLOBAL_HEIGHT = 109 as const; // px export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled' as const; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51' as const; @@ -225,6 +232,11 @@ export const INTERNAL_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_id` as const; export const INTERNAL_RULE_ALERT_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_alert_id` as const; export const INTERNAL_IMMUTABLE_KEY = `${INTERNAL_IDENTIFIER}_immutable` as const; +/** + * Internal actions route + */ +export const UPDATE_OR_CREATE_LEGACY_ACTIONS = '/internal/api/detection/legacy/notifications'; + /** * Detection engine routes */ @@ -245,6 +257,13 @@ export const DETECTION_ENGINE_RULES_PREVIEW = `${DETECTION_ENGINE_RULES_URL}/pre export const DETECTION_ENGINE_RULES_PREVIEW_INDEX_URL = `${DETECTION_ENGINE_RULES_PREVIEW}/index` as const; +/** + * Internal detection engine routes + */ +export const INTERNAL_DETECTION_ENGINE_URL = '/internal/detection_engine' as const; +export const INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL = + `${INTERNAL_DETECTION_ENGINE_URL}/rules/_find_status` as const; + export const TIMELINE_RESOLVE_URL = '/api/timeline/resolve' as const; export const TIMELINE_URL = '/api/timeline' as const; export const TIMELINES_URL = '/api/timelines' as const; @@ -256,6 +275,7 @@ export const TIMELINE_PREPACKAGED_URL = `${TIMELINE_URL}/_prepackaged` as const; export const NOTE_URL = '/api/note' as const; export const PINNED_EVENT_URL = '/api/pinned_event' as const; +export const SOURCERER_API_URL = '/api/sourcerer' as const; /** * Default signals index key for kibana.dev.yml @@ -304,6 +324,7 @@ export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.resilient', '.servicenow', '.servicenow-sir', + '.servicenow-itom', '.slack', '.swimlane', '.teams', @@ -314,11 +335,6 @@ if (ENABLE_CASE_CONNECTOR) { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS.push('.case'); } -// TODO: Remove when ITOM is ready -if (ENABLE_ITOM) { - NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS.push('.servicenow-itom'); -} - export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions' as const; export const NOTIFICATION_THROTTLE_RULE = 'rule' as const; @@ -343,7 +359,7 @@ export const ELASTIC_NAME = 'estc' as const; export const METADATA_TRANSFORM_STATS_URL = `/api/transform/transforms/${METADATA_TRANSFORMS_PATTERN}/_stats`; -export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_latest_'; +export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_latest_' as const; export const TRANSFORM_STATES = { ABORTING: 'aborting', diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts index 033e979d2814cc..42c10614975eb3 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts @@ -11,7 +11,7 @@ import type { CreateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { buildExceptionFilter } from '@kbn/securitysolution-list-utils'; -import { Filter, EsQueryConfig, IndexPatternBase, buildEsQuery } from '@kbn/es-query'; +import { Filter, EsQueryConfig, DataViewBase, buildEsQuery } from '@kbn/es-query'; import { ESBoolQuery } from '../typed_json'; import { Query, Index, TimestampOverrideOrUndefined } from './schemas/common/schemas'; @@ -24,7 +24,7 @@ export const getQueryFilter = ( lists: Array, excludeExceptions: boolean = true ): ESBoolQuery => { - const indexPattern: IndexPatternBase = { + const indexPattern: DataViewBase = { fields: [], title: index.join(), }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts index 1437a8230b67a7..d489ad562f3043 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts @@ -16,3 +16,13 @@ export const findRulesStatusesSchema = t.exact( export type FindRulesStatusesSchema = t.TypeOf; export type FindRulesStatusesSchemaDecoded = FindRulesStatusesSchema; + +export const findRuleStatusSchema = t.exact( + t.type({ + ruleId: t.string, + }) +); + +export type FindRuleStatusSchema = t.TypeOf; + +export type FindRuleStatusSchemaDecoded = FindRuleStatusSchema; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts new file mode 100644 index 00000000000000..a47d57c2991172 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts @@ -0,0 +1,38 @@ +/* + * 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 { ExportRulesDetails } from './export_rules_details_schema'; +import { + ExportExceptionDetailsMock, + getExceptionExportDetailsMock, +} from '../../../../../lists/common/schemas/response/exception_export_details_schema.mock'; + +interface RuleDetailsMock { + totalCount?: number; + rulesCount?: number; + missingCount?: number; + missingRules?: Array>; +} + +export const getOutputDetailsSample = (ruleDetails?: RuleDetailsMock): ExportRulesDetails => ({ + exported_count: ruleDetails?.totalCount ?? 0, + exported_rules_count: ruleDetails?.rulesCount ?? 0, + missing_rules: ruleDetails?.missingRules ?? [], + missing_rules_count: ruleDetails?.missingCount ?? 0, +}); + +export const getOutputDetailsSampleWithExceptions = ( + ruleDetails?: RuleDetailsMock, + exceptionDetails?: ExportExceptionDetailsMock +): ExportRulesDetails => ({ + ...getOutputDetailsSample(ruleDetails), + ...getExceptionExportDetailsMock(exceptionDetails), +}); + +export const getSampleDetailsAsNdjson = (sample: ExportRulesDetails): string => { + return `${JSON.stringify(sample)}\n`; +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.test.ts new file mode 100644 index 00000000000000..af0295ee460466 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.test.ts @@ -0,0 +1,60 @@ +/* + * 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. + */ + +/* + * 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 { pipe } from 'fp-ts/lib/pipeable'; +import { left } from 'fp-ts/lib/Either'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { + getOutputDetailsSample, + getOutputDetailsSampleWithExceptions, +} from './export_rules_details_schema.mock'; +import { + ExportRulesDetails, + exportRulesDetailsWithExceptionsSchema, +} from './export_rules_details_schema'; + +describe('exportRulesDetailsWithExceptionsSchema', () => { + test('it should validate export details response', () => { + const payload = getOutputDetailsSample(); + const decoded = exportRulesDetailsWithExceptionsSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate export details with exceptions details response', () => { + const payload = getOutputDetailsSampleWithExceptions(); + const decoded = exportRulesDetailsWithExceptionsSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should strip out extra keys', () => { + const payload: ExportRulesDetails & { + extraKey?: string; + } = getOutputDetailsSample(); + payload.extraKey = 'some extra key'; + const decoded = exportRulesDetailsWithExceptionsSchema.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(getOutputDetailsSample()); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts new file mode 100644 index 00000000000000..00e34ca9d73262 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { exportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types'; +import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; + +const createSchema = ( + requiredFields: Required, + optionalFields: Optional +) => { + return t.intersection([t.exact(t.type(requiredFields)), t.exact(t.partial(optionalFields))]); +}; + +export const exportRulesDetails = { + exported_count: t.number, + exported_rules_count: t.number, + missing_rules: t.array( + t.exact( + t.type({ + rule_id: NonEmptyString, + }) + ) + ), + missing_rules_count: t.number, +}; + +const exportRulesDetailsSchema = t.exact(t.type(exportRulesDetails)); +export type ExportRulesDetailsSchema = t.TypeOf; + +// With exceptions +export const exportRulesDetailsWithExceptionsSchema = createSchema( + exportRulesDetails, + exportExceptionDetails +); + +export type ExportRulesDetails = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/trusted_app_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/trusted_app_generator.ts index 91c2e17a1e12de..6c691894103be9 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/trusted_app_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/trusted_app_generator.ts @@ -67,8 +67,8 @@ export class TrustedAppGenerator extends BaseDataGenerator { ...(scopeType === 'policy' ? { policies: this.randomArray(5, () => this.randomUUID()) } : {}), }) as EffectScope; - // TODO: remove ts-ignore. TS types are conditional when it comes to the combination of OS and ENTRIES - // @ts-ignore + // TS types are conditional when it comes to the combination of OS and ENTRIES + // @ts-expect-error TS2322 return merge( { description: `Generator says we trust ${name}`, diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts index e19cffb8084645..61f7123c368409 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts @@ -31,7 +31,7 @@ export const setupFleetForEndpoint = async ( kbnClient: KbnClient ): Promise => { // We try to use the kbnClient **private** logger, bug if unable to access it, then just use console - // @ts-ignore + // @ts-expect-error TS2341 const log = kbnClient.log ? kbnClient.log : console; // Setup Fleet diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index b6a0724faebedf..4fd1b00ae3bee5 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -21,6 +21,7 @@ export const allowedExperimentalValues = Object.freeze({ uebaEnabled: false, disableIsolationUIPendingStatuses: false, riskyHostsEnabled: false, + pendingActionResponsesWithAck: true, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts b/x-pack/plugins/security_solution/common/field_maps/alerts.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts rename to x-pack/plugins/security_solution/common/field_maps/alerts.ts index 9cc5c63332a55e..08ce8f098f6fd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts +++ b/x-pack/plugins/security_solution/common/field_maps/alerts.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FieldMap } from '../../../../../../rule_registry/common/field_map'; +import { FieldMap } from '../../../rule_registry/common/field_map'; export const alertsFieldMap: FieldMap = { 'kibana.alert.ancestors': { @@ -163,6 +163,11 @@ export const alertsFieldMap: FieldMap = { array: false, required: true, }, + 'kibana.alert.original_event.severity': { + type: 'long', + array: false, + required: false, + }, 'kibana.alert.original_event.start': { type: 'date', array: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/field_names.ts b/x-pack/plugins/security_solution/common/field_maps/field_names.ts similarity index 90% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/field_names.ts rename to x-pack/plugins/security_solution/common/field_maps/field_names.ts index 62c20217d23f0b..1cb40063202d0a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/field_names.ts +++ b/x-pack/plugins/security_solution/common/field_maps/field_names.ts @@ -13,6 +13,7 @@ export const ALERT_DEPTH = `${ALERT_NAMESPACE}.depth` as const; export const ALERT_GROUP_ID = `${ALERT_NAMESPACE}.group.id` as const; export const ALERT_GROUP_INDEX = `${ALERT_NAMESPACE}.group.index` as const; export const ALERT_ORIGINAL_TIME = `${ALERT_NAMESPACE}.original_time` as const; +export const ALERT_THRESHOLD_RESULT = `${ALERT_NAMESPACE}.threshold_result` as const; export const ALERT_ORIGINAL_EVENT = `${ALERT_NAMESPACE}.original_event` as const; export const ALERT_ORIGINAL_EVENT_ACTION = `${ALERT_ORIGINAL_EVENT}.action` as const; @@ -24,3 +25,4 @@ export const ALERT_ORIGINAL_EVENT_TYPE = `${ALERT_ORIGINAL_EVENT}.type` as const export const ALERT_RULE_THRESHOLD = `${ALERT_RULE_NAMESPACE}.threshold` as const; export const ALERT_RULE_THRESHOLD_FIELD = `${ALERT_RULE_THRESHOLD}.field` as const; +export const ALERT_RULE_TIMELINE_ID = `${ALERT_RULE_NAMESPACE}.timeline_id` as const; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/index.ts b/x-pack/plugins/security_solution/common/field_maps/index.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/index.ts rename to x-pack/plugins/security_solution/common/field_maps/index.ts index 0f19e2b3d3a918..e293dac6638168 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/index.ts +++ b/x-pack/plugins/security_solution/common/field_maps/index.ts @@ -7,4 +7,5 @@ import { AlertsFieldMap, alertsFieldMap } from './alerts'; import { RulesFieldMap, rulesFieldMap } from './rules'; -export { AlertsFieldMap, RulesFieldMap, alertsFieldMap, rulesFieldMap }; +export type { AlertsFieldMap, RulesFieldMap }; +export { alertsFieldMap, rulesFieldMap }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts b/x-pack/plugins/security_solution/common/field_maps/rules.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts rename to x-pack/plugins/security_solution/common/field_maps/rules.ts index 87b55e092ec5d9..a067a36fe039be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts +++ b/x-pack/plugins/security_solution/common/field_maps/rules.ts @@ -126,17 +126,17 @@ export const rulesFieldMap = { array: true, required: false, }, - 'kibana.alert.rule.threat_mapping.field': { + 'kibana.alert.rule.threat_mapping.entries.field': { type: 'keyword', array: true, required: false, }, - 'kibana.alert.rule.threat_mapping.value': { + 'kibana.alert.rule.threat_mapping.entries.value': { type: 'keyword', array: true, required: false, }, - 'kibana.alert.rule.threat_mapping.type': { + 'kibana.alert.rule.threat_mapping.entries.type': { type: 'keyword', array: true, required: false, diff --git a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts index be5fd3b5c4dc54..86bc11f7a596d8 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts @@ -5,79 +5,17 @@ * 2.0. */ -import type { IFieldSubType } from '@kbn/es-query'; - -import type { - IEsSearchRequest, - IEsSearchResponse, - IIndexPattern, -} from '../../../../../../src/plugins/data/common'; -import type { DocValueFields, Maybe } from '../common'; - -interface FieldInfo { - category: string; - description?: string; - example?: string | number; - format?: string; - name: string; - type?: string; -} - -export interface IndexField { - /** Where the field belong */ - category: string; - /** Example of field's value */ - example?: Maybe; - /** whether the field's belong to an alias index */ - indexes: Array>; - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Description of the field */ - description?: Maybe; - format?: Maybe; - /** the elastic type as mapped in the index */ - esTypes?: string[]; - subType?: IFieldSubType; - readFromDocValues: boolean; -} - -export type BeatFields = Record; - -export interface IndexFieldsStrategyRequest extends IEsSearchRequest { - indices: string[]; - onlyCheckIfIndicesExist: boolean; -} - -export interface IndexFieldsStrategyResponse extends IEsSearchResponse { - indexFields: IndexField[]; - indicesExist: string[]; -} - -export interface BrowserField { - aggregatable: boolean; - category: string; - description: string | null; - example: string | number | null; - fields: Readonly>>; - format: string; - indexes: string[]; - name: string; - searchable: boolean; - type: string; - subType?: IFieldSubType; -} - -export type BrowserFields = Readonly>>; - -export const EMPTY_BROWSER_FIELDS = {}; -export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; -export const EMPTY_INDEX_PATTERN: IIndexPattern = { - fields: [], - title: '', -}; +export type { + FieldInfo, + IndexField, + BeatFields, + IndexFieldsStrategyRequest, + IndexFieldsStrategyResponse, + BrowserField, + BrowserFields, +} from '../../../../timelines/common'; +export { + EMPTY_BROWSER_FIELDS, + EMPTY_DOCVALUE_FIELD, + EMPTY_INDEX_FIELDS, +} from '../../../../timelines/common'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts index 39f23a63c8afea..d6735b59c229d1 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts @@ -10,6 +10,7 @@ export { LastEventIndexKey } from '../../../../../../timelines/common'; export type { LastTimeDetails, TimelineEventsLastEventTimeStrategyResponse, + TimelineKpiStrategyRequest, TimelineKpiStrategyResponse, TimelineEventsLastEventTimeRequestOptions, } from '../../../../../../timelines/common'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index 548560ac5cb8cb..2d94a36a937d54 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; import { ESQuery } from '../../typed_json'; import { @@ -41,6 +42,7 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest { defaultIndex: string[]; docValueFields?: DocValueFields[]; factoryQueryType?: TimelineFactoryQueryTypes; + runtimeMappings: MappingRuntimeFields; } export interface TimelineRequestSortField extends SortField { @@ -171,6 +173,7 @@ export interface SortTimelineInput { export interface TimelineInput { columns?: Maybe; dataProviders?: Maybe; + dataViewId?: Maybe; description?: Maybe; eqlOptions?: Maybe; eventType?: Maybe; diff --git a/x-pack/plugins/security_solution/common/test/index.ts b/x-pack/plugins/security_solution/common/test/index.ts index 6d5df76b306a3a..53261d54e84b03 100644 --- a/x-pack/plugins/security_solution/common/test/index.ts +++ b/x-pack/plugins/security_solution/common/test/index.ts @@ -7,12 +7,12 @@ // For the source of these roles please consult the PR these were introduced https://github.com/elastic/kibana/pull/81866#issue-511165754 export enum ROLES { + soc_manager = 'soc_manager', reader = 'reader', t1_analyst = 't1_analyst', t2_analyst = 't2_analyst', hunter = 'hunter', rule_author = 'rule_author', - soc_manager = 'soc_manager', platform_engineer = 'platform_engineer', detections_admin = 'detections_admin', } diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index c0046f7535db84..60fd126e6fd85c 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -272,6 +272,7 @@ export type TimelineTypeLiteralWithNull = runtimeTypes.TypeOf; +export type TimelineWithoutExternalRefs = Omit; /* * Timeline IDs @@ -719,6 +720,7 @@ export interface TimelineResult { created?: Maybe; createdBy?: Maybe; dataProviders?: Maybe; + dataViewId?: Maybe; dateRange?: Maybe; description?: Maybe; eqlOptions?: Maybe; diff --git a/x-pack/plugins/security_solution/common/types/timeline/store.ts b/x-pack/plugins/security_solution/common/types/timeline/store.ts index 03cf0c39378e57..75cd44ba2b7d79 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/store.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/store.ts @@ -38,19 +38,20 @@ export interface SortColumnTimeline { } export interface TimelinePersistInput { - id: string; + columns: ColumnHeaderOptions[]; dataProviders?: DataProvider[]; + dataViewId: string; dateRange?: { start: string; end: string; }; + defaultColumns?: ColumnHeaderOptions[]; excludedRowRendererIds?: RowRendererId[]; expandedDetail?: TimelineExpandedDetail; filters?: Filter[]; - columns: ColumnHeaderOptions[]; - defaultColumns?: ColumnHeaderOptions[]; - itemsPerPage?: number; + id: string; indexNames: string[]; + itemsPerPage?: number; kqlQuery?: { filterQuery: SerializedFilterQuery | null; }; diff --git a/x-pack/plugins/security_solution/cypress/README.md b/x-pack/plugins/security_solution/cypress/README.md index b500091aacc2d4..4d9f5d40ffe3e6 100644 --- a/x-pack/plugins/security_solution/cypress/README.md +++ b/x-pack/plugins/security_solution/cypress/README.md @@ -357,7 +357,7 @@ Note that the command will create the folder if it does not exist. ### Using an archive from within the Cypress tests -Task [cypress/tasks/es_archiver.ts](https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts) provides helpers such as `esArchiverLoad` and `esArchiverUnload` by means of `es_archiver`'s CLI. +Task [cypress/tasks/es_archiver.ts](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts) provides helpers such as `esArchiverLoad` and `esArchiverUnload` by means of `es_archiver`'s CLI. Because of `cy.exec`, used to invoke `es_archiver`, it's necessary to override its environment with `NODE_TLS_REJECT_UNAUTHORIZED=1`. It indeed would inject `NODE_TLS_REJECT_UNAUTHORIZED=0` and make `es_archive` otherwise abort with the following warning if used over https: @@ -374,7 +374,7 @@ Incorrect handling of the above points might result in false positives, in that #### Remote data loading -Helpers `esArchiverCCSLoad` and `esArchiverCCSUnload` are provided by [cypress/tasks/es_archiver.ts](https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts): +Helpers `esArchiverCCSLoad` and `esArchiverCCSUnload` are provided by [cypress/tasks/es_archiver.ts](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/cypress/tasks/es_archiver.ts): ```javascript import { esArchiverCCSLoad, esArchiverCCSUnload } from '../../tasks/es_archiver'; diff --git a/x-pack/plugins/security_solution/cypress/cypress.json b/x-pack/plugins/security_solution/cypress/cypress.json index 6a9a240af58739..8c27309becf08f 100644 --- a/x-pack/plugins/security_solution/cypress/cypress.json +++ b/x-pack/plugins/security_solution/cypress/cypress.json @@ -12,5 +12,10 @@ "video": false, "videosFolder": "../../../target/kibana-security-solution/cypress/videos", "viewportHeight": 900, - "viewportWidth": 1440 + "viewportWidth": 1440, + "env": { + "protocol": "http", + "hostname": "localhost", + "configport": "5601" + } } diff --git a/x-pack/plugins/security_solution/cypress/downloads/timelines_export.ndjson b/x-pack/plugins/security_solution/cypress/downloads/timelines_export.ndjson new file mode 100644 index 00000000000000..8cf76734ad8767 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/downloads/timelines_export.ndjson @@ -0,0 +1 @@ +{"savedObjectId":"46cca0e0-2580-11ec-8e56-9dafa0b0343b","version":"WzIyNjIzNCwxXQ==","columns":[{"id":"@timestamp"},{"id":"user.name"},{"id":"event.category"},{"id":"event.action"},{"id":"host.name"}],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"host.name: *","kind":"kuery"}}},"dateRange":{"start":"1514809376000","end":"1577881376000"},"description":"This is the best timeline","title":"Security Timeline","created":1633399341550,"createdBy":"elastic","updated":1633399341550,"updatedBy":"elastic","savedQueryId":null,"dataViewId":null,"timelineType":"default","sort":[],"eventNotes":[],"globalNotes":[],"pinnedEventIds":[]} diff --git a/x-pack/plugins/security_solution/cypress/fixtures/7_15_timeline.ndjson b/x-pack/plugins/security_solution/cypress/fixtures/7_15_timeline.ndjson new file mode 100644 index 00000000000000..7366889fb10823 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/fixtures/7_15_timeline.ndjson @@ -0,0 +1 @@ +{"savedObjectId":"53f99cf0-2b48-11ec-abd7-4702b60533ad","version":"WzExNDEyMSwxXQ==","columns":[{"columnHeaderType":"not-filtered","id":"@timestamp","type":"number"},{"columnHeaderType":"not-filtered","id":"message"},{"columnHeaderType":"not-filtered","id":"event.category"},{"columnHeaderType":"not-filtered","id":"event.action"},{"columnHeaderType":"not-filtered","id":"host.name"},{"columnHeaderType":"not-filtered","id":"source.ip"},{"columnHeaderType":"not-filtered","id":"destination.ip"},{"columnHeaderType":"not-filtered","id":"user.name"}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"source.ip: 127.0.0.1","queryMatch":{"field":"source.ip","value":"127.0.0.1","operator":":"},"id":"formatted-ip-data-provider-plain-column-renderer-formatted-field-value-timeline-1-query-source_ip-127_0_0_1-df246068f5258a4168f6dd37eecf8c8575d4821ce459d7060ab7be66e6768d52","enabled":true}],"description":"This a timeline created on 7.15 version","eventType":"all","filters":[],"kqlMode":"filter","timelineType":"default","kqlQuery":{"filterQuery":{"serializedQuery":"{\"bool\":{\"should\":[{\"match_phrase\":{\"host.name\":\"security-solution.local\"}}],\"minimum_should_match\":1}}","kuery":{"expression":"host.name:\"security-solution.local\" ","kind":"kuery"}}},"title":"7.15 Timeline","sort":[{"columnType":"number","sortDirection":"desc","columnId":"@timestamp"}],"templateTimelineId":null,"templateTimelineVersion":null,"dateRange":{"start":"2020-10-10T22:00:00.000Z","end":"2030-10-11T15:13:15.851Z"},"indexNames":["auditbeat-*",".siem-signals-default"],"eqlOptions":{"tiebreakerField":"","size":100,"query":"","eventCategoryField":"event.category","timestampField":"@timestamp"},"savedQueryId":null,"favorite":[{"favoriteDate":1633965242498,"keySearch":"MTY3Mzk5OTM3OQ==","fullName":"glo@email.com","userName":"1673999379"}],"created":1634035018815,"createdBy":"elastic","updated":1634035018815,"updatedBy":"elastic","eventNotes":[],"globalNotes":[{"noteId":"7aa03750-2b49-11ec-abd7-4702b60533ad","version":"WzExNDI3NiwxXQ==","note":"This is a note","timelineId":"53f99cf0-2b48-11ec-abd7-4702b60533ad","created":1634035513157,"createdBy":"1673999379","updated":1634035513157,"updatedBy":"1673999379"}],"pinnedEventIds":["df246068f5258a4168f6dd37eecf8c8575d4821ce459d7060ab7be66e6768d52"]} diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts index 23016ecc512b1f..0337cd3bd6e17d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/privileges.spec.ts @@ -18,184 +18,28 @@ import { filterStatusOpen, } from '../../tasks/create_new_case'; import { - constructUrlWithUser, - getEnvAuth, + loginAndWaitForHostDetailsPage, loginWithUserAndWaitForPageWithoutDateRange, + logout, } from '../../tasks/login'; +import { + createUsersAndRoles, + deleteUsersAndRoles, + secAll, + secAllUser, + secReadCasesAllUser, + secReadCasesAll, +} from '../../tasks/privileges'; import { CASES_URL } from '../../urls/navigation'; - -interface User { - username: string; - password: string; - description?: string; - roles: string[]; -} - -interface UserInfo { - username: string; - full_name: string; - email: string; -} - -interface FeaturesPrivileges { - [featureId: string]: string[]; -} - -interface ElasticsearchIndices { - names: string[]; - privileges: string[]; -} - -interface ElasticSearchPrivilege { - cluster?: string[]; - indices?: ElasticsearchIndices[]; -} - -interface KibanaPrivilege { - spaces: string[]; - base?: string[]; - feature?: FeaturesPrivileges; -} - -interface Role { - name: string; - privileges: { - elasticsearch?: ElasticSearchPrivilege; - kibana?: KibanaPrivilege[]; - }; -} - -const secAll: Role = { - name: 'sec_all_role', - privileges: { - elasticsearch: { - indices: [ - { - names: ['*'], - privileges: ['all'], - }, - ], - }, - kibana: [ - { - feature: { - siem: ['all'], - securitySolutionCases: ['all'], - actions: ['all'], - actionsSimulators: ['all'], - }, - spaces: ['*'], - }, - ], - }, -}; - -const secAllUser: User = { - username: 'sec_all_user', - password: 'password', - roles: [secAll.name], -}; - -const secReadCasesAll: Role = { - name: 'sec_read_cases_all_role', - privileges: { - elasticsearch: { - indices: [ - { - names: ['*'], - privileges: ['all'], - }, - ], - }, - kibana: [ - { - feature: { - siem: ['read'], - securitySolutionCases: ['all'], - actions: ['all'], - actionsSimulators: ['all'], - }, - spaces: ['*'], - }, - ], - }, -}; - -const secReadCasesAllUser: User = { - username: 'sec_read_cases_all_user', - password: 'password', - roles: [secReadCasesAll.name], -}; - +import { openSourcerer } from '../../tasks/sourcerer'; const usersToCreate = [secAllUser, secReadCasesAllUser]; const rolesToCreate = [secAll, secReadCasesAll]; - -const getUserInfo = (user: User): UserInfo => ({ - username: user.username, - full_name: user.username.replace('_', ' '), - email: `${user.username}@elastic.co`, -}); - -const createUsersAndRoles = (users: User[], roles: Role[]) => { - const envUser = getEnvAuth(); - for (const role of roles) { - cy.log(`Creating role: ${JSON.stringify(role)}`); - cy.request({ - body: role.privileges, - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'PUT', - url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`), - }) - .its('status') - .should('eql', 204); - } - - for (const user of users) { - const userInfo = getUserInfo(user); - cy.log(`Creating user: ${JSON.stringify(user)}`); - cy.request({ - body: { - username: user.username, - password: user.password, - roles: user.roles, - full_name: userInfo.full_name, - email: userInfo.email, - }, - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'POST', - url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`), - }) - .its('status') - .should('eql', 200); - } -}; - -const deleteUsersAndRoles = (users: User[], roles: Role[]) => { - const envUser = getEnvAuth(); - for (const user of users) { - cy.log(`Deleting user: ${JSON.stringify(user)}`); - cy.request({ - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'DELETE', - url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`), - failOnStatusCode: false, - }) - .its('status') - .should('oneOf', [204, 404]); - } - - for (const role of roles) { - cy.log(`Deleting role: ${JSON.stringify(role)}`); - cy.request({ - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, - method: 'DELETE', - url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`), - failOnStatusCode: false, - }) - .its('status') - .should('oneOf', [204, 404]); - } +// needed to generate index pattern +const visitSecuritySolution = () => { + loginAndWaitForHostDetailsPage(); + openSourcerer(); + logout(); }; const testCase: TestCaseWithoutTimeline = { @@ -205,11 +49,11 @@ const testCase: TestCaseWithoutTimeline = { reporter: 'elastic', owner: 'securitySolution', }; - describe('Cases privileges', () => { before(() => { cleanKibana(); createUsersAndRoles(usersToCreate, rolesToCreate); + visitSecuritySolution(); }); after(() => { 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 26c366e981d440..bd7acc38c10215 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 @@ -5,7 +5,10 @@ * 2.0. */ -import { loginAndWaitForPage } from '../../tasks/login'; +import { + loginAndWaitForPage, + loginWithUserAndWaitForPageWithoutDateRange, +} from '../../tasks/login'; import { HOSTS_URL } from '../../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../../tasks/hosts/all_hosts'; @@ -28,20 +31,34 @@ import { openTimelineUsingToggle } from '../../tasks/security_main'; import { populateTimeline } from '../../tasks/timeline'; import { SERVER_SIDE_EVENT_COUNT } from '../../screens/timeline'; import { cleanKibana } from '../../tasks/common'; +import { createUsersAndRoles, secReadCasesAll, secReadCasesAllUser } from '../../tasks/privileges'; +import { TOASTER } from '../../screens/configure_cases'; +const usersToCreate = [secReadCasesAllUser]; +const rolesToCreate = [secReadCasesAll]; // Skipped at the moment as this has flake due to click handler issues. This has been raised with team members // and the code is being re-worked and then these tests will be unskipped -describe.skip('Sourcerer', () => { - before(() => { +describe('Sourcerer', () => { + beforeEach(() => { cleanKibana(); }); - - beforeEach(() => { - cy.clearLocalStorage(); - loginAndWaitForPage(HOSTS_URL); + describe('permissions', () => { + before(() => { + createUsersAndRoles(usersToCreate, rolesToCreate); + }); + it(`role(s) ${secReadCasesAllUser.roles.join()} shows error when user does not have permissions`, () => { + loginWithUserAndWaitForPageWithoutDateRange(HOSTS_URL, secReadCasesAllUser); + cy.get(TOASTER).should('have.text', 'Write role required to generate data'); + }); }); + // Originially written in December 2020, flakey from day1 + // has always been skipped with intentions to fix, see note at top of file + describe.skip('Default scope', () => { + beforeEach(() => { + cy.clearLocalStorage(); + loginAndWaitForPage(HOSTS_URL); + }); - describe('Default scope', () => { it('has SIEM index patterns selected on initial load', () => { openSourcerer(); isSourcererSelection(`auditbeat-*`); @@ -52,7 +69,7 @@ describe.skip('Sourcerer', () => { isSourcererOptions([`metrics-*`, `logs-*`]); }); - it('selected KIP gets added to sourcerer', () => { + it('selected DATA_VIEW gets added to sourcerer', () => { setSourcererOption(`metrics-*`); openSourcerer(); isSourcererSelection(`metrics-*`); @@ -75,8 +92,14 @@ describe.skip('Sourcerer', () => { isNotSourcererSelection(`metrics-*`); }); }); + // Originially written in December 2020, flakey from day1 + // has always been skipped with intentions to fix + describe.skip('Timeline scope', () => { + beforeEach(() => { + cy.clearLocalStorage(); + loginAndWaitForPage(HOSTS_URL); + }); - describe('Timeline scope', () => { const alertPatterns = ['.siem-signals-default']; const rawPatterns = ['auditbeat-*']; const allPatterns = [...alertPatterns, ...rawPatterns]; 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 2cde29ec9da634..033a12dd9de3ee 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 @@ -9,6 +9,7 @@ import { ALERT_FLYOUT, CELL_TEXT, JSON_TEXT, TABLE_ROWS } from '../../screens/al import { expandFirstAlert, + refreshAlerts, waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded, } from '../../tasks/alerts'; @@ -32,6 +33,7 @@ describe('Alert details with unmapped fields', () => { createCustomRuleActivated(getUnmappedRule()); loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); + refreshAlerts(); expandFirstAlert(); }); @@ -46,9 +48,10 @@ describe('Alert details with unmapped fields', () => { }); }); + // This test needs to be updated to not look for the field in a specific row, as it prevents us from adding/removing fields it('Displays the unmapped field on the table', () => { const expectedUnmmappedField = { - row: 54, + row: 82, field: 'unmapped', text: 'This is the unmapped field', }; 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 49c2dd4b417171..e5b2c4eed3b003 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 @@ -54,6 +54,7 @@ describe('Alerts timeline', () => { loadDetectionsPage(ROLES.platform_engineer); }); + // Skipping due to alerts not refreshing for platform_engineer despite being returned from API? it.skip('should allow a user with crud privileges to attach alerts to cases', () => { cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true }); cy.get(ATTACH_ALERT_TO_CASE_BUTTON).first().should('not.be.disabled'); 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 10f556a11bf602..171d224cc32d35 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 @@ -70,7 +70,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { ALERTS_URL } from '../../urls/navigation'; -describe.skip('Detection rules, EQL', () => { +describe('Detection rules, EQL', () => { const expectedUrls = getEqlRule().referenceUrls.join(''); const expectedFalsePositives = getEqlRule().falsePositivesExamples.join(''); const expectedTags = getEqlRule().tags.join(''); @@ -169,7 +169,7 @@ describe.skip('Detection rules, EQL', () => { }); }); -describe.skip('Detection rules, sequence EQL', () => { +describe('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 02621ea49e9068..378de8f0bc5934 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 { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { goBackToAllRulesTable } from '../../tasks/rule_details'; import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation'; -import { DEFAULT_THREAT_MATCH_QUERY } from '../../../common/constants'; +const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d"'; describe('indicator match', () => { describe('Detection rules, Indicator Match', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts index ef3d3a82d40bd7..92f9e8180d50c9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts @@ -34,7 +34,6 @@ import { waitForRuleToChangeStatus, } from '../../tasks/alerts_detection_rules'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../../common/constants'; import { ALERTS_URL } from '../../urls/navigation'; import { createCustomRule } from '../../tasks/api_calls/rules'; @@ -46,6 +45,8 @@ import { getNewThresholdRule, } from '../../objects/rule'; +const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; + describe('Alerts detection rules', () => { beforeEach(() => { cleanKibana(); 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 84ad93fa089434..cea290eeef17be 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 @@ -35,7 +35,7 @@ import { import { ALERTS_URL } from '../../urls/navigation'; import { cleanKibana } 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 ea6b6cf0186b49..4af6467e5d33c2 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 @@ -35,7 +35,7 @@ import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; import { cleanKibana } 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/ml/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts index 89a0d5a660b97b..f9d78ba12a5ea9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts @@ -98,7 +98,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpNullKqlQuery); cy.url().should( 'include', - 'app/security/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + 'app/security/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -106,7 +106,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery); cy.url().should( 'include', - '/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -114,7 +114,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery); cy.url().should( 'include', - 'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + 'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -122,7 +122,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery); cy.url().should( 'include', - '/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -130,15 +130,16 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkNullKqlQuery); cy.url().should( 'include', - '/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); it('redirects from a $ip$ with a value for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery); + cy.url().should( 'include', - '/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + `/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))` ); }); @@ -146,7 +147,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostNullKqlQuery); cy.url().should( 'include', - '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -154,7 +155,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQueryVariable); cy.url().should( 'include', - '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -162,7 +163,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery); cy.url().should( 'include', - '/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -170,7 +171,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -178,7 +179,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -186,7 +187,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostVariableHostNullKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); @@ -194,7 +195,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery); cy.url().should( 'include', - '/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:!(%27auditbeat-*%27))' + '/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))' ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts index fb41aec91b6c4a..cbff911e5d9826 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts @@ -121,7 +121,6 @@ describe('Create a timeline from a template', () => { loginAndWaitForPageWithoutDateRange(TIMELINE_TEMPLATES_URL); waitForTimelinesPanelToBeLoaded(); }); - it('Should have the same query and open the timeline modal', () => { selectCustomTemplates(); cy.wait('@timeline', { timeout: 100000 }); @@ -132,5 +131,6 @@ describe('Create a timeline from a template', () => { cy.get(TIMELINE_FLYOUT_WRAPPER).should('have.css', 'visibility', 'visible'); cy.get(TIMELINE_DESCRIPTION).should('have.text', getTimeline().description); cy.get(TIMELINE_QUERY).should('have.text', getTimeline().query); + closeTimeline(); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts index eeb7f91f8c0505..de754f86026679 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts @@ -20,7 +20,7 @@ import { addDataProvider, closeTimeline, createNewTimeline } from '../../tasks/t import { HOSTS_URL } from '../../urls/navigation'; import { cleanKibana, scrollToBottom } from '../../tasks/common'; -describe.skip('timeline data providers', () => { +describe('timeline data providers', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts index 73eb141f1ce3d9..28fe1294e6f01a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts @@ -182,11 +182,10 @@ describe('url state', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url); kqlSearch('source.ip: "10.142.0.9" {enter}'); navigateFromHeaderTo(HOSTS); - cy.get(NETWORK).should( 'have.attr', 'href', - `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))` + `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))` ); }); @@ -199,12 +198,12 @@ describe('url state', () => { cy.get(HOSTS).should( 'have.attr', 'href', - `/app/security/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); cy.get(NETWORK).should( 'have.attr', 'href', - `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); cy.get(HOSTS_NAMES).first().should('have.text', 'siem-kibana'); @@ -215,21 +214,21 @@ describe('url state', () => { cy.get(ANOMALIES_TAB).should( 'have.attr', 'href', - "/app/security/hosts/siem-kibana/anomalies?sourcerer=(default:!('auditbeat-*'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" + "/app/security/hosts/siem-kibana/anomalies?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" ); cy.get(BREADCRUMBS) .eq(1) .should( 'have.attr', 'href', - `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); cy.get(BREADCRUMBS) .eq(2) .should( 'have.attr', 'href', - `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); }); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index ae04e20dfe86eb..0a9eecf83c7fc9 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -419,7 +419,63 @@ export const getEditedRule = (): CustomRule => ({ }); export const expectedExportedRule = (ruleResponse: Cypress.Response): string => { - const jsonrule = ruleResponse.body; - - return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"100m","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-50000h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_rules_count":1,"missing_rules":[],"missing_rules_count":0,"exported_exception_list_count":0,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`; + const { + id, + updated_at: updatedAt, + updated_by: updatedBy, + created_at: createdAt, + description, + name, + risk_score: riskScore, + severity, + query, + } = ruleResponse.body; + const rule = { + id, + updated_at: updatedAt, + updated_by: updatedBy, + created_at: createdAt, + created_by: 'elastic', + name, + tags: [], + interval: '100m', + enabled: false, + description, + risk_score: riskScore, + severity, + output_index: '.siem-signals-default', + author: [], + false_positives: [], + from: 'now-50000h', + rule_id: 'rule_testing', + max_signals: 100, + risk_score_mapping: [], + severity_mapping: [], + threat: [], + to: 'now', + references: [], + version: 1, + exceptions_list: [], + immutable: false, + type: 'query', + language: 'kuery', + index: ['exceptions-*'], + query, + throttle: 'no_actions', + actions: [], + }; + const details = { + exported_count: 1, + exported_rules_count: 1, + missing_rules: [], + missing_rules_count: 0, + exported_exception_list_count: 0, + exported_exception_list_item_count: 0, + missing_exception_list_item_count: 0, + missing_exception_list_items: [], + missing_exception_lists: [], + missing_exception_lists_count: 0, + }; + + return `${JSON.stringify(rule)}\n${JSON.stringify(details)}\n`; }; diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts index f3d9bc1b9ef1aa..70b8c1b400d516 100644 --- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts @@ -87,6 +87,7 @@ export const expectedExportedTimelineTemplate = ( }, }, }, + dataViewId: timelineTemplateBody.dataViewId, dateRange: { start: timelineTemplateBody.dateRange?.start, end: timelineTemplateBody.dateRange?.end, @@ -127,6 +128,7 @@ export const expectedExportedTimeline = (timelineResponse: Cypress.Response
) : ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx index 1135a29759315a..5153fbb73ba712 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx @@ -57,7 +57,8 @@ export const withSecurityContext =

({ }), { management: undefined, - // @ts-ignore ignore this error as we just need the enableExperimental and it's temporary + // ignore this error as we just need the enableExperimental and it's temporary + // @ts-expect-error TS2739 app: { enableExperimental: ExperimentalFeaturesService.get(), }, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx index 917ffe49c60906..50564c39935fa8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx @@ -25,8 +25,7 @@ import { import { Immutable } from '../../../../../../../common/endpoint/types'; import { HttpFetchOptionsWithPath } from 'kibana/public'; -// FLAKY https://github.com/elastic/kibana/issues/115100 -describe.skip('When using the RemoveTrustedAppFromPolicyModal component', () => { +describe('When using the RemoveTrustedAppFromPolicyModal component', () => { let appTestContext: AppContextTestRender; let renderResult: ReturnType; let render: (waitForLoadedState?: boolean) => Promise>; @@ -50,7 +49,7 @@ describe.skip('When using the RemoveTrustedAppFromPolicyModal component', () => mockedApis.responseProvider.trustedAppUpdate.mockDelay.mockImplementation( () => new Promise((resolve) => { - setTimeout(resolve, 20); + setTimeout(resolve, 100); }) ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/index.ts index c017e7dec52488..44f378b15fa0fa 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { LogicalConditionBuilder, LogicalConditionBuilderProps } from './logical_condition_builder'; +export type { LogicalConditionBuilderProps } from './logical_condition_builder'; +export { LogicalConditionBuilder } from './logical_condition_builder'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index 0b1b5d4c5675f2..f22cc1179f0d32 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -476,7 +476,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >

Last updated
@@ -486,7 +486,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -522,7 +522,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -532,7 +532,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -640,7 +640,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -704,7 +704,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -734,7 +734,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -743,16 +743,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 0 -

+ Trusted App 0

Last updated
@@ -869,7 +866,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -905,7 +902,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -915,7 +912,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -1023,7 +1020,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -1087,7 +1084,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -1117,7 +1114,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -1126,16 +1123,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 1 -

+ Trusted App 1

Last updated
@@ -1252,7 +1246,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -1288,7 +1282,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -1298,7 +1292,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -1406,7 +1400,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -1470,7 +1464,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -1500,7 +1494,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -1509,16 +1503,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 2 -

+ Trusted App 2

Last updated
@@ -1635,7 +1626,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -1671,7 +1662,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -1681,7 +1672,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -1789,7 +1780,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -1853,7 +1844,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -1883,7 +1874,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -1892,16 +1883,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 3 -

+ Trusted App 3

Last updated
@@ -2018,7 +2006,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -2054,7 +2042,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -2064,7 +2052,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -2172,7 +2160,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -2236,7 +2224,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -2266,7 +2254,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -2275,16 +2263,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 4 -

+ Trusted App 4

Last updated
@@ -2401,7 +2386,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -2437,7 +2422,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -2447,7 +2432,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -2555,7 +2540,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -2619,7 +2604,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -2649,7 +2634,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -2658,16 +2643,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 5 -

+ Trusted App 5

Last updated
@@ -2784,7 +2766,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -2820,7 +2802,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -2830,7 +2812,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -2938,7 +2920,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -3002,7 +2984,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -3032,7 +3014,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -3041,16 +3023,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 6 -

+ Trusted App 6

Last updated
@@ -3167,7 +3146,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -3203,7 +3182,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -3213,7 +3192,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -3321,7 +3300,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -3385,7 +3364,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -3415,7 +3394,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -3424,16 +3403,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 7 -

+ Trusted App 7

Last updated
@@ -3550,7 +3526,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -3586,7 +3562,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -3596,7 +3572,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -3704,7 +3680,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -3768,7 +3744,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -3798,7 +3774,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -3807,16 +3783,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 8 -

+ Trusted App 8

Last updated
@@ -3933,7 +3906,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -3969,7 +3942,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -3979,7 +3952,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -4087,7 +4060,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -4151,7 +4124,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -4181,7 +4154,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -4190,16 +4163,13 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
-

- Trusted App 9 -

+ Trusted App 9

Last updated
@@ -4623,7 +4593,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -4659,7 +4629,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -4669,7 +4639,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -4777,7 +4747,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -4841,7 +4811,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -4871,7 +4841,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -4880,16 +4850,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 0 -

+ Trusted App 0

Last updated
@@ -5006,7 +4973,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -5042,7 +5009,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -5052,7 +5019,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -5160,7 +5127,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -5224,7 +5191,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -5254,7 +5221,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -5263,16 +5230,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 1 -

+ Trusted App 1

Last updated
@@ -5389,7 +5353,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -5425,7 +5389,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -5435,7 +5399,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -5543,7 +5507,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -5607,7 +5571,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -5637,7 +5601,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -5646,16 +5610,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 2 -

+ Trusted App 2

Last updated
@@ -5772,7 +5733,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -5808,7 +5769,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -5818,7 +5779,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -5926,7 +5887,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -5990,7 +5951,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -6020,7 +5981,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -6029,16 +5990,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 3 -

+ Trusted App 3

Last updated
@@ -6155,7 +6113,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -6191,7 +6149,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -6201,7 +6159,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -6309,7 +6267,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -6373,7 +6331,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -6403,7 +6361,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -6412,16 +6370,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 4 -

+ Trusted App 4

Last updated
@@ -6538,7 +6493,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -6574,7 +6529,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -6584,7 +6539,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -6692,7 +6647,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -6756,7 +6711,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -6786,7 +6741,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -6795,16 +6750,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 5 -

+ Trusted App 5

Last updated
@@ -6921,7 +6873,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -6957,7 +6909,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -6967,7 +6919,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -7075,7 +7027,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -7139,7 +7091,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -7169,7 +7121,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -7178,16 +7130,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 6 -

+ Trusted App 6

Last updated
@@ -7304,7 +7253,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -7340,7 +7289,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -7350,7 +7299,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -7458,7 +7407,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -7522,7 +7471,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -7552,7 +7501,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -7561,16 +7510,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 7 -

+ Trusted App 7

Last updated
@@ -7687,7 +7633,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -7723,7 +7669,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -7733,7 +7679,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -7841,7 +7787,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -7905,7 +7851,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -7935,7 +7881,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -7944,16 +7890,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 8 -

+ Trusted App 8

Last updated
@@ -8070,7 +8013,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -8106,7 +8049,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -8116,7 +8059,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -8224,7 +8167,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -8288,7 +8231,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -8318,7 +8261,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -8327,16 +8270,13 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
-

- Trusted App 9 -

+ Trusted App 9

Last updated
@@ -8717,7 +8657,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -8753,7 +8693,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -8763,7 +8703,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -8871,7 +8811,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -8935,7 +8875,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -8965,7 +8905,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -8974,16 +8914,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 0 -

+ Trusted App 0

Last updated
@@ -9100,7 +9037,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -9136,7 +9073,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -9146,7 +9083,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -9254,7 +9191,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -9318,7 +9255,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -9348,7 +9285,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -9357,16 +9294,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 1 -

+ Trusted App 1

Last updated
@@ -9483,7 +9417,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -9519,7 +9453,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -9529,7 +9463,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -9637,7 +9571,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -9701,7 +9635,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -9731,7 +9665,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -9740,16 +9674,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 2 -

+ Trusted App 2

Last updated
@@ -9866,7 +9797,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -9902,7 +9833,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -9912,7 +9843,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -10020,7 +9951,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -10084,7 +10015,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -10114,7 +10045,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -10123,16 +10054,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 3 -

+ Trusted App 3

Last updated
@@ -10249,7 +10177,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -10285,7 +10213,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -10295,7 +10223,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -10403,7 +10331,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -10467,7 +10395,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -10497,7 +10425,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -10506,16 +10434,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 4 -

+ Trusted App 4

Last updated
@@ -10632,7 +10557,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -10668,7 +10593,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -10678,7 +10603,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -10786,7 +10711,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -10850,7 +10775,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -10880,7 +10805,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -10889,16 +10814,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 5 -

+ Trusted App 5

Last updated
@@ -11015,7 +10937,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -11051,7 +10973,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -11061,7 +10983,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -11169,7 +11091,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -11233,7 +11155,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -11263,7 +11185,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -11272,16 +11194,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 6 -

+ Trusted App 6

Last updated
@@ -11398,7 +11317,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -11434,7 +11353,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -11444,7 +11363,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -11552,7 +11471,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -11616,7 +11535,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -11646,7 +11565,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -11655,16 +11574,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 7 -

+ Trusted App 7

Last updated
@@ -11781,7 +11697,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -11817,7 +11733,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -11827,7 +11743,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -11935,7 +11851,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -11999,7 +11915,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -12029,7 +11945,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -12038,16 +11954,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 8 -

+ Trusted App 8

Last updated
@@ -12164,7 +12077,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -12200,7 +12113,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -12210,7 +12123,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -12318,7 +12231,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -12382,7 +12295,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -12412,7 +12325,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -12421,16 +12334,13 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
-

- Trusted App 9 -

+ Trusted App 9

{ http.get.mockImplementation(async (...args) => { const path = args[0] as unknown as string; - // @ts-ignore + // @ts-expect-error TS2352 const httpOptions = args[1] as HttpFetchOptions; if (path === TRUSTED_APPS_LIST_API) { @@ -591,7 +591,7 @@ describe('When on the Trusted Apps Page', () => { // we can control when the API call response is returned, which will allow us // to test the UI behaviours while the API call is in flight coreStart.http.post.mockImplementation( - // @ts-ignore + // @ts-expect-error TS2345 async (_, options: HttpFetchOptions) => { return new Promise((resolve, reject) => { httpPostBody = options.body as string; @@ -794,7 +794,7 @@ describe('When on the Trusted Apps Page', () => { beforeEach(() => { const priorMockImplementation = coreStart.http.get.getMockImplementation(); - // @ts-ignore + // @ts-expect-error TS7006 coreStart.http.get.mockImplementation((path, options) => { if (path === TRUSTED_APPS_LIST_API) { const { page, per_page: perPage } = options.query as { page: number; per_page: number }; @@ -958,7 +958,7 @@ describe('When on the Trusted Apps Page', () => { beforeEach(async () => { // Ensure implementation is defined before render to avoid undefined responses from hidden api calls const priorMockImplementation = coreStart.http.get.getMockImplementation(); - // @ts-ignore + // @ts-expect-error TS7006 coreStart.http.get.mockImplementation((path, options) => { if (path === PACKAGE_POLICY_API_ROUTES.LIST_PATTERN) { const policy = generator.generatePolicyPackagePolicy(); diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx index 63a4571ca11e52..d9201e62268abc 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx @@ -52,7 +52,7 @@ jest.mock('./index_patterns_missing_prompt', () => { describe('EmbeddedMapComponent', () => { const setQuery: jest.Mock = jest.fn(); const mockSelector = { - kibanaIndexPatterns: [ + kibanaDataViews: [ { id: '6f1eeb50-023d-11eb-bcb6-6ba0578012a9', title: 'filebeat-*' }, { id: '28995490-023d-11eb-bcb6-6ba0578012a9', title: 'auditbeat-*' }, ], @@ -132,7 +132,7 @@ describe('EmbeddedMapComponent', () => { const spy = jest.spyOn(redux, 'useSelector'); spy.mockReturnValue({ ...mockSelector, - kibanaIndexPatterns: [], + kibanaDataViews: [], }); (createEmbeddable as jest.Mock).mockResolvedValue(mockCreateEmbeddable); diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx index f2cb974a5b5c1a..15a0f870d7bdaa 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx @@ -28,8 +28,9 @@ import * as i18n from './translations'; import { MapEmbeddable } from '../../../../../../plugins/maps/public/embeddable'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { useKibana } from '../../../common/lib/kibana'; -import { getDefaultSourcererSelector } from './selector'; import { getLayerList } from './map_config'; +import { sourcererSelectors } from '../../../common/store/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; interface EmbeddableMapProps { @@ -95,13 +96,13 @@ export const EmbeddedMapComponent = ({ const [isIndexError, setIsIndexError] = useState(false); const [, dispatchToaster] = useStateToaster(); - const defaultSourcererScopeSelector = useMemo(getDefaultSourcererSelector, []); - const { kibanaIndexPatterns, sourcererScope } = useDeepEqualSelector( - defaultSourcererScopeSelector - ); + + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); + const { kibanaDataViews, sourcererScope }: sourcererSelectors.SourcererScopeSelector = + useDeepEqualSelector((state) => sourcererScopeSelector(state, SourcererScopeName.default)); const [mapIndexPatterns, setMapIndexPatterns] = useState( - kibanaIndexPatterns.filter((kip) => sourcererScope.selectedPatterns.includes(kip.title)) + kibanaDataViews.filter((dataView) => sourcererScope.selectedPatterns.includes(dataView.title)) ); // This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our @@ -114,8 +115,8 @@ export const EmbeddedMapComponent = ({ useEffect(() => { setMapIndexPatterns((prevMapIndexPatterns) => { - const newIndexPatterns = kibanaIndexPatterns.filter((kip) => - sourcererScope.selectedPatterns.includes(kip.title) + const newIndexPatterns = kibanaDataViews.filter((dataView) => + sourcererScope.selectedPatterns.includes(dataView.title) ); if (!deepEqual(newIndexPatterns, prevMapIndexPatterns)) { if (newIndexPatterns.length === 0) { @@ -125,7 +126,7 @@ export const EmbeddedMapComponent = ({ } return prevMapIndexPatterns; }); - }, [kibanaIndexPatterns, sourcererScope.selectedPatterns]); + }, [kibanaDataViews, sourcererScope.selectedPatterns]); // Initial Load useEffect useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/selector.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/selector.test.tsx deleted file mode 100644 index 99efe50a467a29..00000000000000 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/selector.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { State } from '../../../common/store'; - -import { getDefaultSourcererSelector } from './selector'; - -jest.mock('../../../common/store/sourcerer', () => ({ - sourcererSelectors: { - kibanaIndexPatternsSelector: jest.fn().mockReturnValue(jest.fn()), - scopesSelector: jest.fn().mockReturnValue(jest.fn().mockReturnValue({ default: '' })), - }, -})); - -describe('getDefaultSourcererSelector', () => { - test('Returns correct format', () => { - const mockMapStateToProps = getDefaultSourcererSelector(); - const result = mockMapStateToProps({} as State); - expect(result).toHaveProperty('kibanaIndexPatterns'); - expect(result).toHaveProperty('sourcererScope'); - }); -}); diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/selector.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/selector.tsx deleted file mode 100644 index c646276d710a28..00000000000000 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/selector.tsx +++ /dev/null @@ -1,36 +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 { State } from '../../../common/store'; -import { sourcererSelectors } from '../../../common/store/sourcerer'; -import { - KibanaIndexPatterns, - ManageScope, - SourcererScopeName, -} from '../../../common/store/sourcerer/model'; - -export interface DefaultSourcererSelector { - kibanaIndexPatterns: KibanaIndexPatterns; - sourcererScope: ManageScope; -} - -export const getDefaultSourcererSelector = () => { - const getKibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); - const getScopesSelector = sourcererSelectors.scopesSelector(); - - const mapStateToProps = (state: State): DefaultSourcererSelector => { - const kibanaIndexPatterns = getKibanaIndexPatternsSelector(state); - const scope = getScopesSelector(state)[SourcererScopeName.default]; - - return { - kibanaIndexPatterns, - sourcererScope: scope, - }; - }; - - return mapStateToProps; -}; diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx index 28953730ce3cfb..4c50a3a0f49c5f 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx @@ -8,8 +8,7 @@ import { get } from 'lodash/fp'; import numeral from '@elastic/numeral'; import React from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; - +import { DataViewBase } from '@kbn/es-query'; import { CountryFlagAndName } from '../source_destination/country_flag'; import { FlowTargetSourceDest, @@ -47,7 +46,7 @@ export type NetworkTopCountriesColumnsNetworkDetails = [ ]; export const getNetworkTopCountriesColumns = ( - indexPattern: IIndexPattern, + indexPattern: DataViewBase, flowTarget: FlowTargetSourceDest, type: networkModel.NetworkType, tableId: string @@ -161,7 +160,7 @@ export const getNetworkTopCountriesColumns = ( ]; export const getCountriesColumnsCurated = ( - indexPattern: IIndexPattern, + indexPattern: DataViewBase, flowTarget: FlowTargetSourceDest, type: networkModel.NetworkType, tableId: string diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx index d4a611c7f557fc..23b453c0f73844 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx @@ -9,7 +9,7 @@ import { last } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import deepEqual from 'fast-deep-equal'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { networkActions, networkModel, networkSelectors } from '../../store'; import { @@ -31,7 +31,7 @@ interface NetworkTopCountriesTableProps { fakeTotalCount: number; flowTargeted: FlowTargetSourceDest; id: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isInspect: boolean; loading: boolean; loadPage: (newActivePage: number) => void; diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx index 612acdc813c926..7d8389d95f33e6 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx @@ -10,7 +10,7 @@ import { Router, useParams } from 'react-router-dom'; import '../../../common/mock/match_media'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { mockGlobalState, TestProviders, @@ -87,7 +87,7 @@ const getMockHistory = (ip: string) => ({ describe('Network Details', () => { const mount = useMountAppended(); beforeAll(() => { - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: false, indexPattern: {}, }); @@ -131,7 +131,7 @@ describe('Network Details', () => { test('it renders ipv6 headline', async () => { const ip = 'fe80--24ce-f7ff-fede-a571'; - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index 5aee4022a7f309..e0ede2d23846e9 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -48,7 +48,7 @@ import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anom import { esQuery } from '../../../../../../../src/plugins/data/public'; import { networkModel } from '../../store'; import { SecurityPageName } from '../../../app/types'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; export { getBreadcrumbs } from './utils'; @@ -92,7 +92,7 @@ const NetworkDetailsComponent: React.FC = () => { dispatch(setNetworkDetailsTablesActivePageToZero()); }, [detailName, dispatch]); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const ip = decodeIpv6(detailName); const [filterQuery, kqlError] = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(uiSettings), diff --git a/x-pack/plugins/security_solution/public/network/pages/details/types.ts b/x-pack/plugins/security_solution/public/network/pages/details/types.ts index b91a22cbd5fc32..02722f4709bccf 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/details/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IIndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { ESTermQuery } from '../../../../common/typed_json'; import { NetworkType } from '../../store/model'; @@ -38,5 +38,5 @@ export type TlsQueryTableComponentProps = OwnProps & { export type NetworkWithIndexComponentsQueryTableProps = OwnProps & { flowTarget: FlowTargetSourceDest; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; }; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts index 075aa46637a072..96b72caf033003 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { DataViewBase } from '@kbn/es-query'; import { ESTermQuery } from '../../../../common/typed_json'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { NavTab } from '../../../common/components/navigation/types'; import { FlowTargetSourceDest } from '../../../../common/search_strategy/security_solution/network'; @@ -32,7 +32,7 @@ export type NetworkComponentQueryProps = QueryTabBodyProps & { }; export type IPsQueryTabBodyProps = QueryTabBodyProps & { - indexPattern: IIndexPattern; + indexPattern: DataViewBase; flowTarget: FlowTargetSourceDest; }; @@ -49,7 +49,7 @@ export type NetworkRoutesProps = GlobalTimeArgs & { docValueFields: DocValueFields[]; type: networkModel.NetworkType; filterQuery?: string | ESTermQuery; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; indexNames: string[]; setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker; }; diff --git a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx index 764b8fcd0444b1..d0fff882cdc865 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx @@ -11,7 +11,7 @@ import { Router } from 'react-router-dom'; import { waitFor } from '@testing-library/react'; import '../../common/mock/match_media'; import { Filter } from '../../../../../../src/plugins/data/common/es_query'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { TestProviders, mockGlobalState, @@ -70,10 +70,10 @@ const mockProps = { capabilitiesFetched: true, hasMlUserPermissions: true, }; -const mockUseSourcererScope = useSourcererScope as jest.Mock; +const mockUseSourcererDataView = useSourcererDataView as jest.Mock; describe('Network page - rendering', () => { test('it renders the Setup Instructions text when no index is available', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: false, }); @@ -89,7 +89,7 @@ describe('Network page - rendering', () => { }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -138,7 +138,7 @@ describe('Network page - rendering', () => { }, }, ]; - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: { fields: [], title: 'title' }, diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index af7f513afd7446..f38a26da005996 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -49,7 +49,7 @@ import { import { timelineSelectors } from '../../timelines/store/timeline'; import { TimelineId } from '../../../common/types/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; /** @@ -109,7 +109,7 @@ const NetworkComponent = React.memo( [dispatch] ); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const onSkipFocusBeforeEventsTable = useCallback(() => { containerElement.current diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx index e74e8f82d82442..bce9dd9fa9d0ce 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx @@ -9,17 +9,13 @@ import numeral from '@elastic/numeral'; import React, { useEffect, useMemo, useCallback } from 'react'; import { Position } from '@elastic/charts'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/alerts_viewer/translations'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { HostsTableType } from '../../../hosts/store/model'; import * as i18n from '../../pages/translations'; @@ -42,7 +38,7 @@ const DEFAULT_STACK_BY = 'event.module'; interface Props extends Pick { filters: Filter[]; hideHeaderChildren?: boolean; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; indexNames: string[]; query: Query; } diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx index 919057d6b5eab4..8ec38549477fad 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx @@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { ID as OverviewHostQueryId } from '../../containers/overview_host'; import { OverviewHost } from '../overview_host'; import { OverviewNetwork } from '../overview_network'; @@ -16,12 +17,7 @@ import { filterHostData } from '../../../hosts/pages/navigation/alerts_query_tab import { useKibana } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; import { filterNetworkData } from '../../../network/pages/navigation/alerts_query_tab_body'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; @@ -32,7 +28,7 @@ const HorizontalSpacer = styled(EuiFlexItem)` interface Props extends Pick { filters: Filter[]; indexNames: string[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; query: Query; } diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 562d1d1fd7aadb..a184ba572d77c3 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -10,6 +10,7 @@ import numeral from '@elastic/numeral'; import React, { useEffect, useMemo, useCallback } from 'react'; import uuid from 'uuid'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/events_viewer/translations'; import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; @@ -22,12 +23,7 @@ import { eventsStackByOptions } from '../../../hosts/pages/navigation'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { histogramConfigs } from '../../../hosts/pages/navigation/events_query_tab_body'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { HostsTableType } from '../../../hosts/store/model'; import { InputsModelId } from '../../../common/store/inputs/constants'; import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; @@ -46,7 +42,7 @@ interface Props extends Pick { clearAllMessages: () => undefined, }; }; -const mockUseSourcererScope = useSourcererScope as jest.Mock; +const mockUseSourcererDataView = useSourcererDataView as jest.Mock; const mockUseUserPrivileges = useUserPrivileges as jest.Mock; const mockUseFetchIndex = useFetchIndex as jest.Mock; const mockUseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; @@ -141,7 +141,7 @@ describe('Overview', () => { describe('rendering', () => { test('it DOES NOT render the Getting started text when an index is available', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -168,7 +168,7 @@ describe('Overview', () => { indexExists: false, }, ]); - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -195,7 +195,7 @@ describe('Overview', () => { indexExists: false, }, ]); - mockUseSourcererScope.mockReturnValueOnce({ + mockUseSourcererDataView.mockReturnValueOnce({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -216,7 +216,7 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when the endpoint index is available AND storage is set', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indexExists: true, indexPattern: {}, @@ -237,7 +237,7 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when an index IS available but storage is NOT set', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -258,7 +258,7 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when Ingest is NOT available', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -281,7 +281,7 @@ describe('Overview', () => { describe('when no index is available', () => { beforeEach(() => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: false, }); diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 10fa4e4c4e925f..3a98f062db65d4 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -27,7 +27,7 @@ import { SecurityPageName } from '../../app/types'; import { EndpointNotice } from '../components/endpoint_notice'; import { useMessagesStorage } from '../../common/containers/local_storage/use_messages_storage'; import { ENDPOINT_METADATA_INDEX } from '../../../common/constants'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { Sourcerer } from '../../common/components/sourcerer'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useDeepEqualSelector } from '../../common/hooks/use_selector'; @@ -56,7 +56,7 @@ const OverviewComponent = () => { const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { from, deleteQuery, setQuery, to } = useGlobalTime(); - const { indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const endpointMetadataIndex = useMemo(() => { return [ENDPOINT_METADATA_INDEX]; diff --git a/x-pack/plugins/security_solution/public/overview/pages/summary.tsx b/x-pack/plugins/security_solution/public/overview/pages/summary.tsx deleted file mode 100644 index e706fcc39bd1ea..00000000000000 --- a/x-pack/plugins/security_solution/public/overview/pages/summary.tsx +++ /dev/null @@ -1,90 +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 React from 'react'; -import { EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { ADD_DATA_PATH } from '../../../common/constants'; - -import { useKibana } from '../../common/lib/kibana'; - -export const Summary = React.memo(() => { - const { docLinks, http } = useKibana().services; - const basePath = http.basePath.get(); - return ( - - -

- -

- -

- - - - ), - data: ( - - - - ), - siemSolution: ( - - - - ), - }} - /> -

- -

- -

- -

- - - - ), - }} - /> -

-
-
- ); -}); - -Summary.displayName = 'Summary'; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 6167aa72a47b43..1d60175d56f60c 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -43,14 +43,11 @@ import { APP_ICON_SOLUTION, DETECTION_ENGINE_INDEX_URL, SERVER_APP_ID, + SOURCERER_API_URL, } from '../common/constants'; import { getDeepLinks } from './app/deep_links'; import { getSubPluginRoutesByCapabilities, manageOldSiemRoutes } from './helpers'; -import { - IndexFieldsStrategyRequest, - IndexFieldsStrategyResponse, -} from '../common/search_strategy/index_fields'; import { SecurityAppStore } from './common/store/store'; import { licenseService } from './common/hooks/use_license'; import { SecuritySolutionUiConfigType } from './common/types'; @@ -65,6 +62,8 @@ import { } from '../common/experimental_features'; import type { TimelineState } from '../../timelines/public'; import { LazyEndpointCustomAssetsExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension'; +import { initDataView, SourcererModel, KibanaDataView } from './common/store/sourcerer/model'; +import { SecurityDataView } from './common/containers/sourcerer/api'; export class Plugin implements IPlugin { readonly kibanaVersion: string; @@ -328,7 +327,6 @@ export class Plugin implements IPlugin { if (!this._store) { - const defaultIndicesName = coreStart.uiSettings.get(DEFAULT_INDEX_KEY); - const [{ createStore, createInitialState }, kibanaIndexPatterns, configIndexPatterns] = - await Promise.all([ - this.lazyApplicationDependencies(), - startPlugins.data.indexPatterns.getIdsWithTitle(), - startPlugins.data.search - .search( - { indices: defaultIndicesName, onlyCheckIfIndicesExist: true }, - { - strategy: 'indexFields', - } - ) - .toPromise(), - ]); - let signal: { name: string | null } = { name: null }; try { - // const { index_name: indexName } = await coreStart.http.fetch( - // `${BASE_RAC_ALERTS_API_PATH}/index`, - // { - // method: 'GET', - // query: { features: SERVER_APP_ID }, - // } - // ); - // signal = { name: indexName[0] }; - if (coreStart.application.capabilities[SERVER_APP_ID].read === true) { + if (coreStart.application.capabilities[SERVER_APP_ID].show === true) { signal = await coreStart.http.fetch(DETECTION_ENGINE_INDEX_URL, { method: 'GET', }); @@ -372,6 +347,28 @@ export class Plugin implements IPlugin ({ + ...initDataView, + ...dataView, + })); + } catch (error) { + defaultDataView = { ...initDataView, error }; + kibanaDataViews = []; + } + const { createStore, createInitialState } = await this.lazyApplicationDependencies(); + const appLibs: AppObservableLibs = { kibana: coreStart }; const libs$ = new BehaviorSubject(appLibs); @@ -414,8 +411,8 @@ export class Plugin implements IPlugin +The predefined schemas are located here ```typescript const supportedSchemas: SupportedSchema[] = [ diff --git a/x-pack/plugins/security_solution/public/resolver/store/camera/index.ts b/x-pack/plugins/security_solution/public/resolver/store/camera/index.ts index f900ab92a58b58..e42906ec2029fa 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/camera/index.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/camera/index.ts @@ -20,4 +20,4 @@ * would not be in the camera's viewport would be ignored. */ export { cameraReducer } from './reducer'; -export { CameraAction } from './action'; +export type { CameraAction } from './action'; diff --git a/x-pack/plugins/security_solution/public/shared_imports.ts b/x-pack/plugins/security_solution/public/shared_imports.ts index dda4179cd853cf..8934ad9dab4cdc 100644 --- a/x-pack/plugins/security_solution/public/shared_imports.ts +++ b/x-pack/plugins/security_solution/public/shared_imports.ts @@ -5,28 +5,30 @@ * 2.0. */ +export type { + FieldHook, + FieldValidateResponse, + FormData, + FormHook, + FormSchema, + ValidationError, + ValidationFunc, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { getUseField, getFieldValidityAndErrorMessage, - FieldHook, - FieldValidateResponse, FIELD_TYPES, Form, - FormData, FormDataProvider, - FormHook, - FormSchema, UseField, UseMultiFields, useForm, useFormContext, useFormData, - ValidationError, - ValidationFunc, VALIDATION_TYPES, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { Field, SelectField } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers'; -export { ERROR_CODE } from '../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; +export type { ERROR_CODE } from '../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; export { ExceptionBuilder } from '../../lists/public'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx index 206fcb2dc087ca..7b103fae483b19 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx @@ -12,12 +12,12 @@ import { TestProviders, mockIndexNames, mockIndexPattern } from '../../../../com import { TimelineId } from '../../../../../common/types/timeline'; import { useTimelineKpis } from '../../../containers/kpis'; import { FlyoutHeader } from '.'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockBrowserFields, mockDocValueFields } from '../../../../common/containers/source/mock'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { getEmptyValue } from '../../../../common/components/empty_value'; -const mockUseSourcererScope: jest.Mock = useSourcererScope as jest.Mock; +const mockUseSourcererDataView: jest.Mock = useSourcererDataView as jest.Mock; jest.mock('../../../../common/containers/sourcerer'); const mockUseTimelineKpis: jest.Mock = useTimelineKpis as jest.Mock; @@ -62,7 +62,7 @@ describe('header', () => { beforeEach(() => { // Mocking these services is required for the header component to render. - mockUseSourcererScope.mockImplementation(() => defaultMocks); + mockUseSourcererDataView.mockImplementation(() => defaultMocks); useKibanaMock().services.application.capabilities = { navLinks: {}, management: {}, diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index 1fdfb744f30710..2f54cd6ce29620 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -40,7 +40,7 @@ import { useGetUserCasesPermissions, useKibana } from '../../../../common/lib/ki import { InspectButton } from '../../../../common/components/inspect'; import { useTimelineKpis } from '../../../containers/kpis'; import { esQuery } from '../../../../../../../../src/plugins/data/public'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { startSelector, @@ -76,7 +76,7 @@ const ActiveTimelinesContainer = styled(EuiFlexItem)` const FlyoutHeaderPanelComponent: React.FC = ({ timelineId }) => { const dispatch = useDispatch(); - const { indexPattern, browserFields } = useSourcererScope(SourcererScopeName.timeline); + const { browserFields, indexPattern } = useSourcererDataView(SourcererScopeName.timeline); const { uiSettings } = useKibana().services; const esQueryConfig = useMemo(() => esQuery.getEsQueryConfig(uiSettings), [uiSettings]); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); @@ -345,7 +345,7 @@ const TimelineStatusInfoComponent: React.FC = ({ timelineId } const TimelineStatusInfo = React.memo(TimelineStatusInfoComponent); const FlyoutHeaderComponent: React.FC = ({ timelineId }) => { - const { selectedPatterns, indexPattern, docValueFields, browserFields } = useSourcererScope( + const { selectedPatterns, indexPattern, docValueFields, browserFields } = useSourcererDataView( SourcererScopeName.timeline ); const getStartSelector = useMemo(() => startSelector(), []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx index d672a3c6997072..c16da160022653 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx @@ -13,15 +13,10 @@ import { useGlobalFullScreen, useTimelineFullScreen, } from '../../../common/containers/use_full_screen'; -import { mockTimelineModel, TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../../common/mock'; import { TimelineId } from '../../../../common/types/timeline'; import { GraphOverlay } from '.'; -jest.mock('../../../common/hooks/use_selector', () => ({ - useShallowEqualSelector: jest.fn().mockReturnValue(mockTimelineModel.savedObjectId), - useDeepEqualSelector: jest.fn().mockReturnValue(mockTimelineModel), -})); - jest.mock('../../../common/containers/use_full_screen', () => ({ useGlobalFullScreen: jest.fn(), useTimelineFullScreen: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index 16459381a84318..31a40c46fc0bfa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -30,7 +30,6 @@ import { TimelineId } from '../../../../common/types/timeline'; import { timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; import { isFullScreen } from '../timeline/body/column_headers'; -import { sourcererSelectors } from '../../../common/store'; import { updateTimelineGraphEventId } from '../../../timelines/store/timeline/actions'; import { Resolver } from '../../../resolver/view'; import { @@ -39,6 +38,8 @@ import { endSelector, } from '../../../common/components/super_date_picker/selectors'; import * as i18n from './translations'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; const OverlayContainer = styled.div` display: flex; @@ -180,11 +181,8 @@ const GraphOverlayComponent: React.FC = ({ timelineId }) => { globalFullScreen, ]); - const existingIndexNamesSelector = useMemo( - () => sourcererSelectors.getAllExistingIndexNamesSelector(), - [] - ); - const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); + const { selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); + if (fullScreen && !isInTimeline) { return ( @@ -206,7 +204,7 @@ const GraphOverlayComponent: React.FC = ({ timelineId }) => { @@ -238,7 +236,7 @@ const GraphOverlayComponent: React.FC = ({ timelineId }) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 1b93f1556a95ca..225158061ad21d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -43,7 +43,6 @@ import { TimelineId, TimelineType, TimelineStatus, - TimelineTabs, KueryFilterQueryKind, } from '../../../../common/types/timeline'; import { @@ -51,7 +50,6 @@ import { mockTemplate as mockSelectedTemplate, } from './__mocks__'; import { resolveTimeline } from '../../containers/api'; -import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; jest.mock('../../../common/store/inputs/actions'); jest.mock('../../../common/components/url_state/normalize_time_range.ts'); @@ -75,6 +73,57 @@ jest.mock('../../../common/utils/default_date_settings', () => { jest.mock('../../containers/api'); +const columns = [ + { + columnHeaderType: 'not-filtered', + id: '@timestamp', + type: 'number', + initialWidth: 190, + }, + { + columnHeaderType: 'not-filtered', + id: 'message', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'event.category', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'event.action', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'host.name', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'source.ip', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'destination.ip', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'user.name', + initialWidth: 180, + }, +]; +const defaultTimeline = { + ...timelineDefaults, + columns, + version: '1', + savedObjectId: 'savedObject-1', + id: 'savedObject-1', +}; + describe('helpers', () => { let mockResults: OpenTimelineResult[]; @@ -237,49 +286,6 @@ describe('helpers', () => { }); describe('#defaultTimelineToTimelineModel', () => { - const columns = [ - { - columnHeaderType: 'not-filtered', - id: '@timestamp', - type: 'number', - initialWidth: 190, - }, - { - columnHeaderType: 'not-filtered', - id: 'message', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'event.category', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'event.action', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'host.name', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'source.ip', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'destination.ip', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'user.name', - initialWidth: 180, - }, - ]; test('if title is null, we should get the default title', () => { const timeline = { savedObjectId: 'savedObject-1', @@ -289,65 +295,7 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel(timeline, false); expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns, - defaultColumns: defaultHeaders, - dataProviders: [], - dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, - filters: [], - highlightedDropAndProviderId: '', - historyIds: [], - id: 'savedObject-1', - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - savedObjectId: 'savedObject-1', - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], - status: TimelineStatus.draft, - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - version: '1', + ...defaultTimeline, }); }); @@ -362,65 +310,8 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.template); expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns, - defaultColumns: defaultHeaders, - dataProviders: [], - dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, - filters: [], - highlightedDropAndProviderId: '', - historyIds: [], - id: 'savedObject-1', - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - savedObjectId: 'savedObject-1', - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], - status: TimelineStatus.draft, - title: '', + ...defaultTimeline, timelineType: TimelineType.template, - templateTimelineId: null, - templateTimelineVersion: null, - version: '1', }); }); @@ -435,65 +326,7 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.default); expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns, - defaultColumns: defaultHeaders, - dataProviders: [], - dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, - filters: [], - highlightedDropAndProviderId: '', - historyIds: [], - id: 'savedObject-1', - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - savedObjectId: 'savedObject-1', - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], - status: TimelineStatus.draft, - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - version: '1', + ...defaultTimeline, }); }); @@ -506,65 +339,7 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel(timeline, false); expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns, - defaultColumns: defaultHeaders, - dataProviders: [], - dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, - filters: [], - highlightedDropAndProviderId: '', - historyIds: [], - indexNames: [], - id: 'savedObject-1', - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - savedObjectId: 'savedObject-1', - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], - status: TimelineStatus.draft, - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - version: '1', + ...defaultTimeline, }); }); @@ -580,65 +355,8 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel(timeline, false); expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - savedObjectId: 'savedObject-1', + ...defaultTimeline, columns: columnsWithoutEventAction, - defaultColumns: defaultHeaders, - version: '1', - dataProviders: [], - dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, - filters: [], - highlightedDropAndProviderId: '', - historyIds: [], - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], - status: TimelineStatus.draft, - id: 'savedObject-1', }); }); @@ -685,29 +403,10 @@ describe('helpers', () => { }; const newTimeline = defaultTimelineToTimelineModel(timeline, false); + expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - savedObjectId: 'savedObject-1', + ...defaultTimeline, columns: columnsWithoutEventAction, - defaultColumns: defaultHeaders, - version: '1', - dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, - dataProviders: [], - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, filters: [ { $state: { @@ -752,42 +451,6 @@ describe('helpers', () => { }, }, ], - highlightedDropAndProviderId: '', - historyIds: [], - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], - status: TimelineStatus.draft, - id: 'savedObject-1', }); }); @@ -802,65 +465,11 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.template); expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns, - defaultColumns: defaultHeaders, - dataProviders: [], + ...defaultTimeline, dateRange: { end: '2020-10-28T11:37:31.655Z', start: '2020-10-27T11:37:31.655Z' }, - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, - filters: [], - highlightedDropAndProviderId: '', - historyIds: [], - id: 'savedObject-1', - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - savedObjectId: 'savedObject-1', - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], status: TimelineStatus.immutable, - title: 'Awesome Timeline', timelineType: TimelineType.template, - templateTimelineId: null, - templateTimelineVersion: null, - version: '1', + title: 'Awesome Timeline', }); }); @@ -875,65 +484,10 @@ describe('helpers', () => { const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.default); expect(newTimeline).toEqual({ - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns, - defaultColumns: defaultHeaders, - dataProviders: [], + ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, - description: '', - documentType: '', - deletedEventIds: [], - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventIdToNoteIds: {}, - eventType: 'all', - excludedRowRendererIds: [], - expandedDetail: {}, - filters: [], - highlightedDropAndProviderId: '', - historyIds: [], - id: 'savedObject-1', - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - queryFields: [], - savedObjectId: 'savedObject-1', - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], status: TimelineStatus.active, title: 'Awesome Timeline', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - version: '1', }); }); }); @@ -1189,6 +743,15 @@ describe('helpers', () => { let clock: sinon.SinonFakeTimers; let timelineDispatch: DispatchUpdateTimeline; + const defaultArgs = { + duplicate: true, + id: TimelineId.active, + from: '2020-03-26T14:35:56.356Z', + to: '2020-03-26T14:41:56.356Z', + notes: [], + timeline: mockTimelineModel, + }; + beforeEach(() => { jest.clearAllMocks(); @@ -1201,14 +764,7 @@ describe('helpers', () => { }); test('it invokes date range picker dispatch', () => { - timelineDispatch({ - duplicate: true, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - notes: [], - timeline: mockTimelineModel, - })(); + timelineDispatch(defaultArgs)(); expect(dispatchSetTimelineRangeDatePicker).toHaveBeenCalledWith({ from: '2020-03-26T14:35:56.356Z', @@ -1217,14 +773,7 @@ describe('helpers', () => { }); test('it invokes add timeline dispatch', () => { - timelineDispatch({ - duplicate: true, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - notes: [], - timeline: mockTimelineModel, - })(); + timelineDispatch(defaultArgs)(); expect(dispatchAddTimeline).toHaveBeenCalledWith({ id: TimelineId.active, @@ -1234,27 +783,13 @@ describe('helpers', () => { }); test('it does not invoke kql filter query dispatches if timeline.kqlQuery.filterQuery is null', () => { - timelineDispatch({ - duplicate: true, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - notes: [], - timeline: mockTimelineModel, - })(); + timelineDispatch(defaultArgs)(); expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled(); }); test('it does not invoke notes dispatch if duplicate is true', () => { - timelineDispatch({ - duplicate: true, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - notes: [], - timeline: mockTimelineModel, - })(); + timelineDispatch(defaultArgs)(); expect(dispatchAddNotes).not.toHaveBeenCalled(); }); @@ -1270,11 +805,7 @@ describe('helpers', () => { }, }; timelineDispatch({ - duplicate: true, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - notes: [], + ...defaultArgs, timeline: mockTimeline, })(); @@ -1292,11 +823,7 @@ describe('helpers', () => { }, }; timelineDispatch({ - duplicate: true, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - notes: [], + ...defaultArgs, timeline: mockTimeline, })(); @@ -1314,10 +841,8 @@ describe('helpers', () => { test('it invokes dispatchAddNotes if duplicate is false', () => { timelineDispatch({ + ...defaultArgs, duplicate: false, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', notes: [ { created: 1585233356356, @@ -1326,7 +851,6 @@ describe('helpers', () => { note: 'I am a note', }, ], - timeline: mockTimelineModel, })(); expect(dispatchAddGlobalTimelineNote).not.toHaveBeenCalled(); @@ -1350,12 +874,7 @@ describe('helpers', () => { test('it invokes dispatch to create a timeline note if duplicate is true and ruleNote exists', () => { timelineDispatch({ - duplicate: true, - id: TimelineId.active, - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - notes: [], - timeline: mockTimelineModel, + ...defaultArgs, ruleNote: '# this would be some markdown', })(); const expectedNote: Note = { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index f325ab34e88d5a..12f0b373d24f91 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -413,8 +413,9 @@ export const dispatchUpdateTimeline = () => { if (!isEmpty(timeline.indexNames)) { dispatch( - sourcererActions.initTimelineIndexPatterns({ + sourcererActions.setSelectedDataView({ id: SourcererScopeName.timeline, + selectedDataViewId: timeline.dataViewId, selectedPatterns: timeline.indexNames, eventType: timeline.eventType, }) diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 25dcdb887d3369..f12731396c0538 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -8,8 +8,7 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; -import { sourcererSelectors } from '../../../common/store'; -import { useShallowEqualSelector, useDeepEqualSelector } from '../../../common/hooks/use_selector'; +import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { SortFieldTimeline, TimelineId } from '../../../../common/types/timeline'; import { TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineSelectors } from '../../../timelines/store/timeline'; @@ -46,6 +45,8 @@ import { useTimelineTypes } from './use_timeline_types'; import { useTimelineStatus } from './use_timeline_status'; import { deleteTimelinesByIds } from '../../containers/api'; import { Direction } from '../../../../common/search_strategy'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; interface OwnProps { /** Displays open timeline in modal */ @@ -108,11 +109,7 @@ export const StatefulOpenTimelineComponent = React.memo( (state) => getTimeline(state, TimelineId.active)?.savedObjectId ?? '' ); - const existingIndexNamesSelector = useMemo( - () => sourcererSelectors.getAllExistingIndexNamesSelector(), - [] - ); - const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); + const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); const updateTimeline = useMemo(() => dispatchUpdateTimeline(dispatch), [dispatch]); const updateIsLoading = useCallback( @@ -204,7 +201,8 @@ export const StatefulOpenTimelineComponent = React.memo( dispatchCreateNewTimeline({ id: TimelineId.active, columns: defaultHeaders, - indexNames: existingIndexNames, + dataViewId, + indexNames: selectedPatterns, show: false, }) ); @@ -213,7 +211,7 @@ export const StatefulOpenTimelineComponent = React.memo( await deleteTimelinesByIds(timelineIds); refetch(); }, - [dispatch, existingIndexNames, refetch, timelineSavedObjectId] + [timelineSavedObjectId, refetch, dispatch, dataViewId, selectedPatterns] ); const onDeleteOneTimeline: OnDeleteOneTimeline = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index aff12b74fbfbf3..1016a430807be1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx @@ -26,8 +26,9 @@ import { NOTE_CONTENT_CLASS_NAME } from '../../timeline/body/helpers'; import * as i18n from './translations'; import { TimelineTabs } from '../../../../../common/types/timeline'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { sourcererSelectors } from '../../../../common/store'; import { SaveTimelineButton } from '../../timeline/header/save_timeline_button'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; export const NotePreviewsContainer = styled.section` padding-top: ${({ theme }) => `${theme.eui.euiSizeS}`}; @@ -45,11 +46,7 @@ const ToggleEventDetailsButtonComponent: React.FC timelineId, }) => { const dispatch = useDispatch(); - const existingIndexNamesSelector = useMemo( - () => sourcererSelectors.getAllExistingIndexNamesSelector(), - [] - ); - const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); + const { selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); const handleClick = useCallback(() => { dispatch( @@ -59,11 +56,11 @@ const ToggleEventDetailsButtonComponent: React.FC timelineId, params: { eventId, - indexName: existingIndexNames.join(','), + indexName: selectedPatterns.join(','), }, }) ); - }, [dispatch, eventId, existingIndexNames, timelineId]); + }, [dispatch, eventId, selectedPatterns, timelineId]); return ( } ); -export const IMPORT_FAILED = i18n.translate( - 'xpack.securitySolution.timelines.components.importTimelineModal.importFailedTitle', - { - defaultMessage: 'Failed to import', - } -); +export const IMPORT_FAILED = (totalTimelines: number) => + i18n.translate( + 'xpack.securitySolution.timelines.components.importTimelineModal.importFailedTitle', + { + values: { totalTimelines }, + defaultMessage: + 'Failed to import {totalTimelines} {totalTimelines, plural, =1 {rule} other {rules}}', + } + ); export const IMPORT_TIMELINE = i18n.translate( 'xpack.securitySolution.timelines.components.importTimelineModal.importTitle', @@ -387,11 +390,11 @@ export const IMPORT_TIMELINE = i18n.translate( } ); -export const IMPORT_FAILED_DETAILED = (id: string, statusCode: number, message: string) => +export const IMPORT_FAILED_DETAILED = (message: string) => i18n.translate( 'xpack.securitySolution.timelines.components.importTimelineModal.importFailedDetailedTitle', { - values: { id, statusCode, message }, - defaultMessage: 'Timeline ID: {id}\n Status Code: {statusCode}\n Message: {message}', + values: { message }, + defaultMessage: '{message}', } ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 2bb9da12e44ea7..06dc3ea3ed9670 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -6,6 +6,7 @@ exports[`Details Panel Component DetailsPanel: rendering it should not render th docValueFields={Array []} handleOnPanelClosed={[MockFunction]} isFlyoutView={false} + runtimeMappings={Object {}} tabType="query" timelineId="test" /> @@ -17,6 +18,7 @@ exports[`Details Panel Component DetailsPanel: rendering it should not render th docValueFields={Array []} handleOnPanelClosed={[MockFunction]} isFlyoutView={false} + runtimeMappings={Object {}} tabType="query" timelineId="test" /> @@ -35,6 +37,7 @@ exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should docValueFields={Array []} handleOnPanelClosed={[MockFunction]} isFlyoutView={false} + runtimeMappings={Object {}} tabType="query" timelineId="test" > @@ -199,6 +202,7 @@ exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should handleOnEventClosed={[Function]} isDraggable={false} isFlyoutView={false} + runtimeMappings={Object {}} tabType="query" timelineId="test" > @@ -742,6 +746,7 @@ Array [ handleOnEventClosed={[Function]} isDraggable={false} isFlyoutView={true} + runtimeMappings={Object {}} tabType="query" timelineId="test" > @@ -1781,6 +1786,7 @@ Array [ handleOnEventClosed={[Function]} isDraggable={false} isFlyoutView={true} + runtimeMappings={Object {}} tabType="query" timelineId="test" > diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 07a0aeabcb9983..947786695ded3a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -17,6 +17,7 @@ import { import React, { useState, useCallback, useMemo } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { BrowserFields, DocValueFields } from '../../../../common/containers/source'; import { ExpandableEvent, ExpandableEventTitle } from './expandable_event'; import { useTimelineEventsDetails } from '../../../containers/details'; @@ -64,6 +65,7 @@ interface EventDetailsPanelProps { handleOnEventClosed: () => void; isDraggable?: boolean; isFlyoutView?: boolean; + runtimeMappings: MappingRuntimeFields; tabType: TimelineTabs; timelineId: string; } @@ -76,6 +78,7 @@ const EventDetailsPanelComponent: React.FC = ({ handleOnEventClosed, isDraggable, isFlyoutView, + runtimeMappings, tabType, timelineId, }) => { @@ -84,6 +87,7 @@ const EventDetailsPanelComponent: React.FC = ({ entityType, indexName: expandedEvent.indexName ?? '', eventId: expandedEvent.eventId ?? '', + runtimeMappings, skip: !expandedEvent.eventId, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap index ad149cbcd63d0b..43efd8cd824e6b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap @@ -105,7 +105,15 @@ exports[`Expandable Host Component ExpandableHostDetails: rendering it should re id="hostsDetailsQuery" indexNames={ Array [ - "IShouldBeUsed", + "-*elastic-cloud-logs-*", + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "traces-apm*", + "winlogbeat-*", ] } isDraggable={false} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx index c2df8959c8c949..5efeacee15a372 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx @@ -9,28 +9,11 @@ import { mount } from 'enzyme'; import React from 'react'; import '../../../../common/mock/match_media'; -import { - mockGlobalState, - TestProviders, - SUB_PLUGINS_REDUCER, - kibanaObservable, - createSecuritySolutionStorageMock, -} from '../../../../common/mock'; -import { createStore, State } from '../../../../common/store'; +import { mockGlobalState, TestProviders } from '../../../../common/mock'; import { ExpandableHostDetails } from './expandable_host'; +import { EXCLUDE_ELASTIC_CLOUD_INDEX } from '../../../../common/containers/sourcerer'; describe('Expandable Host Component', () => { - const state: State = { - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - configIndexPatterns: ['IShouldBeUsed'], - }, - }; - - const { storage } = createSecuritySolutionStorageMock(); - const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - const mockProps = { contextID: 'text-context', hostName: 'testHostName', @@ -39,7 +22,7 @@ describe('Expandable Host Component', () => { describe('ExpandableHostDetails: rendering', () => { test('it should render the HostOverview of the ExpandableHostDetails', () => { const wrapper = mount( - + ); @@ -49,12 +32,15 @@ describe('Expandable Host Component', () => { test('it should render the HostOverview of the ExpandableHostDetails with the correct indices', () => { const wrapper = mount( - + ); - expect(wrapper.find('HostOverview').prop('indexNames')).toStrictEqual(['IShouldBeUsed']); + expect(wrapper.find('HostOverview').prop('indexNames')).toStrictEqual([ + EXCLUDE_ELASTIC_CLOUD_INDEX, + ...mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns, + ]); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index 3fe8c2dcc8e084..dae1f7add11e3e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiTitle } from '@elastic/eui'; -import { sourcererSelectors } from '../../../../common/store/sourcerer'; import { HostDetailsLink } from '../../../../common/components/links'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { HostOverview } from '../../../../overview/components/host_overview'; import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; import { HostItem } from '../../../../../common/search_strategy'; @@ -20,7 +19,6 @@ import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/a import { hostToCriteria } from '../../../../common/components/ml/criteria/host_to_criteria'; import { scoreIntervalToDateTime } from '../../../../common/components/ml/score/score_interval_to_datetime'; import { useHostDetails, ID } from '../../../../hosts/containers/hosts/details'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; interface ExpandableHostProps { hostName: string; @@ -57,9 +55,8 @@ export const ExpandableHostDetails = ({ isDraggable = false, }: ExpandableHostProps & { contextID: string; isDraggable?: boolean }) => { const { to, from, isInitializing } = useGlobalTime(); - const { docValueFields } = useSourcererScope(); /* - Normally `selectedPatterns` from useSourcerScope would be where we obtain the indices, + Normally `selectedPatterns` from useSourcererDataView would be where we obtain the indices, but those indices are only loaded when viewing the pages where the sourcerer is initialized (i.e. Hosts and Overview) When a user goes directly to the detections page, the patterns have not been loaded yet as that information isn't used for the detections page. With this details component being accessible @@ -67,15 +64,12 @@ export const ExpandableHostDetails = ({ Otherwise, an empty array is defaulted for the `indexNames` in the query which leads to inconsistencies in the data returned (i.e. extraneous endpoint data is retrieved from the backend leading to endpoint data not being returned) */ - const allExistingIndexNamesSelector = useMemo( - () => sourcererSelectors.getAllExistingIndexNamesSelector(), - [] - ); - const allPatterns = useDeepEqualSelector(allExistingIndexNamesSelector); + const { docValueFields, selectedPatterns } = useSourcererDataView(); + const [loading, { hostDetails: hostOverview }] = useHostDetails({ endDate: to, hostName, - indexNames: allPatterns, + indexNames: selectedPatterns, startDate: from, }); return ( @@ -95,7 +89,7 @@ export const ExpandableHostDetails = ({ anomaliesData={anomaliesData} isDraggable={isDraggable} isLoadingAnomaliesData={isLoadingAnomaliesData} - indexNames={allPatterns} + indexNames={selectedPatterns} loading={loading} startDate={from} endDate={to} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx index 7e530da542bf84..5870ba42405fe4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx @@ -97,6 +97,7 @@ describe('Details Panel Component', () => { docValueFields: [], handleOnPanelClosed: jest.fn(), isFlyoutView: false, + runtimeMappings: {}, tabType: TimelineTabs.query, timelineId: 'test', }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx index a9b1126edcaeb1..1723f3f7f91afb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { EuiFlyout, EuiFlyoutProps } from '@elastic/eui'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { timelineActions, timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; @@ -25,6 +26,7 @@ interface DetailsPanelProps { entityType?: EntityType; handleOnPanelClosed?: () => void; isFlyoutView?: boolean; + runtimeMappings: MappingRuntimeFields; tabType?: TimelineTabs; timelineId: string; } @@ -41,6 +43,7 @@ export const DetailsPanel = React.memo( entityType, handleOnPanelClosed, isFlyoutView, + runtimeMappings, tabType, timelineId, }: DetailsPanelProps) => { @@ -82,6 +85,7 @@ export const DetailsPanel = React.memo( handleOnEventClosed={closePanel} isDraggable={isDraggable} isFlyoutView={isFlyoutView} + runtimeMappings={runtimeMappings} tabType={activeTab} timelineId={timelineId} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx index 184379cd366b1a..5b13f3f2a3b1bc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx @@ -24,7 +24,7 @@ import { inputsSelectors } from '../../../../common/store'; import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; import { OverviewEmpty } from '../../../../overview/components/overview_empty'; import { esQuery } from '../../../../../../../../src/plugins/data/public'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useNetworkDetails } from '../../../../network/containers/details'; import { networkModel } from '../../../../network/store'; import { useAnomaliesTableData } from '../../../../common/components/ml/anomaly/use_anomalies_table_data'; @@ -98,7 +98,7 @@ export const ExpandableNetworkDetails = ({ services: { uiSettings }, } = useKibana(); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const [filterQuery, kqlError] = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(uiSettings), indexPattern, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 25d5104a98d955..11f3d6d795d751 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -126,6 +126,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts index 85e884703c5921..c59a80ae078b22 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts @@ -64,6 +64,7 @@ describe('helpers', () => { id: '@timestamp', indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: '@timestamp', + readFromDocValues: true, searchable: true, type: 'date', initialWidth: 190, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx index 403756a763808b..e7c03612828abf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/stateful_cell.tsx @@ -51,6 +51,7 @@ const StatefulCellComponent = ({ isExpandable: true, isExpanded: false, isDetails: false, + isTimeline: true, linkValues, rowIndex: ariaRowindex - 1, setCellProps, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap index eeb8786b2cfa3d..990f3a0af87c02 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap @@ -127,6 +127,7 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index 64ca766e4dee6a..546b9a31843ac3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -125,6 +125,7 @@ exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap index 94cbe43e93d2d7..e20f63a7b9e79c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap @@ -127,6 +127,7 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx index 5b9fee621b4171..6aec7ae19734cf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx @@ -32,6 +32,7 @@ export const DefaultCellRenderer: React.FC = ({ header, isDetails, isDraggable, + isTimeline, linkValues, rowRenderers, setCellProps, @@ -49,7 +50,7 @@ export const DefaultCellRenderer: React.FC = ({ <> {getColumnRenderer(header.id, columnRenderers, data).renderColumn({ - asPlainText: !!getLink(header.id, header.type), // we want to render value with links as plain text but keep other formatters like badge. + asPlainText: !!getLink(header.id, header.type) && !isTimeline, // we want to render value with links as plain text but keep other formatters like badge. browserFields, columnName: header.id, ecsData, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx index 2848a850a52271..5d66e399ece6f5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/index.tsx @@ -5,4 +5,4 @@ * 2.0. */ -export { CellValueElementProps } from '../../../../../common/types/timeline'; +export type { CellValueElementProps } from '../../../../../common/types/timeline'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx index ef04c1177dcd6f..ddcc2df231a00f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx @@ -12,14 +12,6 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { DataProviders } from '.'; -jest.mock('../../../../common/hooks/use_selector', () => { - const actual = jest.requireActual('../../../../common/hooks/use_selector'); - return { - ...actual, - useDeepEqualSelector: jest.fn().mockReturnValue([]), - }; -}); - describe('DataProviders', () => { const mount = useMountAppended(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx index f642ec35d43068..0a3a40d8f6f062 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx @@ -12,7 +12,7 @@ import uuid from 'uuid'; import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { DroppableWrapper } from '../../../../common/components/drag_and_drop/droppable_wrapper'; import { droppableTimelineProvidersPrefix } from '../../../../common/components/drag_and_drop/helpers'; @@ -85,7 +85,7 @@ const getDroppableId = (id: string): string => * the data pro section. */ export const DataProviders = React.memo(({ timelineId }) => { - const { browserFields } = useSourcererScope(SourcererScopeName.timeline); + const { browserFields } = useSourcererDataView(SourcererScopeName.timeline); const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []); const { isLoading } = useDeepEqualSelector((state) => getManageTimeline(state, timelineId)); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); 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 ad141829858aea..553a3d0e82d299 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 @@ -20,7 +20,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../containers/index'; import { useTimelineEventsDetails } from '../../../containers/details/index'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components'; @@ -88,7 +88,7 @@ describe('Timeline', () => { ]); (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, {}]); - (useSourcererScope as jest.Mock).mockReturnValue(mockSourcererScope); + (useSourcererDataView as jest.Mock).mockReturnValue(mockSourcererScope); props = { columns: defaultHeaders, @@ -176,7 +176,7 @@ describe('Timeline', () => { }); test('it does render the timeline table when the source is loading with no events', () => { - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ browserFields: {}, docValueFields: [], loading: true, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx index 737c6b99cea756..6cb0e6f2e79821 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx @@ -47,7 +47,7 @@ import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useEqlEventsCountPortal } from '../../../../common/hooks/use_timeline_events_count'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { TimelineDatePickerLock } from '../date_picker_lock'; @@ -178,8 +178,9 @@ export const EqlTabContentComponent: React.FC = ({ browserFields, docValueFields, loading: loadingSourcerer, + runtimeMappings, selectedPatterns, - } = useSourcererScope(SourcererScopeName.timeline); + } = useSourcererDataView(SourcererScopeName.timeline); const isBlankTimeline: boolean = isEmpty(eqlQuery); @@ -216,6 +217,7 @@ export const EqlTabContentComponent: React.FC = ({ language: 'eql', limit: itemsPerPage, filterQuery: eqlQuery ?? '', + runtimeMappings, startDate: start, skip: !canQueryTimeline(), timerangeKind, @@ -346,6 +348,7 @@ export const EqlTabContentComponent: React.FC = ({ { return mapStateToProps; }; const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({ - updateEventTypeAndIndexesName: (newEventType: TimelineEventsType, newIndexNames: string[]) => { + updateEventTypeAndIndexesName: ( + newEventType: TimelineEventsType, + newIndexNames: string[], + newDataViewId: string + ) => { dispatch(timelineActions.updateEventType({ id: timelineId, eventType: newEventType })); - dispatch(timelineActions.updateIndexNames({ id: timelineId, indexNames: newIndexNames })); dispatch( - sourcererActions.setSelectedIndexPatterns({ + timelineActions.updateDataView({ + dataViewId: newDataViewId, + id: timelineId, + indexNames: newIndexNames, + }) + ); + dispatch( + sourcererActions.setSelectedDataView({ id: SourcererScopeName.timeline, + selectedDataViewId: newDataViewId, selectedPatterns: newIndexNames, }) ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts index ca7c3596d13bb8..c90d04e1e640a4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts @@ -7,7 +7,7 @@ import { ColumnId } from './body/column_id'; import { DataProvider, QueryOperator } from './data_providers/data_provider'; -export { +export type { OnColumnSorted, OnColumnsSorted, OnColumnRemoved, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx index 2c85f1547dbeb0..547f005ab61ab8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx @@ -8,7 +8,7 @@ import { isEmpty, get } from 'lodash/fp'; import memoizeOne from 'memoize-one'; -import { EsQueryConfig, Filter, Query } from '@kbn/es-query'; +import { DataViewBase, EsQueryConfig, Filter, Query } from '@kbn/es-query'; import { handleSkipFocus, elementOrChildrenHasFocus, @@ -25,7 +25,6 @@ import { EXISTS_OPERATOR, } from './data_providers/data_provider'; import { BrowserFields } from '../../../common/containers/source'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { EVENTS_TABLE_CLASS_NAME } from './styles'; @@ -151,7 +150,7 @@ export const combineQueries = ({ }: { config: EsQueryConfig; dataProviders: DataProvider[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; browserFields: BrowserFields; filters: Filter[]; kqlQuery: Query; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx index c91673e5f931c6..bb1281c0eff2e2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx @@ -59,7 +59,7 @@ jest.mock('../../../common/containers/sourcerer', () => { return { ...originalModule, - useSourcererScope: jest.fn().mockReturnValue({ + useSourcererDataView: jest.fn().mockReturnValue({ browserFields: mockBrowserFields, docValueFields: mockDocValueFields, loading: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index ca883529b5ce6f..48f9274fa563f5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -16,7 +16,7 @@ import { timelineActions, timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { defaultHeaders } from './body/column_headers/default_headers'; import { CellValueElementProps } from './cell_rendering'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { FlyoutHeader, FlyoutHeaderPanel } from '../flyout/header'; import { TimelineType, TimelineId, RowRenderer } from '../../../../common/types/timeline'; @@ -62,7 +62,8 @@ const StatefulTimelineComponent: React.FC = ({ const dispatch = useDispatch(); const containerElement = useRef(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { selectedPatterns } = useSourcererScope(SourcererScopeName.timeline); + const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); + const { graphEventId, savedObjectId, timelineType, description } = useDeepEqualSelector((state) => pick( ['graphEventId', 'savedObjectId', 'timelineType', 'description'], @@ -77,6 +78,7 @@ const StatefulTimelineComponent: React.FC = ({ timelineActions.createTimeline({ id: timelineId, columns: defaultHeaders, + dataViewId, indexNames: selectedPatterns, expandedDetail: activeTimeline.getExpandedDetail(), show: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx index 726071b2caf742..e4d97abc0433ad 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx @@ -21,7 +21,7 @@ import React, { Fragment, useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineActions } from '../../../store/timeline'; import { @@ -145,8 +145,9 @@ const NotesTabContentComponent: React.FC = ({ timelineId } noteIds, status: timelineStatus, } = useDeepEqualSelector((state) => getTimelineNotes(state, timelineId)); - - const { browserFields, docValueFields } = useSourcererScope(SourcererScopeName.timeline); + const { browserFields, docValueFields, runtimeMappings } = useSourcererDataView( + SourcererScopeName.timeline + ); const getNotesAsCommentsList = useMemo( () => appSelectors.selectNotesAsCommentsListSelector(), @@ -188,11 +189,19 @@ const NotesTabContentComponent: React.FC = ({ timelineId } browserFields={browserFields} docValueFields={docValueFields} handleOnPanelClosed={handleOnPanelClosed} + runtimeMappings={runtimeMappings} tabType={TimelineTabs.notes} timelineId={timelineId} /> ) : null, - [browserFields, docValueFields, expandedDetail, handleOnPanelClosed, timelineId] + [ + browserFields, + docValueFields, + expandedDetail, + handleOnPanelClosed, + runtimeMappings, + timelineId, + ] ); const SidebarContent = useMemo( 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 cb41d0c69bc97c..8707bb33da08cd 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 @@ -19,7 +19,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../containers/index'; import { useTimelineEventsDetails } from '../../../containers/details/index'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { PinnedTabContentComponent, Props as PinnedTabContentComponentProps } from '.'; import { Direction } from '../../../../../common/search_strategy'; @@ -93,7 +93,7 @@ describe('PinnedTabContent', () => { ]); (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, {}]); - (useSourcererScope as jest.Mock).mockReturnValue(mockSourcererScope); + (useSourcererDataView as jest.Mock).mockReturnValue(mockSourcererScope); props = { columns: defaultHeaders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index 2051f95b75ae87..fbb1269b05b27b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -22,10 +22,9 @@ import { StatefulBody } from '../body'; import { Footer, footerHeight } from '../footer'; import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config'; import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context'; -import { sourcererSelectors } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen'; import { TimelineModel } from '../../../store/timeline/model'; import { State } from '../../../../common/store'; @@ -37,7 +36,6 @@ import { ToggleDetailPanel, } from '../../../../../common/types/timeline'; import { DetailsPanel } from '../../side_panel'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { ExitFullScreen } from '../../../../common/components/exit_full_screen'; import { defaultControlColumn } from '../body/control_columns'; @@ -119,15 +117,11 @@ export const PinnedTabContentComponent: React.FC = ({ browserFields, docValueFields, loading: loadingSourcerer, - } = useSourcererScope(SourcererScopeName.timeline); + runtimeMappings, + selectedPatterns, + } = useSourcererDataView(SourcererScopeName.timeline); const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen(); - const existingIndexNamesSelector = useMemo( - () => sourcererSelectors.getAllExistingIndexNamesSelector(), - [] - ); - const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); - const filterQuery = useMemo(() => { if (isEmpty(pinnedEventIds)) { return ''; @@ -188,10 +182,11 @@ export const PinnedTabContentComponent: React.FC = ({ docValueFields, endDate: '', id: `pinned-${timelineId}`, - indexNames: existingIndexNames, + indexNames: selectedPatterns, fields: timelineQueryFields, limit: itemsPerPage, filterQuery, + runtimeMappings, skip: filterQuery === '', startDate: '', sort: timelineQuerySortField, @@ -269,6 +264,7 @@ export const PinnedTabContentComponent: React.FC = ({ browserFields={browserFields} docValueFields={docValueFields} handleOnPanelClosed={handleOnPanelClosed} + runtimeMappings={runtimeMappings} tabType={TimelineTabs.pinned} timelineId={timelineId} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx index 1fea015de772ff..a4dd57768372ee 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx @@ -22,13 +22,6 @@ jest.mock('react-redux', () => { return { ...actual, useDispatch: () => mockDispatch, - useSelector: () => ({ - kind: 'relative', - fromStr: 'now-24h', - toStr: 'now', - from: '2020-07-07T08:20:18.966Z', - to: '2020-07-08T08:20:18.966Z', - }), }; }); @@ -135,7 +128,7 @@ describe('useCreateTimelineButton', () => { wrapper.find('[data-test-subj="timeline-new"]').first().simulate('click'); expect(mockDispatch.mock.calls[0][0].type).toEqual( - 'x-pack/security_solution/local/sourcerer/SET_SELECTED_INDEX_PATTERNS' + 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW' ); expect(mockDispatch.mock.calls[1][0].type).toEqual( 'x-pack/security_solution/local/timeline/CREATE_TIMELINE' diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx index 32d05202f1d12f..6b10ff20008dc8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; @@ -19,9 +19,10 @@ import { } from '../../../../../common/types/timeline'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { inputsActions, inputsSelectors } from '../../../../common/store/inputs'; -import { sourcererActions, sourcererSelectors } from '../../../../common/store/sourcerer'; +import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { appActions } from '../../../../common/store/app'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; interface Props { timelineId?: string; @@ -31,11 +32,8 @@ interface Props { export const useCreateTimeline = ({ timelineId, timelineType, closeGearMenu }: Props) => { const dispatch = useDispatch(); - const existingIndexNamesSelector = useMemo( - () => sourcererSelectors.getAllExistingIndexNamesSelector(), - [] - ); - const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); + const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); + const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const globalTimeRange = useDeepEqualSelector(inputsSelectors.globalTimeRangeSelector); const createTimeline = useCallback( @@ -44,18 +42,20 @@ export const useCreateTimeline = ({ timelineId, timelineType, closeGearMenu }: P setTimelineFullScreen(false); } dispatch( - sourcererActions.setSelectedIndexPatterns({ + sourcererActions.setSelectedDataView({ id: SourcererScopeName.timeline, - selectedPatterns: existingIndexNames, + selectedDataViewId: dataViewId, + selectedPatterns, }) ); dispatch( timelineActions.createTimeline({ - id, columns: defaultHeaders, + dataViewId, + id, + indexNames: selectedPatterns, show, timelineType, - indexNames: existingIndexNames, }) ); dispatch(inputsActions.addGlobalLinkTo({ linkToId: 'timeline' })); @@ -78,9 +78,10 @@ export const useCreateTimeline = ({ timelineId, timelineType, closeGearMenu }: P } }, [ - existingIndexNames, dispatch, globalTimeRange, + dataViewId, + selectedPatterns, setTimelineFullScreen, timelineFullScreen, timelineType, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index b65e4321262830..f045f735ae168d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -11,7 +11,7 @@ import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { FieldsEqlOptions } from '../../../../../../common/search_strategy'; -import { useSourcererScope } from '../../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; import { EqlQueryBar } from '../../../../../detections/components/rules/eql_query_bar'; @@ -71,7 +71,7 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) loading: indexPatternsLoading, indexPattern, selectedPatterns, - } = useSourcererScope(SourcererScopeName.timeline); + } = useSourcererDataView(SourcererScopeName.timeline); const initialState = { ...defaultValues, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx index daafec3005eb8a..450a43b43ef5f4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx @@ -11,7 +11,7 @@ import { useDispatch } from 'react-redux'; import { Subscription } from 'rxjs'; import deepEqual from 'fast-deep-equal'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { Query, @@ -81,8 +81,7 @@ export const QueryBarTimeline = memo( const [dateRangeTo, setDateRangTo] = useState( toStr != null ? toStr : new Date(to).toISOString() ); - const { browserFields, indexPattern } = useSourcererScope(SourcererScopeName.timeline); - + const { browserFields, indexPattern } = useSourcererDataView(SourcererScopeName.timeline); const [savedQuery, setSavedQuery] = useState(undefined); const [filterQueryConverted, setFilterQueryConverted] = useState({ query: filterQuery != null ? filterQuery.expression : '', 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 c9bb14dfc0c672..23f0fa0b9f2893 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 @@ -22,7 +22,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineStatus, TimelineTabs } from '../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../containers/index'; import { useTimelineEventsDetails } from '../../../containers/details/index'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { Direction } from '../../../../../common/search_strategy'; import * as helpers from '../helpers'; @@ -102,7 +102,7 @@ describe('Timeline', () => { ]); (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, {}]); - (useSourcererScope as jest.Mock).mockReturnValue(mockSourcererScope); + (useSourcererDataView as jest.Mock).mockReturnValue(mockSourcererScope); props = { columns: defaultHeaders, @@ -187,7 +187,7 @@ describe('Timeline', () => { }); test('it does render the timeline table when the source is loading with no events', () => { - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ browserFields: {}, docValueFields: [], loading: true, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 2082e7f5b69bb1..5f6f2796d4ba97 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -51,7 +51,7 @@ import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useTimelineEventsCountPortal } from '../../../../common/hooks/use_timeline_events_count'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { TimelineDatePickerLock } from '../date_picker_lock'; @@ -190,9 +190,9 @@ export const QueryTabContentComponent: React.FC = ({ docValueFields, loading: loadingSourcerer, indexPattern, + runtimeMappings, selectedPatterns, - } = useSourcererScope(SourcererScopeName.timeline); - + } = useSourcererDataView(SourcererScopeName.timeline); const { uiSettings } = useKibana().services; const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []); @@ -282,6 +282,7 @@ export const QueryTabContentComponent: React.FC = ({ language: kqlQuery.language, limit: itemsPerPage, filterQuery: combinedQueries?.filterQuery, + runtimeMappings, startDate: start, skip: !canQueryTimeline, sort: timelineQuerySortField, @@ -429,6 +430,7 @@ export const QueryTabContentComponent: React.FC = ({ browserFields={browserFields} docValueFields={docValueFields} handleOnPanelClosed={handleOnPanelClosed} + runtimeMappings={runtimeMappings} tabType={TimelineTabs.query} timelineId={timelineId} /> @@ -502,13 +504,24 @@ const makeMapStateToProps = () => { }; const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({ - updateEventTypeAndIndexesName: (newEventType: TimelineEventsType, newIndexNames: string[]) => { + updateEventTypeAndIndexesName: ( + newEventType: TimelineEventsType, + newIndexNames: string[], + newDataViewId: string + ) => { dispatch(timelineActions.updateEventType({ id: timelineId, eventType: newEventType })); - dispatch(timelineActions.updateIndexNames({ id: timelineId, indexNames: newIndexNames })); dispatch( - sourcererActions.setSelectedIndexPatterns({ + timelineActions.updateDataView({ + dataViewId: newDataViewId, + id: timelineId, + indexNames: newIndexNames, + }) + ); + dispatch( + sourcererActions.setSelectedDataView({ id: SourcererScopeName.timeline, selectedPatterns: newIndexNames, + selectedDataViewId: newDataViewId, }) ); }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx index e519cfcd204a7f..47ea0f781f7c39 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx @@ -14,6 +14,7 @@ import { createSecuritySolutionStorageMock, kibanaObservable, mockGlobalState, + mockSourcererState, SUB_PLUGINS_REDUCER, TestProviders, } from '../../../../common/mock'; @@ -29,41 +30,59 @@ jest.mock('@elastic/eui', () => { }; }); -describe('pick_events', () => { +describe('Pick Events/Timeline Sourcerer', () => { const defaultProps = { eventType: 'all' as TimelineEventsType, onChangeEventTypeAndIndexesName: jest.fn(), }; const initialPatterns = [ - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline].selectedPatterns, - mockGlobalState.sourcerer.signalIndexName, + ...mockSourcererState.defaultDataView.patternList.filter( + (p) => p !== mockSourcererState.signalIndexName + ), + mockSourcererState.signalIndexName, ]; const { storage } = createSecuritySolutionStorageMock(); + + // const state = { + // ...mockGlobalState, + // sourcerer: { + // ...mockGlobalState.sourcerer, + // kibanaIndexPatterns: [ + // { id: '1234', title: 'auditbeat-*' }, + // { id: '9100', title: 'filebeat-*' }, + // { id: '9100', title: 'auditbeat-*,filebeat-*' }, + // { id: '5678', title: 'auditbeat-*,.siem-signals-default' }, + // ], + // configIndexPatterns: + // mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline].selectedPatterns, + // signalIndexName: mockGlobalState.sourcerer.signalIndexName, + // sourcererScopes: { + // ...mockGlobalState.sourcerer.sourcererScopes, + // [SourcererScopeName.timeline]: { + // ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], + // loading: false, + // selectedPatterns: ['filebeat-*'], + // }, + // }, + // }, + // }; + // const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); const state = { ...mockGlobalState, sourcerer: { ...mockGlobalState.sourcerer, - kibanaIndexPatterns: [ - { id: '1234', title: 'auditbeat-*' }, - { id: '9100', title: 'filebeat-*' }, - { id: '9100', title: 'auditbeat-*,filebeat-*' }, - { id: '5678', title: 'auditbeat-*,.siem-signals-default' }, - ], - configIndexPatterns: - mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline].selectedPatterns, - signalIndexName: mockGlobalState.sourcerer.signalIndexName, sourcererScopes: { ...mockGlobalState.sourcerer.sourcererScopes, [SourcererScopeName.timeline]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], loading: false, + selectedDataViewId: mockGlobalState.sourcerer.defaultDataView.id, selectedPatterns: ['filebeat-*'], }, }, }, }; const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - const mockTooltip = ({ tooltipContent, children, @@ -94,6 +113,57 @@ describe('pick_events', () => { expect(wrapper.getByTestId('timeline-sourcerer').textContent).toEqual( initialPatterns.sort().join('') ); + fireEvent.click(wrapper.getByTestId(`sourcerer-accordion`)); + fireEvent.click(wrapper.getByTestId('comboBoxToggleListButton')); + const optionNodes = wrapper.getAllByTestId('sourcerer-option'); + expect(optionNodes.length).toBe(1); + }); + it('Removes duplicate options from options list', () => { + const store2 = createStore( + { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + defaultDataView: { + ...mockGlobalState.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', + patternList: ['filebeat-*', 'auditbeat-*'], + }, + kibanaDataViews: [ + { + ...mockGlobalState.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', + patternList: ['filebeat-*', 'auditbeat-*'], + }, + ], + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.timeline]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], + loading: false, + selectedDataViewId: '1234', + selectedPatterns: ['filebeat-*'], + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const wrapper = render( + + + + ); + fireEvent.click(wrapper.getByTestId(`sourcerer-timeline-trigger`)); + fireEvent.click(wrapper.getByTestId(`sourcerer-accordion`)); + fireEvent.click(wrapper.getByTestId(`comboBoxToggleListButton`)); + expect( + wrapper.getByTestId('comboBoxOptionsList timeline-sourcerer-optionsList').textContent + ).toEqual('auditbeat-*'); }); it('renders tooltip', () => { @@ -129,6 +199,7 @@ describe('pick_events', () => { ); fireEvent.click(wrapper.getByTestId('sourcerer-timeline-trigger')); fireEvent.click(wrapper.getByTestId('comboBoxToggleListButton')); + fireEvent.click(wrapper.getByTestId('sourcerer-accordion')); const optionNodes = wrapper.getAllByTestId('sourcerer-option'); expect(optionNodes.length).toBe(9); }); @@ -145,8 +216,5 @@ describe('pick_events', () => { expect(wrapper.getByTestId('timeline-sourcerer').textContent).toEqual( initialPatterns.sort().join('') ); - fireEvent.click(wrapper.getByTestId('comboBoxToggleListButton')); - const optionNodes = wrapper.getAllByTestId('sourcerer-option'); - expect(optionNodes.length).toBe(2); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx index 6d86d7c0f13301..791993d67135dd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -21,17 +21,19 @@ import { EuiSpacer, EuiText, EuiToolTip, + EuiSuperSelect, } from '@elastic/eui'; import deepEqual from 'fast-deep-equal'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { useSelector } from 'react-redux'; import styled from 'styled-components'; -import { State } from '../../../../common/store'; +import { sourcererSelectors } from '../../../../common/store'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { TimelineEventsType } from '../../../../../common/types/timeline'; -import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors'; +import { TimelineEventsType } from '../../../../../common'; import * as i18n from './translations'; +import { getScopePatternListSelection } from '../../../../common/store/sourcerer/helpers'; +import { SIEM_DATA_VIEW_LABEL } from '../../../../common/components/sourcerer/translations'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; const PopoverContent = styled.div` width: 600px; @@ -89,7 +91,7 @@ const PickEventContainer = styled.div` } `; -const getEventTypeOptions = (isCustomDisabled: boolean = true) => [ +const getEventTypeOptions = (isCustomDisabled: boolean = true, isDefaultPattern: boolean) => [ { id: 'all', label: ( @@ -101,10 +103,12 @@ const getEventTypeOptions = (isCustomDisabled: boolean = true) => [ { id: 'raw', label: {i18n.RAW_EVENT}, + disabled: !isDefaultPattern, }, { id: 'alert', label: {i18n.DETECTION_ALERTS_EVENT}, + disabled: !isDefaultPattern, }, { id: 'custom', @@ -115,9 +119,14 @@ const getEventTypeOptions = (isCustomDisabled: boolean = true) => [ interface PickEventTypeProps { eventType: TimelineEventsType; - onChangeEventTypeAndIndexesName: (value: TimelineEventsType, indexNames: string[]) => void; + onChangeEventTypeAndIndexesName: ( + value: TimelineEventsType, + indexNames: string[], + dataViewId: string + ) => void; } +// AKA TimelineSourcerer const PickEventTypeComponents: React.FC = ({ eventType = 'all', onChangeEventTypeAndIndexesName, @@ -125,81 +134,63 @@ const PickEventTypeComponents: React.FC = ({ const [isPopoverOpen, setPopover] = useState(false); const [showAdvanceSettings, setAdvanceSettings] = useState(eventType === 'custom'); const [filterEventType, setFilterEventType] = useState(eventType); - const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); - const { configIndexPatterns, kibanaIndexPatterns, signalIndexName, sourcererScope } = useSelector< - State, - SourcererScopeSelector - >((state) => sourcererScopeSelector(state, SourcererScopeName.timeline), deepEqual); - const [selectedOptions, setSelectedOptions] = useState>>( - sourcererScope.selectedPatterns.map((indexSelected) => ({ - label: indexSelected, - value: indexSelected, - })) + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); + const { + defaultDataView, + kibanaDataViews, + signalIndexName, + sourcererScope: { loading, selectedPatterns, selectedDataViewId }, + }: sourcererSelectors.SourcererScopeSelector = useDeepEqualSelector((state) => + sourcererScopeSelector(state, SourcererScopeName.timeline) ); - const indexesPatternOptions = useMemo( - () => - [ - ...configIndexPatterns, - ...kibanaIndexPatterns.map((kip) => kip.title), - signalIndexName, - ].reduce>>((acc, index) => { - if (index != null && !acc.some((o) => o.label === index)) { - return [...acc, { label: index, value: index }]; + const [dataViewId, setDataViewId] = useState(selectedDataViewId ?? ''); + const { patternList, selectablePatterns } = useMemo(() => { + const theDataView = kibanaDataViews.find((dataView) => dataView.id === dataViewId); + return theDataView != null + ? { + patternList: theDataView.title + .split(',') + // remove duplicates patterns from selector + .filter((pattern, i, self) => self.indexOf(pattern) === i), + selectablePatterns: theDataView.patternList, } - return acc; - }, []), - [configIndexPatterns, kibanaIndexPatterns, signalIndexName] - ); - - const renderOption = useCallback( - ({ value }) => { - if (kibanaIndexPatterns.some((kip) => kip.title === value)) { - return ( - - {value} - - ); - } - return {value}; - }, - [kibanaIndexPatterns] + : { patternList: [], selectablePatterns: [] }; + }, [kibanaDataViews, dataViewId]); + const [selectedOptions, setSelectedOptions] = useState>>( + selectedPatterns.map((indexName) => ({ + label: indexName, + value: indexName, + })) ); - - const onChangeCombo = useCallback( - (newSelectedOptions: Array>) => { - const localSelectedPatterns = newSelectedOptions.map((nso) => nso.label); - if ( - localSelectedPatterns.sort().join() === - [...configIndexPatterns, signalIndexName].sort().join() - ) { - setFilterEventType('all'); - } else if (localSelectedPatterns.sort().join() === configIndexPatterns.sort().join()) { - setFilterEventType('raw'); - } else if (localSelectedPatterns.sort().join() === signalIndexName) { - setFilterEventType('alert'); - } else { - setFilterEventType('custom'); - } - - setSelectedOptions(newSelectedOptions); - }, - [configIndexPatterns, signalIndexName] + const isSavingDisabled = useMemo(() => selectedOptions.length === 0, [selectedOptions]); + const selectableOptions = useMemo( + () => + patternList.map((indexName) => ({ + label: indexName, + value: indexName, + 'data-test-subj': 'sourcerer-option', + disabled: !selectablePatterns.includes(indexName), + })), + [selectablePatterns, patternList] ); const onChangeFilter = useCallback( (filter) => { setFilterEventType(filter); - if (filter === 'all') { + if (filter === 'all' || filter === 'kibana') { setSelectedOptions( - [...configIndexPatterns.sort(), signalIndexName ?? ''].map((indexSelected) => ({ + selectablePatterns.map((indexSelected) => ({ label: indexSelected, value: indexSelected, })) ); } else if (filter === 'raw') { setSelectedOptions( - configIndexPatterns.sort().map((indexSelected) => ({ + (signalIndexName == null + ? selectablePatterns + : selectablePatterns.filter((index) => index !== signalIndexName) + ).map((indexSelected) => ({ label: indexSelected, value: indexSelected, })) @@ -211,16 +202,56 @@ const PickEventTypeComponents: React.FC = ({ value: signalIndexName ?? '', }, ]); - } else if (filter === 'kibana') { - setSelectedOptions( - kibanaIndexPatterns.map((kip) => ({ - label: kip.title, - value: kip.title, - })) - ); } }, - [configIndexPatterns, kibanaIndexPatterns, signalIndexName] + [selectablePatterns, signalIndexName] + ); + + const onChangeCombo = useCallback( + (newSelectedOptions: Array>) => { + const localSelectedPatterns = newSelectedOptions + .map((nso) => nso.label) + .sort() + .join(); + if (localSelectedPatterns === selectablePatterns.sort().join()) { + setFilterEventType('all'); + } else if ( + dataViewId === defaultDataView.id && + localSelectedPatterns === + selectablePatterns + .filter((index) => index !== signalIndexName) + .sort() + .join() + ) { + setFilterEventType('raw'); + } else if (dataViewId === defaultDataView.id && localSelectedPatterns === signalIndexName) { + setFilterEventType('alert'); + } else { + setFilterEventType('custom'); + } + + setSelectedOptions(newSelectedOptions); + }, + [defaultDataView.id, dataViewId, selectablePatterns, signalIndexName] + ); + + const onChangeSuper = useCallback( + (newSelectedOption) => { + setFilterEventType('all'); + setDataViewId(newSelectedOption); + setSelectedOptions( + getScopePatternListSelection( + kibanaDataViews.find((dataView) => dataView.id === newSelectedOption), + SourcererScopeName.timeline, + signalIndexName, + newSelectedOption === defaultDataView.id + ).map((indexSelected: string) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + }, + [defaultDataView.id, kibanaDataViews, signalIndexName] ); const togglePopover = useCallback( @@ -233,66 +264,69 @@ const PickEventTypeComponents: React.FC = ({ const handleSaveIndices = useCallback(() => { onChangeEventTypeAndIndexesName( filterEventType, - selectedOptions.map((so) => so.label) + selectedOptions.map((so) => so.label), + dataViewId ); setPopover(false); - }, [filterEventType, onChangeEventTypeAndIndexesName, selectedOptions]); + }, [dataViewId, filterEventType, onChangeEventTypeAndIndexesName, selectedOptions]); const resetDataSources = useCallback(() => { - onChangeFilter('all'); - }, [onChangeFilter]); + setDataViewId(defaultDataView.id); + setSelectedOptions( + getScopePatternListSelection( + defaultDataView, + SourcererScopeName.timeline, + signalIndexName, + true + ).map((indexSelected: string) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + setFilterEventType(eventType); + }, [defaultDataView, eventType, signalIndexName]); - const comboBox = useMemo( - () => ( - - ), - [onChangeCombo, indexesPatternOptions, renderOption, selectedOptions] + const dataViewSelectOptions = useMemo( + () => + kibanaDataViews.map(({ title, id }) => ({ + inputDisplay: + id === defaultDataView.id ? ( + + {SIEM_DATA_VIEW_LABEL} + + ) : ( + + {title} + + ), + value: id, + })), + [defaultDataView.id, kibanaDataViews] ); const filterOptions = useMemo( - () => getEventTypeOptions(filterEventType !== 'custom'), - [filterEventType] - ); - - const filter = useMemo( - () => ( - - ), - [filterEventType, filterOptions, onChangeFilter] + () => getEventTypeOptions(filterEventType !== 'custom', dataViewId === defaultDataView.id), + [defaultDataView.id, filterEventType, dataViewId] ); const button = useMemo(() => { - const options = getEventTypeOptions(); + const options = getEventTypeOptions(true, dataViewId === defaultDataView.id); return ( {options.find((opt) => opt.id === eventType)?.label} ); - }, [eventType, sourcererScope.loading, togglePopover]); + }, [defaultDataView.id, eventType, dataViewId, loading, togglePopover]); const tooltipContent = useMemo( - () => (isPopoverOpen ? null : sourcererScope.selectedPatterns.sort().join(', ')), - [isPopoverOpen, sourcererScope.selectedPatterns] + () => (isPopoverOpen ? null : selectedPatterns.sort().join(', ')), + [isPopoverOpen, selectedPatterns] ); const buttonWithTooptip = useMemo(() => { @@ -321,7 +355,7 @@ const PickEventTypeComponents: React.FC = ({ ); useEffect(() => { - const newSelectedOptions = sourcererScope.selectedPatterns.map((indexSelected) => ({ + const newSelectedOptions = selectedPatterns.map((indexSelected) => ({ label: indexSelected, value: indexSelected, })); @@ -331,7 +365,7 @@ const PickEventTypeComponents: React.FC = ({ } return prevSelectedOptions; }); - }, [sourcererScope.selectedPatterns]); + }, [selectedPatterns]); useEffect(() => { setFilterEventType((prevFilter) => (prevFilter !== eventType ? eventType : prevFilter)); @@ -352,9 +386,16 @@ const PickEventTypeComponents: React.FC = ({ <>{i18n.SELECT_INDEX_PATTERNS} - {filter} + = ({ > <> - {comboBox} + + + {!showAdvanceSettings && ( @@ -389,7 +446,8 @@ const PickEventTypeComponents: React.FC = ({ { - const getkibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); - const getScopeIdSelector = sourcererSelectors.scopeIdSelector(); - const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector(); - const getSignalIndexNameSelector = sourcererSelectors.signalIndexNameSelector(); - - const mapStateToProps = (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { - const kibanaIndexPatterns = getkibanaIndexPatternsSelector(state); - const scope = getScopeIdSelector(state, scopeId); - const configIndexPatterns = getConfigIndexPatternsSelector(state); - const signalIndexName = getSignalIndexNameSelector(state); - - return { - kibanaIndexPatterns, - configIndexPatterns, - signalIndexName, - sourcererScope: scope, - }; - }; - - return mapStateToProps; -}; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx index f05966bd97870b..ac96cc334f7951 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx @@ -10,6 +10,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import { Subscription } from 'rxjs'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { inputsModel } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { @@ -33,6 +34,7 @@ export interface UseTimelineEventsDetailsProps { docValueFields: DocValueFields[]; indexName: string; eventId: string; + runtimeMappings: MappingRuntimeFields; skip: boolean; } @@ -41,6 +43,7 @@ export const useTimelineEventsDetails = ({ docValueFields, indexName, eventId, + runtimeMappings, skip, }: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData'], object | undefined] => { const { data } = useKibana().services; @@ -112,13 +115,14 @@ export const useTimelineEventsDetails = ({ indexName, eventId, factoryQueryType: TimelineEventsQueries.details, + runtimeMappings, }; if (!deepEqual(prevRequest, myRequest)) { return myRequest; } return prevRequest; }); - }, [docValueFields, entityType, eventId, indexName]); + }, [docValueFields, entityType, eventId, indexName, runtimeMappings]); useEffect(() => { timelineDetailsSearch(timelineDetailsRequest); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx index 62846eb01e60f6..c6ae5d50abd37b 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx @@ -113,6 +113,7 @@ describe('useTimelineEvents', () => { filterQuery: '', startDate: '', limit: 25, + runtimeMappings: {}, sort: initSortDefault, skip: false, }; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 4ccb90b0ee5aec..c755159dd6b10d 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -11,6 +11,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Subscription } from 'rxjs'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ESQuery } from '../../../common/typed_json'; import { isCompleteResponse, isErrorResponse } from '../../../../../../src/plugins/data/public'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; @@ -74,15 +75,16 @@ type TimelineResponse = T extends 'kuery' export interface UseTimelineEventsProps { docValueFields?: DocValueFields[]; - filterQuery?: ESQuery | string; - skip?: boolean; endDate: string; eqlOptions?: EqlOptionsSelected; - id: string; fields: string[]; + filterQuery?: ESQuery | string; + id: string; indexNames: string[]; language?: KueryFilterQueryKind; limit: number; + runtimeMappings: MappingRuntimeFields; + skip?: boolean; sort?: TimelineRequestSortField[]; startDate: string; timerangeKind?: 'absolute' | 'relative'; @@ -131,6 +133,7 @@ export const useTimelineEvents = ({ indexNames, fields, filterQuery, + runtimeMappings, startDate, language = 'kuery', limit, @@ -341,6 +344,7 @@ export const useTimelineEvents = ({ querySize: prevRequest?.pagination.querySize ?? 0, sort: prevRequest?.sort ?? initSortDefault, timerange: prevRequest?.timerange ?? {}, + runtimeMappings: prevRequest?.runtimeMappings ?? {}, ...deStructureEqlOptions(prevEqlRequest), }; @@ -354,6 +358,7 @@ export const useTimelineEvents = ({ from: startDate, to: endDate, }, + runtimeMappings, ...deStructureEqlOptions(eqlOptions), }; @@ -373,6 +378,7 @@ export const useTimelineEvents = ({ querySize: limit, }, language, + runtimeMappings, sort, timerange: { interval: '12h', @@ -411,6 +417,7 @@ export const useTimelineEvents = ({ startDate, sort, fields, + runtimeMappings, ]); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx index f32b4df2c46842..3b13c261f970c6 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx @@ -15,7 +15,7 @@ import { useKibana } from '../../../common/lib/kibana'; import { DocValueFields, TimelineEventsQueries, - TimelineRequestBasicOptions, + TimelineKpiStrategyRequest, TimelineKpiStrategyResponse, TimerangeInput, } from '../../../../common/search_strategy'; @@ -44,7 +44,7 @@ export const useTimelineKpis = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [timelineKpiRequest, setTimelineKpiRequest] = useState( + const [timelineKpiRequest, setTimelineKpiRequest] = useState( null ); const [timelineKpiResponse, setTimelineKpiResponse] = @@ -52,7 +52,7 @@ export const useTimelineKpis = ({ const { addError, addWarning } = useAppToasts(); const timelineKpiSearch = useCallback( - (request: TimelineRequestBasicOptions | null) => { + (request: TimelineKpiStrategyRequest | null) => { if (request == null) { return; } @@ -61,7 +61,7 @@ export const useTimelineKpis = ({ setLoading(true); searchSubscription$.current = data.search - .search(request, { + .search(request, { strategy: 'timelineSearchStrategy', abortSignal: abortCtrl.current.signal, }) diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx index dd60656933ba83..c44d1351b0830f 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx @@ -123,4 +123,4 @@ export const useTimelinesStorage = (): TimelinesStorage => { return { getAllTimelines, getTimelineById, addTimeline }; }; -export { TimelinesStorage }; +export type { TimelinesStorage }; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx index 597ff25246d94e..7a36f7296eff31 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx @@ -27,7 +27,7 @@ jest.mock('../../common/containers/sourcerer', () => { return { ...originalModule, - useSourcererScope: jest.fn().mockReturnValue({ + useSourcererDataView: jest.fn().mockReturnValue({ indicesExist: true, }), }; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index f79d513380349f..3a9b9b0d2693ee 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -22,7 +22,7 @@ import { NewTemplateTimeline } from '../components/timeline/properties/new_templ import { NewTimeline } from '../components/timeline/properties/helpers'; import * as i18n from './translations'; import { SecurityPageName } from '../../app/types'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; const TimelinesContainer = styled.div` width: 100%; @@ -36,7 +36,7 @@ export const TimelinesPageComponent: React.FC = () => { const onImportTimelineBtnClick = useCallback(() => { setImportDataModalToggle(true); }, [setImportDataModalToggle]); - const { indicesExist } = useSourcererScope(); + const { indicesExist } = useSourcererDataView(); const capabilitiesCanUserCRUD: boolean = !!useKibana().services.application.capabilities.siem.crud; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index f3a70bd1390ae3..6002a7b2cf3989 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -193,10 +193,11 @@ export const setExcludedRowRendererIds = actionCreator<{ excludedRowRendererIds: RowRendererId[]; }>('SET_TIMELINE_EXCLUDED_ROW_RENDERER_IDS'); -export const updateIndexNames = actionCreator<{ +export const updateDataView = actionCreator<{ id: string; + dataViewId: string; indexNames: string[]; -}>('UPDATE_INDEXES_NAME'); +}>('UPDATE_DATA_VIEW'); export const setActiveTabTimeline = actionCreator<{ id: string; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts index 4c2b8d2992d3d6..a7d87bc98c048a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts @@ -22,6 +22,7 @@ export const timelineDefaults: SubsetTimelineModel & documentType: '', defaultColumns: defaultHeaders, dataProviders: [], + dataViewId: '', dateRange: { start, end }, deletedEventIds: [], description: '', diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts index d45ba26c0e12a2..175e6804c04a16 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts @@ -89,6 +89,7 @@ describe('Epic Timeline', () => { ], }, ], + dataViewId: '', deletedEventIds: [], description: '', documentType: '', @@ -238,6 +239,7 @@ describe('Epic Timeline', () => { }, }, ], + dataViewId: '', dateRange: { end: '2019-10-31T21:06:27.644Z', start: '2019-10-30T21:06:27.644Z', diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index 164f293edee65a..c973cf9a5bbbf3 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -65,7 +65,7 @@ import { updateRange, updateSort, upsertColumn, - updateIndexNames, + updateDataView, updateTimeline, updateTitleAndDescription, updateAutoSaveMsg, @@ -109,7 +109,7 @@ const timelineActionsType = [ updateProviders.type, updateTitleAndDescription.type, - updateIndexNames.type, + updateDataView.type, removeColumn.type, updateColumns.type, updateSort.type, @@ -326,6 +326,7 @@ export const createTimelineEpic = const timelineInput: TimelineInput = { columns: null, dataProviders: null, + dataViewId: null, description: null, eqlOptions: null, eventType: null, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts index 29b49197ef7978..18b6566b13b6c9 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts @@ -85,6 +85,7 @@ export type SubsetTimelineModel = Readonly< | 'columns' | 'defaultColumns' | 'dataProviders' + | 'dataViewId' | 'deletedEventIds' | 'description' | 'documentType' diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts index 639d1b1e4f489b..50992cf7b21def 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts @@ -85,6 +85,7 @@ const basicTimeline: TimelineModel = { columns: [], defaultColumns: [], dataProviders: [{ ...basicDataProvider }], + dataViewId: '', dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z', @@ -219,6 +220,7 @@ describe('Timeline', () => { const update = addNewTimeline({ id: 'bar', columns: defaultHeaders, + dataViewId: '', indexNames: [], timelineById: timelineByIdMock, timelineType: TimelineType.default, @@ -230,6 +232,7 @@ describe('Timeline', () => { const update = addNewTimeline({ id: 'bar', columns: timelineDefaults.columns, + dataViewId: '', indexNames: [], timelineById: timelineByIdMock, timelineType: TimelineType.default, @@ -247,6 +250,7 @@ describe('Timeline', () => { const update = addNewTimeline({ id: 'bar', columns: defaultHeaders, + dataViewId: '', indexNames: [], timelineById: timelineByIdMock, timelineType: TimelineType.default, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index e997bbd848d506..6a3aa68908bb5c 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -34,7 +34,7 @@ import { updateDataProviderKqlQuery, updateDataProviderType, updateEventType, - updateIndexNames, + updateDataView, updateIsFavorite, updateIsLive, updateKqlMode, @@ -326,12 +326,13 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, insertTimeline, })) - .case(updateIndexNames, (state, { id, indexNames }) => ({ + .case(updateDataView, (state, { id, dataViewId, indexNames }) => ({ ...state, timelineById: { ...state.timelineById, [id]: { ...state.timelineById[id], + dataViewId, indexNames, }, }, diff --git a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx index b7a994ffa7ca87..72122aba3c4aa5 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx @@ -39,7 +39,7 @@ import { Display } from '../display'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -69,7 +69,7 @@ const UebaDetailsComponent: React.FC = ({ detailName, uebaDeta ); const getFilters = () => [...uebaDetailsPageFilters, ...filters]; - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope( + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView( SourcererScopeName.detections ); diff --git a/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts b/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts index 976b033db5f5a9..e46a128e15f855 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts @@ -6,7 +6,7 @@ */ import { ActionCreator } from 'typescript-fsa'; -import { Query, IIndexPattern, Filter } from 'src/plugins/data/public'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { InputsModelId } from '../../../common/store/inputs/constants'; import { UebaTableType } from '../../store/model'; import { UebaQueryProps } from '../types'; @@ -54,7 +54,7 @@ export type UebaDetailsTabsProps = HostBodyComponentDispatchProps & indexNames: string[]; pageFilters?: Filter[]; filterQuery?: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; type: uebaModel.UebaType; }; diff --git a/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx b/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx index 4e0041a98454cc..93e22efdba3dda 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx +++ b/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx @@ -43,7 +43,7 @@ import { } from '../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; @@ -78,7 +78,7 @@ const UebaComponent = () => { const { uiSettings } = useKibana().services; const tabsFilters = filters; - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const [filterQuery, kqlError] = useMemo( () => convertToBuildEsQuery({ diff --git a/x-pack/plugins/security_solution/public/ueba/store/reducer.ts b/x-pack/plugins/security_solution/public/ueba/store/reducer.ts index f981868c21eb18..71e2f5312140c6 100644 --- a/x-pack/plugins/security_solution/public/ueba/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/ueba/store/reducer.ts @@ -111,7 +111,7 @@ export const uebaReducer = reducerWithInitialState(initialUebaState) ...state[uebaType].queries, [tableType]: { // TODO: Steph/ueba fix active page/limit on ueba tables. is broken because multiple UebaTableType.userRules tables - // @ts-ignore + // @ts-expect-error TS7053 ...state[uebaType].queries[tableType], activePage, }, @@ -126,7 +126,7 @@ export const uebaReducer = reducerWithInitialState(initialUebaState) ...state[uebaType].queries, [tableType]: { // TODO: Steph/ueba fix active page/limit on ueba tables. is broken because multiple UebaTableType.userRules tables - // @ts-ignore + // @ts-expect-error TS7053 ...state[uebaType].queries[tableType], limit, }, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts index 0fcb05827358e9..d20d29a34754cd 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts @@ -41,7 +41,7 @@ export const cli = async () => { node ${basename(process.argv[1])} [options] Options:${Object.keys(cliDefaults.default).reduce((out, option) => { - // @ts-ignore + // @ts-expect-error TS7053 return `${out}\n --${option}=${cliDefaults.default[option]}`; }, '')} `); diff --git a/x-pack/plugins/security_solution/server/client/client.ts b/x-pack/plugins/security_solution/server/client/client.ts index 12a13e06c2ffee..e697331944012e 100644 --- a/x-pack/plugins/security_solution/server/client/client.ts +++ b/x-pack/plugins/security_solution/server/client/client.ts @@ -6,22 +6,25 @@ */ import { ConfigType } from '../config'; -import { DEFAULT_PREVIEW_INDEX } from '../../common/constants'; +import { DEFAULT_DATA_VIEW_ID, DEFAULT_PREVIEW_INDEX } from '../../common/constants'; export class AppClient { private readonly signalsIndex: string; private readonly spaceId: string; private readonly previewIndex: string; + private readonly sourcererDataViewId: string; constructor(_spaceId: string, private config: ConfigType) { const configuredSignalsIndex = this.config.signalsIndex; this.signalsIndex = `${configuredSignalsIndex}-${_spaceId}`; this.previewIndex = `${DEFAULT_PREVIEW_INDEX}-${_spaceId}`; + this.sourcererDataViewId = `${DEFAULT_DATA_VIEW_ID}-${_spaceId}`; this.spaceId = _spaceId; } public getSignalsIndex = (): string => this.signalsIndex; public getPreviewIndex = (): string => this.previewIndex; + public getSourcererDataViewId = (): string => this.sourcererDataViewId; public getSpaceId = (): string => this.spaceId; } diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts index 277ccf030f808b..aef57f6679b271 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts @@ -68,7 +68,7 @@ describe('When migrating artifacts to fleet', () => { }), _index: '.fleet-artifacts-7', _id: `endpoint:endpoint-exceptionlist-macos-v1-${ - // @ts-ignore + // @ts-expect-error TS2339 props?.body?.decodedSha256 ?? 'UNKNOWN?' }`, _version: 1, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts index 1c9d781af38e75..a2e01e7e3312b2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts @@ -139,6 +139,7 @@ export class MockResponse { private command: ISOLATION_ACTIONS = 'isolate'; private comment?: string; private error?: string; + private ack?: boolean; constructor() {} @@ -154,9 +155,61 @@ export class MockResponse { command: this.command, comment: this.comment, }, + action_response: { + endpoint: { + ack: this.ack, + }, + }, }; } + public withAck(ack?: boolean) { + this.ack = ack; + return this; + } + + public forAction(id: string) { + this.actionID = id; + return this; + } + public forAgent(id: string) { + this.agent = id; + return this; + } +} + +export const aMockResponse = (actionID: string, agentID: string, ack?: boolean): MockResponse => { + return new MockResponse().forAction(actionID).forAgent(agentID).withAck(ack); +}; + +export class MockEndpointResponse { + private actionID: string = uuid.v4(); + private ts: moment.Moment = moment(); + private started: moment.Moment = moment(); + private completed: moment.Moment = moment(); + private agent: string = ''; + private command: ISOLATION_ACTIONS = 'isolate'; + private comment?: string; + private error?: string; + + constructor() {} + + public build(): LogsEndpointActionResponse { + return { + '@timestamp': this.ts.toISOString(), + EndpointActions: { + action_id: this.actionID, + completed_at: this.completed.toISOString(), + data: { + command: this.command, + comment: this.comment, + }, + started_at: this.started.toISOString(), + }, + agent: { id: this.agent }, + error: { message: this.error ?? '' }, + }; + } public forAction(id: string) { this.actionID = id; return this; @@ -167,6 +220,6 @@ export class MockResponse { } } -export const aMockResponse = (actionID: string, agentID: string): MockResponse => { - return new MockResponse().forAction(actionID).forAgent(agentID); +export const aMockEndpointResponse = (actionID: string, agentID: string): MockEndpointResponse => { + return new MockEndpointResponse().forAction(actionID).forAgent(agentID); }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts index 2f8ba30936f25b..e4dc9b049e2baa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts @@ -27,7 +27,15 @@ import { } from '../../mocks'; import { registerActionStatusRoutes } from './status'; import uuid from 'uuid'; -import { aMockAction, aMockResponse, MockAction, mockSearchResult, MockResponse } from './mocks'; +import { + aMockAction, + aMockResponse, + aMockEndpointResponse, + MockEndpointResponse, + MockAction, + mockSearchResult, + MockResponse, +} from './mocks'; describe('Endpoint Action Status', () => { describe('schema', () => { @@ -62,7 +70,11 @@ describe('Endpoint Action Status', () => { // convenience for calling the route and handler for action status let getPendingStatus: (reqParams?: any) => Promise>; // convenience for injecting mock responses for actions index and responses - let havingActionsAndResponses: (actions: MockAction[], responses: any[]) => void; + let havingActionsAndResponses: ( + actions: MockAction[], + responses: MockResponse[], + endpointResponses?: MockEndpointResponse[] + ) => void; beforeEach(() => { const esClientMock = elasticsearchServiceMock.createScopedClusterClient(); @@ -75,7 +87,10 @@ describe('Endpoint Action Status', () => { logFactory: loggingSystemMock.create(), service: endpointAppContextService, config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + experimentalFeatures: { + ...parseExperimentalConfigValue(createMockConfig().enableExperimental), + pendingActionResponsesWithAck: true, + }, }); getPendingStatus = async (reqParams?: any): Promise> => { @@ -94,12 +109,19 @@ describe('Endpoint Action Status', () => { return mockResponse; }; - havingActionsAndResponses = (actions: MockAction[], responses: MockResponse[]) => { + havingActionsAndResponses = ( + actions: MockAction[], + responses: MockResponse[], + endpointResponses?: MockEndpointResponse[] + ) => { esClientMock.asCurrentUser.search = jest.fn().mockImplementation((req) => { const size = req.size ? req.size : 10; - const items: any[] = - req.index === '.fleet-actions' ? actions.splice(0, size) : responses.splice(0, size); + req.index === '.fleet-actions' + ? actions.splice(0, size) + : req.index === '.logs-endpoint.action.responses' && !!endpointResponses + ? endpointResponses + : responses.splice(0, size); if (items.length > 0) { return Promise.resolve(mockSearchResult(items.map((x) => x.build()))); @@ -311,5 +333,442 @@ describe('Endpoint Action Status', () => { }, }); }); + + describe('with endpoint response index', () => { + it('should respond with 1 pending action response when no endpoint response', async () => { + const mockAgentID = 'XYZABC-000'; + const actionID = 'some-known-action_id'; + havingActionsAndResponses( + [aMockAction().withAgent(mockAgentID).withID(actionID)], + [aMockResponse(actionID, mockAgentID, true)] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [mockAgentID], + }, + }); + + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID); + }); + + it('should respond with 0 pending action response when there is a matching endpoint response', async () => { + const mockAgentID = 'XYZABC-000'; + const actionID = 'some-known-action_id'; + havingActionsAndResponses( + [aMockAction().withAgent(mockAgentID).withID(actionID)], + [aMockResponse(actionID, mockAgentID, true)], + [aMockEndpointResponse(actionID, mockAgentID)] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [mockAgentID], + }, + }); + + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID); + }); + + it('should include a total count of a pending action response', async () => { + const mockAgentId = 'XYZABC-000'; + const actionIds = ['action_id_0', 'action_id_1']; + havingActionsAndResponses( + [ + aMockAction().withAgent(mockAgentId).withAction('isolate').withID(actionIds[0]), + aMockAction().withAgent(mockAgentId).withAction('isolate').withID(actionIds[1]), + ], + [ + aMockResponse(actionIds[0], mockAgentId, true), + aMockResponse(actionIds[1], mockAgentId, true), + ] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [mockAgentId], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentId); + expect( + (response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate + ).toEqual(2); + }); + + it('should show multiple pending action responses, and their counts', async () => { + const mockAgentID = 'XYZABC-000'; + const actionIds = ['ack_0', 'ack_1', 'ack_2', 'ack_3', 'ack_4']; + havingActionsAndResponses( + [ + aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[0]), + aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[1]), + aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[2]), + aMockAction().withAgent(mockAgentID).withAction('unisolate').withID(actionIds[3]), + aMockAction().withAgent(mockAgentID).withAction('unisolate').withID(actionIds[4]), + ], + [ + aMockResponse(actionIds[0], mockAgentID, true), + aMockResponse(actionIds[1], mockAgentID, true), + aMockResponse(actionIds[2], mockAgentID, true), + aMockResponse(actionIds[3], mockAgentID, true), + aMockResponse(actionIds[4], mockAgentID, true), + ] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [mockAgentID], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID); + expect( + (response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate + ).toEqual(3); + expect( + (response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.unisolate + ).toEqual(2); + }); + }); + }); + + describe('response (when pendingActionResponsesWithAck is FALSE)', () => { + let endpointAppContextService: EndpointAppContextService; + + // convenience for calling the route and handler for action status + let getPendingStatus: (reqParams?: any) => Promise>; + // convenience for injecting mock responses for actions index and responses + let havingActionsAndResponses: ( + actions: MockAction[], + responses: MockResponse[], + endpointResponses?: MockEndpointResponse[] + ) => void; + + beforeEach(() => { + const esClientMock = elasticsearchServiceMock.createScopedClusterClient(); + const routerMock = httpServiceMock.createRouter(); + endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); + endpointAppContextService.start(createMockEndpointAppContextServiceStartContract()); + + registerActionStatusRoutes(routerMock, { + logFactory: loggingSystemMock.create(), + service: endpointAppContextService, + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: { + ...parseExperimentalConfigValue(createMockConfig().enableExperimental), + pendingActionResponsesWithAck: false, + }, + }); + + getPendingStatus = async (reqParams?: any): Promise> => { + const req = httpServerMock.createKibanaRequest(reqParams); + const mockResponse = httpServerMock.createResponseFactory(); + const [, routeHandler]: [ + RouteConfig, + RequestHandler + ] = routerMock.get.mock.calls.find(([{ path }]) => path.startsWith(ACTION_STATUS_ROUTE))!; + await routeHandler( + createRouteHandlerContext(esClientMock, savedObjectsClientMock.create()), + req, + mockResponse + ); + + return mockResponse; + }; + + havingActionsAndResponses = ( + actions: MockAction[], + responses: MockResponse[], + endpointResponses?: MockEndpointResponse[] + ) => { + esClientMock.asCurrentUser.search = jest.fn().mockImplementation((req) => { + const size = req.size ? req.size : 10; + const items: any[] = + req.index === '.fleet-actions' + ? actions.splice(0, size) + : req.index === '.logs-endpoint.action.responses' && !!endpointResponses + ? endpointResponses + : responses.splice(0, size); + + if (items.length > 0) { + return Promise.resolve(mockSearchResult(items.map((x) => x.build()))); + } else { + return Promise.resolve(mockSearchResult()); + } + }); + }; + }); + + afterEach(() => { + endpointAppContextService.stop(); + }); + + it('should include total counts for large (more than a page) action counts', async () => { + const mockID = 'XYZABC-000'; + const actions = []; + for (let i = 0; i < 1400; i++) { + // putting more than a single page of results in + actions.push(aMockAction().withAgent(mockID)); + } + havingActionsAndResponses(actions, []); + + const response = await getPendingStatus({ + query: { + agent_ids: [mockID], + }, + }); + + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockID); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate).toEqual( + 0 + ); + }); + it('should include a total count of a pending action', async () => { + const mockID = 'XYZABC-000'; + havingActionsAndResponses( + [ + aMockAction().withAgent(mockID).withAction('isolate'), + aMockAction().withAgent(mockID).withAction('isolate'), + ], + [] + ); + const response = await getPendingStatus({ + query: { + agent_ids: [mockID], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockID); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate).toEqual( + 0 + ); + }); + it('should show multiple pending actions, and their counts', async () => { + const mockID = 'XYZABC-000'; + havingActionsAndResponses( + [ + aMockAction().withAgent(mockID).withAction('isolate'), + aMockAction().withAgent(mockID).withAction('isolate'), + aMockAction().withAgent(mockID).withAction('isolate'), + aMockAction().withAgent(mockID).withAction('unisolate'), + aMockAction().withAgent(mockID).withAction('unisolate'), + ], + [] + ); + const response = await getPendingStatus({ + query: { + agent_ids: [mockID], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockID); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate).toEqual( + 0 + ); + expect( + (response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.unisolate + ).toEqual(0); + }); + it('should calculate correct pending counts from grouped/bulked actions', async () => { + const mockID = 'XYZABC-000'; + havingActionsAndResponses( + [ + aMockAction() + .withAgents([mockID, 'IRRELEVANT-OTHER-AGENT', 'ANOTHER-POSSIBLE-AGENT']) + .withAction('isolate'), + aMockAction().withAgents([mockID, 'YET-ANOTHER-AGENT-ID']).withAction('isolate'), + aMockAction().withAgents(['YET-ANOTHER-AGENT-ID']).withAction('isolate'), // one WITHOUT our agent-under-test + ], + [] + ); + const response = await getPendingStatus({ + query: { + agent_ids: [mockID], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockID); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate).toEqual( + 0 + ); + }); + + it('should exclude actions that have responses from the pending count', async () => { + const mockAgentID = 'XYZABC-000'; + const actionID = 'some-known-actionid'; + havingActionsAndResponses( + [ + aMockAction().withAgent(mockAgentID).withAction('isolate'), + aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionID), + ], + [aMockResponse(actionID, mockAgentID)] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [mockAgentID], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate).toEqual( + 0 + ); + }); + it('should have accurate counts for multiple agents, bulk actions, and responses', async () => { + const agentOne = 'XYZABC-000'; + const agentTwo = 'DEADBEEF'; + const agentThree = 'IDIDIDID'; + + const actionTwoID = 'ID-TWO'; + havingActionsAndResponses( + [ + aMockAction().withAgents([agentOne, agentTwo, agentThree]).withAction('isolate'), + aMockAction() + .withAgents([agentTwo, agentThree]) + .withAction('isolate') + .withID(actionTwoID), + aMockAction().withAgents([agentThree]).withAction('isolate'), + ], + [aMockResponse(actionTwoID, agentThree)] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [agentOne, agentTwo, agentThree], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(3); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toContainEqual({ + agent_id: agentOne, + pending_actions: { + isolate: 0, + }, + }); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toContainEqual({ + agent_id: agentTwo, + pending_actions: { + isolate: 0, + }, + }); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toContainEqual({ + agent_id: agentThree, + pending_actions: { + isolate: 0, + }, + }); + }); + + describe('with endpoint response index', () => { + it('should include a total count of a pending action response', async () => { + const mockAgentId = 'XYZABC-000'; + const actionIds = ['action_id_0', 'action_id_1']; + havingActionsAndResponses( + [ + aMockAction().withAgent(mockAgentId).withAction('isolate').withID(actionIds[0]), + aMockAction().withAgent(mockAgentId).withAction('isolate').withID(actionIds[1]), + ], + [ + aMockResponse(actionIds[0], mockAgentId, true), + aMockResponse(actionIds[1], mockAgentId, true), + ] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [mockAgentId], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentId); + expect( + (response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate + ).toEqual(0); + }); + + it('should show multiple pending action responses, and their counts', async () => { + const mockAgentID = 'XYZABC-000'; + const actionIds = ['ack_0', 'ack_1', 'ack_2', 'ack_3', 'ack_4']; + havingActionsAndResponses( + [ + aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[0]), + aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[1]), + aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[2]), + aMockAction().withAgent(mockAgentID).withAction('unisolate').withID(actionIds[3]), + aMockAction().withAgent(mockAgentID).withAction('unisolate').withID(actionIds[4]), + ], + [ + aMockResponse(actionIds[0], mockAgentID, true), + aMockResponse(actionIds[1], mockAgentID, true), + aMockResponse(actionIds[2], mockAgentID, true), + aMockResponse(actionIds[3], mockAgentID, true), + aMockResponse(actionIds[4], mockAgentID, true), + ] + ); + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + const response = await getPendingStatus({ + query: { + agent_ids: [mockAgentID], + }, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1); + expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID); + expect( + (response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate + ).toEqual(0); + expect( + (response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.unisolate + ).toEqual(0); + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts index 4ba03bf220c21c..32c709aef2b879 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts @@ -50,7 +50,8 @@ export const actionStatusRequestHandler = function ( const response = await getPendingActionCounts( esClient, endpointContext.service.getEndpointMetadataService(), - agentIDs + agentIDs, + endpointContext.experimentalFeatures.pendingActionResponsesWithAck ); return res.ok({ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 4ef3291e1b8f28..c72b1307d04b7a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -41,7 +41,7 @@ import { findAllUnenrolledAgentIds } from './support/unenroll'; import { getAllEndpointPackagePolicies } from './support/endpoint_package_policies'; import { findAgentIdsByStatus } from './support/agent_status'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; -import { fleetAgentStatusToEndpointHostStatus } from '../../utils'; +import { catchAndWrapError, fleetAgentStatusToEndpointHostStatus } from '../../utils'; import { queryResponseToHostListResult, queryResponseToHostResult, @@ -85,8 +85,8 @@ const errorHandler = ( return res.badRequest({ body: error }); } - // legacy check for Boom errors. `ts-ignore` is for the errors around non-standard error properties - // @ts-ignore + // legacy check for Boom errors. for the errors around non-standard error properties + // @ts-expect-error TS2339 const boomStatusCode = error.isBoom && error?.output?.statusCode; if (boomStatusCode) { return res.customError({ @@ -194,7 +194,9 @@ export async function getHostMetaData( const query = getESQueryHostMetadataByID(id); - const response = await esClient.asCurrentUser.search(query); + const response = await esClient.asCurrentUser + .search(query) + .catch(catchAndWrapError); const hostResult = queryResponseToHostResult(response.body); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mocks.ts index 083263809d3091..dd871b78110664 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mocks.ts @@ -66,7 +66,7 @@ export const getPutTrustedAppByPolicyMock = function (): ExceptionListItemSchema export const getPackagePoliciesResponse = function (): PackagePolicy[] { return [ // Next line is ts-ignored as this is the response when the policy doesn't exists but the type is complaining about it. - // @ts-ignore + // @ts-expect-error TS2740 { id: '9da95be9-9bee-4761-a8c4-28d6d9bd8c71', version: undefined }, { id: 'e5cbb9cf-98aa-4303-a04b-6a1165915079', diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts index 6b44b7b3ce87aa..4dbfd700496f5e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts @@ -186,7 +186,8 @@ export const getPendingActionCounts = async ( esClient: ElasticsearchClient, metadataService: EndpointMetadataService, /** The Fleet Agent IDs to be checked */ - agentIDs: string[] + agentIDs: string[], + isPendingActionResponsesWithAckEnabled: boolean ): Promise => { // retrieve the unexpired actions for the given hosts const recentActions = await esClient @@ -222,8 +223,6 @@ export const getPendingActionCounts = async ( agentIDs ); - // - const pending: EndpointPendingActions[] = []; for (const agentId of agentIDs) { const agentResponses = responses[agentId]; @@ -256,11 +255,17 @@ export const getPendingActionCounts = async ( pending_actions: pendingActions .map((a) => a.data.command) .reduce((acc, cur) => { - if (cur in acc) { - acc[cur] += 1; + if (!isPendingActionResponsesWithAckEnabled) { + acc[cur] = 0; // set pending counts to 0 when FF is disabled } else { - acc[cur] = 1; + // else do the usual counting + if (cur in acc) { + acc[cur] += 1; + } else { + acc[cur] = 1; + } } + return acc; }, {} as EndpointPendingActions['pending_actions']), }); @@ -270,11 +275,11 @@ export const getPendingActionCounts = async ( }; /** - * Returns a boolean for search result + * Returns a string of action ids for search result * * @param esClient * @param actionIds - * @param agentIds + * @param agentId */ const hasEndpointResponseDoc = async ({ actionIds, @@ -307,7 +312,7 @@ const hasEndpointResponseDoc = async ({ }; /** - * Returns back a map of elastic Agent IDs to array of Action IDs that have received a response. + * Returns back a map of elastic Agent IDs to array of action responses that have a response. * * @param esClient * @param metadataService diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 736bf1c58cb901..b687519bf55739 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -485,7 +485,7 @@ export class ManifestManager { try { await this.packagePolicyService.update( this.savedObjectsClient, - // @ts-ignore + // @ts-expect-error TS2345 undefined, id, newPackagePolicy diff --git a/x-pack/plugins/security_solution/server/index.ts b/x-pack/plugins/security_solution/server/index.ts index 0adcd25f5e2466..16a4992e68698c 100644 --- a/x-pack/plugins/security_solution/server/index.ts +++ b/x-pack/plugins/security_solution/server/index.ts @@ -45,7 +45,8 @@ export const config: PluginConfigDescriptor = { ], }; -export { ConfigType, Plugin, PluginSetup, PluginStart }; +export type { ConfigType, PluginSetup, PluginStart }; +export { Plugin }; export { AppClient }; export type { SecuritySolutionApiRequestHandlerContext } from './types'; export { EndpointError } from './endpoint/errors'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/README.md index 5806df1de0e3d3..a2385e15a1bf26 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/README.md @@ -152,8 +152,8 @@ logging.events: ``` See these two README.md's pages for more references on the alerting and actions API: -https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md -https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions +https://github.com/elastic/kibana/blob/main/x-pack/plugins/alerting/README.md +https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions ### Signals API diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts new file mode 100644 index 00000000000000..b40f6c6f8a72d3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts @@ -0,0 +1,102 @@ +/* + * 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 { AlertServicesMock, alertsMock } from '../../../../../alerting/server/mocks'; +import { sampleThresholdAlert } from '../rule_types/__mocks__/threshold'; +import { + NotificationRuleTypeParams, + scheduleNotificationActions, +} from './schedule_notification_actions'; + +describe('schedule_notification_actions', () => { + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertId = 'fb30ddd1-5edc-43e2-9afb-3bcd970b78ee'; + + const notificationRuleParams: NotificationRuleTypeParams = { + author: ['123'], + id: '123', + name: 'some name', + description: '123', + buildingBlockType: undefined, + from: '123', + ruleId: '123', + immutable: false, + license: '', + falsePositives: ['false positive 1', 'false positive 2'], + query: 'user.name: root or user.name: admin', + language: 'kuery', + savedId: 'savedId-123', + timelineId: 'timelineid-123', + timelineTitle: 'timeline-title-123', + meta: {}, + filters: [], + index: ['index-123'], + maxSignals: 100, + riskScore: 80, + riskScoreMapping: [], + ruleNameOverride: undefined, + outputIndex: 'output-1', + severity: 'high', + severityMapping: [], + threat: [], + timestampOverride: undefined, + to: 'now', + type: 'query', + references: ['http://www.example.com'], + namespace: 'a namespace', + note: '# sample markdown', + version: 1, + exceptionsList: [], + }; + + it('Should schedule actions with unflatted and legacy context', () => { + const alertInstance = alertServices.alertInstanceFactory(alertId); + const signals = [sampleThresholdAlert._source, sampleThresholdAlert._source]; + scheduleNotificationActions({ + alertInstance, + signalsCount: 2, + resultsLink: '', + ruleParams: notificationRuleParams, + signals, + }); + expect(alertInstance.scheduleActions).toHaveBeenCalledWith( + 'default', + expect.objectContaining({ + alerts: [ + expect.objectContaining({ + kibana: expect.objectContaining({ + alert: expect.objectContaining({ + rule: expect.objectContaining({ + uuid: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + }), + }), + }), + signal: expect.objectContaining({ + rule: expect.objectContaining({ + id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + }), + }), + }), + expect.objectContaining({ + kibana: expect.objectContaining({ + alert: expect.objectContaining({ + rule: expect.objectContaining({ + uuid: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + }), + }), + }), + signal: expect.objectContaining({ + rule: expect.objectContaining({ + id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + }), + }), + }), + ], + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts index 147c54cd6eb8af..2362a6a392a56e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts @@ -7,13 +7,44 @@ import { mapKeys, snakeCase } from 'lodash/fp'; import { AlertInstance } from '../../../../../alerting/server'; +import { expandDottedObject } from '../rule_types/utils'; import { RuleParams } from '../schemas/rule_schemas'; +import aadFieldConversion from '../routes/index/signal_aad_mapping.json'; +import { isRACAlert } from '../signals/utils'; +import { RACAlert } from '../rule_types/types'; export type NotificationRuleTypeParams = RuleParams & { id: string; name: string; }; +const convertToLegacyAlert = (alert: RACAlert) => + Object.entries(aadFieldConversion).reduce((acc, [legacyField, aadField]) => { + const val = alert[aadField]; + if (val != null) { + return { + ...acc, + [legacyField]: val, + }; + } + return acc; + }, {}); + +/* + * Formats alerts before sending to `scheduleActions`. We augment the context with + * the equivalent "legacy" alert context so that pre-8.0 actions will continue to work. + */ +const formatAlertsForNotificationActions = (alerts: unknown[]): unknown[] => { + return alerts.map((alert) => + isRACAlert(alert) + ? { + ...expandDottedObject(convertToLegacyAlert(alert)), + ...expandDottedObject(alert), + } + : alert + ); +}; + interface ScheduleNotificationActions { alertInstance: AlertInstance; signalsCount: number; @@ -36,5 +67,5 @@ export const scheduleNotificationActions = ({ .scheduleActions('default', { results_link: resultsLink, rule: mapKeys(snakeCase, ruleParams), - alerts: signals, + alerts: formatAlertsForNotificationActions(signals), }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index 2f5f8ac846954e..fc88e7b8b2be03 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -11,6 +11,7 @@ import { coreMock } from 'src/core/server/mocks'; import { ActionsApiRequestHandlerContext } from '../../../../../../actions/server'; import { AlertingApiRequestHandlerContext } from '../../../../../../alerting/server'; import { rulesClientMock } from '../../../../../../alerting/server/mocks'; +import { actionsClientMock } from '../../../../../../actions/server/mocks'; import { licensingMock } from '../../../../../../licensing/server/mocks'; import { listMock } from '../../../../../../lists/server/mocks'; import { ruleRegistryMocks } from '../../../../../../rule_registry/server/mocks'; @@ -44,6 +45,7 @@ const createMockClients = () => { exceptionListClient: listMock.getExceptionListClient(core.savedObjects.client), }, rulesClient: rulesClientMock.create(), + actionsClient: actionsClientMock.create(), ruleDataService: ruleRegistryMocks.createRuleDataService(), config: createMockConfig(), @@ -65,7 +67,9 @@ const createRequestContextMock = ( return { core: clients.core, securitySolution: createSecuritySolutionRequestContextMock(clients), - actions: {} as unknown as jest.Mocked, + actions: { + getActionsClient: jest.fn(() => clients.actionsClient), + } as unknown as jest.Mocked, alerting: { getRulesClient: jest.fn(() => clients.rulesClient), } as unknown as jest.Mocked, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index a890b12d3b7aad..3c1a49c6408633 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -9,7 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; -import { SavedObjectsFindResponse, SavedObjectsFindResult } from 'kibana/server'; +import { SavedObjectsFindResponse } from 'src/core/server'; import { ActionResult } from '../../../../../../actions/server'; import { @@ -23,6 +23,7 @@ import { DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL, DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL, DETECTION_ENGINE_RULES_BULK_ACTION, + INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, } from '../../../../../common/constants'; import { RuleAlertType, @@ -42,7 +43,7 @@ import { SanitizedAlert, ResolvedSanitizedRule } from '../../../../../../alertin import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; import { getPerformBulkActionSchemaMock } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema.mock'; import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; -import { FindBulkExecutionLogResponse } from '../../rule_execution_log/types'; +import { GetCurrentStatusBulkResult } from '../../rule_execution_log/types'; // eslint-disable-next-line no-restricted-imports import type { LegacyRuleNotificationAlertType } from '../../notifications/legacy_types'; @@ -232,6 +233,13 @@ export const ruleStatusRequest = () => body: { ids: ['04128c15-0d1b-4716-a4c5-46997ac7f3bd'] }, }); +export const internalRuleStatusRequest = () => + requestMock.create({ + method: 'post', + path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, + body: { ruleId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' }, + }); + export const getImportRulesRequest = (hapiStream?: HapiReadableStream) => requestMock.create({ method: 'post', @@ -475,94 +483,64 @@ export const getEmptySavedObjectsResponse = saved_objects: [], }); -export const getRuleExecutionStatuses = (): Array< - SavedObjectsFindResult -> => [ - { - type: 'my-type', - id: 'e0b86950-4e9f-11ea-bdbd-07b56aa159b3', - attributes: { - statusDate: '2020-02-18T15:26:49.783Z', - status: RuleExecutionStatus.succeeded, - lastFailureAt: undefined, - lastSuccessAt: '2020-02-18T15:26:49.783Z', - lastFailureMessage: undefined, - lastSuccessMessage: 'succeeded', - lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), - gap: '500.32', - searchAfterTimeDurations: ['200.00'], - bulkCreateTimeDurations: ['800.43'], - }, - score: 1, - references: [ - { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bc', - type: 'alert', - name: 'alert_0', - }, - ], - updated_at: '2020-02-18T15:26:51.333Z', - version: 'WzQ2LDFd', - }, - { - type: 'my-type', - id: '91246bd0-5261-11ea-9650-33b954270f67', - attributes: { - statusDate: '2020-02-18T15:15:58.806Z', - status: RuleExecutionStatus.failed, - lastFailureAt: '2020-02-18T15:15:58.806Z', - lastSuccessAt: '2020-02-13T20:31:59.855Z', - lastFailureMessage: - 'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.', - lastSuccessMessage: 'succeeded', - lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), - gap: '500.32', - searchAfterTimeDurations: ['200.00'], - bulkCreateTimeDurations: ['800.43'], - }, - score: 1, - references: [ - { - id: '1ea5a820-4da1-4e82-92a1-2b43a7bece08', - type: 'alert', - name: 'alert_0', - }, - ], - updated_at: '2020-02-18T15:15:58.860Z', - version: 'WzMyLDFd', - }, +export const getRuleExecutionStatusSucceeded = (): IRuleStatusSOAttributes => ({ + statusDate: '2020-02-18T15:26:49.783Z', + status: RuleExecutionStatus.succeeded, + lastFailureAt: undefined, + lastSuccessAt: '2020-02-18T15:26:49.783Z', + lastFailureMessage: undefined, + lastSuccessMessage: 'succeeded', + lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), + gap: '500.32', + searchAfterTimeDurations: ['200.00'], + bulkCreateTimeDurations: ['800.43'], +}); + +export const getRuleExecutionStatusFailed = (): IRuleStatusSOAttributes => ({ + statusDate: '2020-02-18T15:15:58.806Z', + status: RuleExecutionStatus.failed, + lastFailureAt: '2020-02-18T15:15:58.806Z', + lastSuccessAt: '2020-02-13T20:31:59.855Z', + lastFailureMessage: + 'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.', + lastSuccessMessage: 'succeeded', + lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), + gap: '500.32', + searchAfterTimeDurations: ['200.00'], + bulkCreateTimeDurations: ['800.43'], +}); + +export const getRuleExecutionStatuses = (): IRuleStatusSOAttributes[] => [ + getRuleExecutionStatusSucceeded(), + getRuleExecutionStatusFailed(), ]; -export const getFindBulkResultStatus = (): FindBulkExecutionLogResponse => ({ - '04128c15-0d1b-4716-a4c5-46997ac7f3bd': [ - { - statusDate: '2020-02-18T15:26:49.783Z', - status: RuleExecutionStatus.succeeded, - lastFailureAt: undefined, - lastSuccessAt: '2020-02-18T15:26:49.783Z', - lastFailureMessage: undefined, - lastSuccessMessage: 'succeeded', - lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), - gap: '500.32', - searchAfterTimeDurations: ['200.00'], - bulkCreateTimeDurations: ['800.43'], - }, - ], - '1ea5a820-4da1-4e82-92a1-2b43a7bece08': [ - { - statusDate: '2020-02-18T15:15:58.806Z', - status: RuleExecutionStatus.failed, - lastFailureAt: '2020-02-18T15:15:58.806Z', - lastSuccessAt: '2020-02-13T20:31:59.855Z', - lastFailureMessage: - 'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.', - lastSuccessMessage: 'succeeded', - lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), - gap: '500.32', - searchAfterTimeDurations: ['200.00'], - bulkCreateTimeDurations: ['800.43'], - }, - ], +export const getFindBulkResultStatus = (): GetCurrentStatusBulkResult => ({ + '04128c15-0d1b-4716-a4c5-46997ac7f3bd': { + statusDate: '2020-02-18T15:26:49.783Z', + status: RuleExecutionStatus.succeeded, + lastFailureAt: undefined, + lastSuccessAt: '2020-02-18T15:26:49.783Z', + lastFailureMessage: undefined, + lastSuccessMessage: 'succeeded', + lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), + gap: '500.32', + searchAfterTimeDurations: ['200.00'], + bulkCreateTimeDurations: ['800.43'], + }, + '1ea5a820-4da1-4e82-92a1-2b43a7bece08': { + statusDate: '2020-02-18T15:15:58.806Z', + status: RuleExecutionStatus.failed, + lastFailureAt: '2020-02-18T15:15:58.806Z', + lastSuccessAt: '2020-02-13T20:31:59.855Z', + lastFailureMessage: + 'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.', + lastSuccessMessage: 'succeeded', + lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), + gap: '500.32', + searchAfterTimeDurations: ['200.00'], + bulkCreateTimeDurations: ['800.43'], + }, }); export const getBasicEmptySearchResponse = (): estypes.SearchResponse => ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/check_template_version.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/check_template_version.ts index 974d18292a078c..0040b5f30afb41 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/check_template_version.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/check_template_version.ts @@ -43,10 +43,12 @@ export const templateNeedsUpdate = async ({ export const fieldAliasesOutdated = async (esClient: ElasticsearchClient, index: string) => { const { body: indexMappings } = await esClient.indices.get({ index }); - for (const [_, mapping] of Object.entries(indexMappings)) { - const aliasesVersion = get(mapping.mappings?._meta, ALIAS_VERSION_FIELD) ?? 0; - if (aliasesVersion < SIGNALS_FIELD_ALIASES_VERSION) { - return true; + for (const [indexName, mapping] of Object.entries(indexMappings)) { + if (indexName.startsWith(`${index}-`)) { + const aliasesVersion = get(mapping.mappings?._meta, ALIAS_VERSION_FIELD) ?? 0; + if (aliasesVersion < SIGNALS_FIELD_ALIASES_VERSION) { + return true; + } } } return 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 2e377e50530d1f..cf40988c1d7421 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 @@ -5,12 +5,12 @@ * 2.0. */ -import { get } from 'lodash'; +import { chunk, get } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from 'src/core/server'; import { transformError, - getIndexExists, + getBootstrapIndexExists, getPolicyExists, setPolicy, createBootstrapIndex, @@ -25,6 +25,8 @@ import { getSignalsTemplate, SIGNALS_TEMPLATE_VERSION, createBackwardsCompatibilityMapping, + ALIAS_VERSION_FIELD, + SIGNALS_FIELD_ALIASES_VERSION, } from './get_signals_template'; import { ensureMigrationCleanupPolicy } from '../../migrations/migration_cleanup'; import signalsPolicy from './signals_policy.json'; @@ -71,7 +73,10 @@ export const createDetectionIndex = async ( const spaceId = context.getSpaceId(); const index = siemClient.getSignalsIndex(); - const indexExists = await getIndexExists(esClient, index); + const indexExists = await getBootstrapIndexExists( + context.core.elasticsearch.client.asInternalUser, + index + ); const { ruleRegistryEnabled } = config.experimentalFeatures; // If using the rule registry implementation, we don't want to create new .siem-signals indices - @@ -124,6 +129,11 @@ export const createDetectionIndex = async ( } }; +// This function can be expensive if there are lots of existing .siem-signals indices +// because any new backwards compatibility mappings need to be applied to all of them +// while also preserving the original 'version' of the mapping. To do it somewhat efficiently, +// we first group the indices by version and exclude any that already have up-to-date +// aliases. Then we start updating the mappings sequentially in chunks. const addFieldAliasesToIndices = async ({ esClient, index, @@ -132,14 +142,34 @@ const addFieldAliasesToIndices = async ({ index: string; }) => { const { body: indexMappings } = await esClient.indices.get({ index }); + const indicesByVersion: Record = {}; + const versions: Set = new Set(); for (const [indexName, mapping] of Object.entries(indexMappings)) { - const currentVersion: number | undefined = get(mapping.mappings?._meta, 'version'); - const body = createBackwardsCompatibilityMapping(currentVersion ?? 0); - await esClient.indices.putMapping({ - index: indexName, - body, - allow_no_indices: true, - } as estypes.IndicesPutMappingRequest); + 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 + // This limits us to legacy .siem-signals indices, since alerts as data indices use a different naming + // scheme (but have the same alias, so will also be returned by the "get" request) + if ( + indexName.startsWith(`${index}-`) && + isOutdated({ current: aliasesVersion, target: SIGNALS_FIELD_ALIASES_VERSION }) + ) { + indicesByVersion[version] = indicesByVersion[version] + ? [...indicesByVersion[version], indexName] + : [indexName]; + versions.add(version); + } + } + for (const version of versions) { + const body = createBackwardsCompatibilityMapping(version); + const indexNameChunks = chunk(indicesByVersion[version], 20); + for (const indexNameChunk of indexNameChunks) { + await esClient.indices.putMapping({ + index: indexNameChunk, + body, + allow_no_indices: true, + } as estypes.IndicesPutMappingRequest); + } } }; @@ -152,7 +182,7 @@ const addIndexAliases = async ({ index: string; aadIndexAliasName: string; }) => { - const { body: indices } = await esClient.indices.getAlias({ name: index }); + const { body: indices } = await esClient.indices.getAlias({ index: `${index}-*`, name: index }); const aliasActions = { actions: Object.keys(indices).map((concreteIndexName) => { return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts index 242c78ceed28b7..3b1b27176ef88f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts @@ -5,12 +5,16 @@ * 2.0. */ -import { transformError } from '@kbn/securitysolution-es-utils'; +import { transformError, getBootstrapIndexExists } from '@kbn/securitysolution-es-utils'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; import { RuleDataPluginService } from '../../../../../../rule_registry/server'; +import { fieldAliasesOutdated } from './check_template_version'; +import { getIndexVersion } from './get_index_version'; +import { isOutdated } from '../../migrations/helpers'; +import { SIGNALS_TEMPLATE_VERSION } from './get_signals_template'; export const readIndexRoute = ( router: SecuritySolutionPluginRouter, @@ -29,6 +33,7 @@ export const readIndexRoute = ( try { const siemClient = context.securitySolution?.getAppClient(); + const esClient = context.core.elasticsearch.client.asCurrentUser; if (!siemClient) { return siemResponse.error({ statusCode: 404 }); @@ -37,12 +42,47 @@ export const readIndexRoute = ( const spaceId = context.securitySolution.getSpaceId(); const indexName = ruleDataService.getResourceName(`security.alerts-${spaceId}`); - return response.ok({ - body: { - name: indexName, - index_mapping_outdated: false, - }, - }); + const index = siemClient.getSignalsIndex(); + const indexExists = await getBootstrapIndexExists( + context.core.elasticsearch.client.asInternalUser, + index + ); + + if (indexExists) { + let mappingOutdated: boolean | null = null; + let aliasesOutdated: boolean | null = null; + try { + const indexVersion = await getIndexVersion(esClient, index); + mappingOutdated = isOutdated({ + current: indexVersion, + target: SIGNALS_TEMPLATE_VERSION, + }); + aliasesOutdated = await fieldAliasesOutdated(esClient, index); + } catch (err) { + const error = transformError(err); + // Some users may not have the view_index_metadata permission necessary to check the index mapping version + // so just continue and return null for index_mapping_outdated if the error is a 403 + if (error.statusCode !== 403) { + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + return response.ok({ + body: { + name: indexName, + index_mapping_outdated: mappingOutdated || aliasesOutdated, + }, + }); + } else { + return response.ok({ + body: { + name: indexName, + index_mapping_outdated: false, + }, + }); + } } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 010c4b27507bb7..a9f5938abb921d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -10,7 +10,7 @@ import { getEmptyFindResult, getAlertMock, getCreateRequest, - getRuleExecutionStatuses, + getRuleExecutionStatusSucceeded, getFindResultWithSingleHit, createMlRuleRequest, getBasicEmptySearchResponse, @@ -43,7 +43,9 @@ describe.each([ clients.rulesClient.create.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); // creation succeeds - clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); // needed to transform: ; + clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue( + getRuleExecutionStatusSucceeded() + ); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) 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 9e03e5f8f2143d..71d453809d0fa5 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 @@ -106,14 +106,13 @@ export const createRulesRoute = ( await rulesClient.muteAll({ id: createdRule.id }); } - const ruleStatuses = await context.securitySolution.getExecutionLogClient().find({ - logsCount: 1, + const ruleStatus = await context.securitySolution.getExecutionLogClient().getCurrentStatus({ ruleId: createdRule.id, spaceId: context.securitySolution.getSpaceId(), }); const [validated, errors] = newTransformValidate( createdRule, - ruleStatuses[0], + ruleStatus, isRuleRegistryEnabled ); if (errors != null) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 6aecfff1178bc7..054238cf6fa45d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -80,21 +80,19 @@ export const deleteRulesBulkRoute = ( return getIdBulkError({ id, ruleId }); } - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 6, + const ruleStatus = await ruleStatusClient.getCurrentStatus({ ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); await deleteRules({ + ruleId: rule.id, rulesClient, ruleStatusClient, - ruleStatuses, - id: rule.id, }); return transformValidateBulkError( idOrRuleIdOrUnknown, rule, - ruleStatuses, + ruleStatus, isRuleRegistryEnabled ); } catch (err) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts index 466012a045eb3a..9c126a177eeb59 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts @@ -12,7 +12,7 @@ import { getDeleteRequest, getFindResultWithSingleHit, getDeleteRequestById, - getRuleExecutionStatuses, + getRuleExecutionStatusSucceeded, getEmptySavedObjectsResponse, } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../__mocks__'; @@ -32,7 +32,9 @@ describe.each([ clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); - clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); + clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue( + getRuleExecutionStatusSucceeded() + ); deleteRulesRoute(server.router, isRuleRegistryEnabled); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts index 77b8dd6fc5b543..abcf0d07a33b68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -62,18 +62,16 @@ export const deleteRulesRoute = ( }); } - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 6, + const currentStatus = await ruleStatusClient.getCurrentStatus({ ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); await deleteRules({ + ruleId: rule.id, rulesClient, ruleStatusClient, - ruleStatuses, - id: rule.id, }); - const transformed = transform(rule, ruleStatuses[0], isRuleRegistryEnabled); + const transformed = transform(rule, currentStatus, isRuleRegistryEnabled); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'failed to transform alert' }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.test.ts new file mode 100644 index 00000000000000..285b839cacb9ff --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.test.ts @@ -0,0 +1,115 @@ +/* + * 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 { INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL } from '../../../../../common/constants'; +import { + internalRuleStatusRequest, + getAlertMock, + getRuleExecutionStatusSucceeded, + getRuleExecutionStatusFailed, +} from '../__mocks__/request_responses'; +import { serverMock, requestContextMock, requestMock } from '../__mocks__'; +import { findRuleStatusInternalRoute } from './find_rule_status_internal_route'; +import { RuleStatusResponse } from '../../rules/types'; +import { AlertExecutionStatusErrorReasons } from '../../../../../../alerting/common'; +import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; + +describe.each([ + ['Legacy', false], + ['RAC', true], +])(`${INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL} - %s`, (_, isRuleRegistryEnabled) => { + let server: ReturnType; + let { clients, context } = requestContextMock.createTools(); + + beforeEach(async () => { + server = serverMock.create(); + ({ clients, context } = requestContextMock.createTools()); + + clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue( + getRuleExecutionStatusSucceeded() + ); + clients.ruleExecutionLogClient.getLastFailures.mockResolvedValue([ + getRuleExecutionStatusFailed(), + ]); + clients.rulesClient.get.mockResolvedValue( + getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) + ); + + findRuleStatusInternalRoute(server.router); + }); + + describe('status codes with actionClient and alertClient', () => { + test('returns 200 when finding a single rule status with a valid rulesClient', async () => { + const response = await server.inject(internalRuleStatusRequest(), context); + expect(response.status).toEqual(200); + }); + + test('returns 404 if alertClient is not available on the route', async () => { + context.alerting.getRulesClient = jest.fn(); + const response = await server.inject(internalRuleStatusRequest(), context); + expect(response.status).toEqual(404); + expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); + }); + + test('catch error when status search throws error', async () => { + clients.ruleExecutionLogClient.getCurrentStatus.mockImplementation(async () => { + throw new Error('Test error'); + }); + const response = await server.inject(internalRuleStatusRequest(), context); + expect(response.status).toEqual(500); + expect(response.body).toEqual({ + message: 'Test error', + status_code: 500, + }); + }); + + test('returns success if rule status client writes an error status', async () => { + // 0. task manager tried to run the rule but couldn't, so the alerting framework + // wrote an error to the executionStatus. + const failingExecutionRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); + failingExecutionRule.executionStatus = { + status: 'error', + lastExecutionDate: failingExecutionRule.executionStatus.lastExecutionDate, + error: { + reason: AlertExecutionStatusErrorReasons.Read, + message: 'oops', + }, + }; + + // 1. getFailingRules api found a rule where the executionStatus was 'error' + clients.rulesClient.get.mockResolvedValue({ + ...failingExecutionRule, + }); + + const request = internalRuleStatusRequest(); + const { ruleId } = request.body; + + const response = await server.inject(request, context); + const responseBody: RuleStatusResponse = response.body; + const ruleStatus = responseBody[ruleId].current_status; + + expect(response.status).toEqual(200); + expect(ruleStatus?.status).toEqual('failed'); + expect(ruleStatus?.last_failure_message).toEqual('Reason: read Message: oops'); + }); + }); + + describe('request validation', () => { + test('disallows singular id query param', async () => { + const request = requestMock.create({ + method: 'post', + path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, + body: { id: ['someId'] }, + }); + const result = server.validate(request); + + expect(result.badRequest).toHaveBeenCalledWith( + 'Invalid value "undefined" supplied to "ruleId"' + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.ts new file mode 100644 index 00000000000000..6d9b371a9370c8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.ts @@ -0,0 +1,85 @@ +/* + * 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 { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL } from '../../../../../common/constants'; +import { buildSiemResponse, mergeStatuses, getFailingRules } from '../utils'; +import { + findRuleStatusSchema, + FindRuleStatusSchemaDecoded, +} from '../../../../../common/detection_engine/schemas/request/find_rule_statuses_schema'; +import { mergeAlertWithSidecarStatus } from '../../schemas/rule_converters'; + +/** + * Returns the current execution status and metrics + last five failed statuses of a given rule. + * Accepts a rule id. + * + * NOTE: This endpoint is a raw implementation of an endpoint for reading rule execution + * status and logs for a given rule (e.g. for use on the Rule Details page). It will be reworked. + * See the plan in https://github.com/elastic/kibana/pull/115574 + * + * @param router + * @returns RuleStatusResponse containing data only for the given rule (normally it contains data for N rules). + */ +export const findRuleStatusInternalRoute = (router: SecuritySolutionPluginRouter) => { + router.post( + { + path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, + validate: { + body: buildRouteValidation( + findRuleStatusSchema + ), + }, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const { ruleId } = request.body; + + const siemResponse = buildSiemResponse(response); + const rulesClient = context.alerting?.getRulesClient(); + + if (!rulesClient) { + return siemResponse.error({ statusCode: 404 }); + } + + try { + const ruleStatusClient = context.securitySolution.getExecutionLogClient(); + const spaceId = context.securitySolution.getSpaceId(); + + const [currentStatus, lastFailures, failingRules] = await Promise.all([ + ruleStatusClient.getCurrentStatus({ ruleId, spaceId }), + ruleStatusClient.getLastFailures({ ruleId, spaceId }), + getFailingRules([ruleId], rulesClient), + ]); + + const failingRule = failingRules[ruleId]; + let statuses = {}; + + if (currentStatus != null) { + const finalCurrentStatus = + failingRule != null + ? mergeAlertWithSidecarStatus(failingRule, currentStatus) + : currentStatus; + + statuses = mergeStatuses(ruleId, [finalCurrentStatus, ...lastFailures], statuses); + } + + return response.ok({ body: statuses }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index 0b0650d48872fb..9f151d1db9292a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -36,7 +36,9 @@ describe.each([ getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); - clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus()); + clients.ruleExecutionLogClient.getCurrentStatusBulk.mockResolvedValue( + getFindBulkResultStatus() + ); findRulesRoute(server.router, logger, isRuleRegistryEnabled); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts index a55a525806b171..199ef75e22f25b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts @@ -68,15 +68,14 @@ export const findRulesRoute = ( }); const alertIds = rules.data.map((rule) => rule.id); - const [ruleStatuses, ruleActions] = await Promise.all([ - execLogClient.findBulk({ + const [currentStatusesByRuleId, ruleActions] = await Promise.all([ + execLogClient.getCurrentStatusBulk({ ruleIds: alertIds, - logsCount: 1, spaceId: context.securitySolution.getSpaceId(), }), legacyGetBulkRuleActionsSavedObject({ alertIds, savedObjectsClient, logger }), ]); - const transformed = transformFindAlerts(rules, ruleStatuses, ruleActions); + const transformed = transformFindAlerts(rules, currentStatusesByRuleId, ruleActions); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts index 5d6b9810a2cdac..2286c010a0a5ae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts @@ -27,7 +27,9 @@ describe.each([ beforeEach(async () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus()); // successful status search + clients.ruleExecutionLogClient.getCurrentStatusBulk.mockResolvedValue( + getFindBulkResultStatus() + ); // successful status search clients.rulesClient.get.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); @@ -48,7 +50,7 @@ describe.each([ }); test('catch error when status search throws error', async () => { - clients.ruleExecutionLogClient.findBulk.mockImplementation(async () => { + clients.ruleExecutionLogClient.getCurrentStatusBulk.mockImplementation(async () => { throw new Error('Test error'); }); const response = await server.inject(ruleStatusRequest(), context); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts index 71ebe23f124d2c..af4f8ddbb9ec89 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts @@ -17,11 +17,16 @@ import { import { mergeAlertWithSidecarStatus } from '../../schemas/rule_converters'; /** - * Given a list of rule ids, return the current status and - * last five errors for each associated rule. + * Returns the current execution status and metrics for N rules. + * Accepts an array of rule ids. + * + * NOTE: This endpoint is used on the Rule Management page and will be reworked. + * See the plan in https://github.com/elastic/kibana/pull/115574 * * @param router - * @returns RuleStatusResponse + * @returns RuleStatusResponse containing data for N requested rules. + * RuleStatusResponse[ruleId].failures is always an empty array, because + * we don't need failure history of every rule when we render tables with rules. */ export const findRulesStatusesRoute = (router: SecuritySolutionPluginRouter) => { router.post( @@ -48,31 +53,30 @@ export const findRulesStatusesRoute = (router: SecuritySolutionPluginRouter) => const ids = body.ids; try { const ruleStatusClient = context.securitySolution.getExecutionLogClient(); - const [statusesById, failingRules] = await Promise.all([ - ruleStatusClient.findBulk({ + const [currentStatusesByRuleId, failingRules] = await Promise.all([ + ruleStatusClient.getCurrentStatusBulk({ ruleIds: ids, - logsCount: 6, spaceId: context.securitySolution.getSpaceId(), }), getFailingRules(ids, rulesClient), ]); const statuses = ids.reduce((acc, id) => { - const lastFiveErrorsForId = statusesById[id]; + const currentStatus = currentStatusesByRuleId[id]; + const failingRule = failingRules[id]; - if (lastFiveErrorsForId == null || lastFiveErrorsForId.length === 0) { + if (currentStatus == null) { return acc; } - const failingRule = failingRules[id]; + const finalCurrentStatus = + failingRule != null + ? mergeAlertWithSidecarStatus(failingRule, currentStatus) + : currentStatus; - if (failingRule != null) { - const currentStatus = mergeAlertWithSidecarStatus(failingRule, lastFiveErrorsForId[0]); - const updatedLastFiveErrorsSO = [currentStatus, ...lastFiveErrorsForId.slice(1)]; - return mergeStatuses(id, updatedLastFiveErrorsSO, acc); - } - return mergeStatuses(id, [...lastFiveErrorsForId], acc); + return mergeStatuses(id, [finalCurrentStatus], acc); }, {}); + return response.ok({ body: statuses }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts index 23779afdc5410d..86be61e8f9c99c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts @@ -53,6 +53,7 @@ describe.each([ clients.rulesClient.update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); + clients.actionsClient.getAll.mockResolvedValue([]); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); @@ -77,21 +78,6 @@ describe.each([ status_code: 500, }); }); - - test('returns 404 if alertClient is not available on the route', async () => { - context.alerting.getRulesClient = jest.fn(); - const response = await server.inject(request, context); - expect(response.status).toEqual(404); - expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); - }); - - it('returns 404 if siem client is unavailable', async () => { - const { securitySolution, ...contextWithoutSecuritySolution } = context; - // @ts-expect-error - const response = await server.inject(request, contextWithoutSecuritySolution); - expect(response.status).toEqual(404); - expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); - }); }); describe('unhappy paths', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index 3752128d3daa33..187de40d33df06 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -41,7 +41,7 @@ import { import { patchRules } from '../../rules/patch_rules'; import { legacyMigrate } from '../../rules/utils'; -import { getTupleDuplicateErrorsAndUniqueRules } from './utils'; +import { getTupleDuplicateErrorsAndUniqueRules, getInvalidConnectors } from './utils'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { HapiReadableStream } from '../../rules/types'; @@ -78,14 +78,11 @@ export const importRulesRoute = ( const siemResponse = buildSiemResponse(response); try { - const rulesClient = context.alerting?.getRulesClient(); + const rulesClient = context.alerting.getRulesClient(); + const actionsClient = context.actions.getActionsClient(); const esClient = context.core.elasticsearch.client; const savedObjectsClient = context.core.savedObjects.client; - const siemClient = context.securitySolution?.getAppClient(); - - if (!siemClient || !rulesClient) { - return siemResponse.error({ statusCode: 404 }); - } + const siemClient = context.securitySolution.getAppClient(); const mlAuthz = buildMlAuthz({ license: context.licensing.license, @@ -103,6 +100,7 @@ export const importRulesRoute = ( body: `Invalid file extension ${fileExtension}`, }); } + const signalsIndex = siemClient.getSignalsIndex(); const indexExists = await getIndexExists(esClient.asCurrentUser, signalsIndex); if (!isRuleRegistryEnabled && !indexExists) { @@ -118,14 +116,24 @@ export const importRulesRoute = ( request.body.file as HapiReadableStream, ...readStream, ]); - const [duplicateIdErrors, uniqueParsedObjects] = getTupleDuplicateErrorsAndUniqueRules( - parsedObjects, - request.query.overwrite + + const [duplicateIdErrors, parsedObjectsWithoutDuplicateErrors] = + getTupleDuplicateErrorsAndUniqueRules(parsedObjects, request.query.overwrite); + + const [nonExistentActionErrors, uniqueParsedObjects] = await getInvalidConnectors( + parsedObjectsWithoutDuplicateErrors, + actionsClient ); const chunkParseObjects = chunk(CHUNK_PARSED_OBJECT_SIZE, uniqueParsedObjects); let importRuleResponse: ImportRuleResponse[] = []; + // If we had 100% errors and no successful rule could be imported we still have to output an error. + // otherwise we would output we are success importing 0 rules. + if (chunkParseObjects.length === 0) { + importRuleResponse = [...nonExistentActionErrors, ...duplicateIdErrors]; + } + while (chunkParseObjects.length) { const batchParseObjects = chunkParseObjects.shift() ?? []; const newImportRuleResponse = await Promise.all( @@ -362,6 +370,7 @@ export const importRulesRoute = ( }, []) ); importRuleResponse = [ + ...nonExistentActionErrors, ...duplicateIdErrors, ...importRuleResponse, ...newImportRuleResponse, 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 2b514ba9110915..838bfe63782c80 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 @@ -194,12 +194,11 @@ export const patchRulesBulkRoute = ( exceptionsList, }); if (rule != null && rule.enabled != null && rule.name != null) { - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 1, + const ruleStatus = await ruleStatusClient.getCurrentStatus({ ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled); + return transformValidateBulkError(rule.id, rule, ruleStatus, isRuleRegistryEnabled); } else { return getIdBulkError({ id, ruleId }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts index 00d7180dfc9be4..fe8e4470a61cfc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts @@ -10,7 +10,7 @@ import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../mach import { buildMlAuthz } from '../../../machine_learning/authz'; import { getEmptyFindResult, - getRuleExecutionStatuses, + getRuleExecutionStatusSucceeded, getAlertMock, getPatchRequest, getFindResultWithSingleHit, @@ -46,8 +46,15 @@ describe.each([ getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); // successful update clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform - clients.savedObjectsClient.create.mockResolvedValue(getRuleExecutionStatuses()[0]); // successful transform - clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); + clients.savedObjectsClient.create.mockResolvedValue({ + type: 'my-type', + id: 'e0b86950-4e9f-11ea-bdbd-07b56aa159b3', + attributes: getRuleExecutionStatusSucceeded(), + references: [], + }); // successful transform + clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue( + getRuleExecutionStatusSucceeded() + ); patchRulesRoute(server.router, ml, isRuleRegistryEnabled); }); 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 0096cd2e381807..bb9f7e1475247f 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 @@ -195,17 +195,12 @@ export const patchRulesRoute = ( exceptionsList, }); if (rule != null && rule.enabled != null && rule.name != null) { - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 1, + const ruleStatus = await ruleStatusClient.getCurrentStatus({ ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - const [validated, errors] = transformValidate( - rule, - ruleStatuses[0], - isRuleRegistryEnabled - ); + const [validated, errors] = transformValidate(rule, ruleStatus, isRuleRegistryEnabled); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts index 44f2577e032b5f..251ff1e6e5f388 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 @@ -109,16 +109,10 @@ export const performBulkActionRoute = ( case BulkAction.delete: await Promise.all( rules.data.map(async (rule) => { - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 6, - ruleId: rule.id, - spaceId: context.securitySolution.getSpaceId(), - }); await deleteRules({ + ruleId: rule.id, rulesClient, ruleStatusClient, - ruleStatuses, - id: rule.id, }); }) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts index bc9fa43b56ae75..4264ca9961bd47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts @@ -16,6 +16,7 @@ import { getFindResultWithSingleHit, nonRuleFindResult, getEmptySavedObjectsResponse, + getRuleExecutionStatusSucceeded, resolveAlertMock, } from '../__mocks__/request_responses'; import { requestMock, requestContextMock, serverMock } from '../__mocks__'; @@ -37,7 +38,9 @@ describe.each([ clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform - clients.ruleExecutionLogClient.find.mockResolvedValue([]); + clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue( + getRuleExecutionStatusSucceeded() + ); clients.rulesClient.resolve.mockResolvedValue({ ...resolveAlertMock(isRuleRegistryEnabled, { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts index c3d6f09c306f03..06d0b9d8c327a6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -70,12 +70,10 @@ export const readRulesRoute = ( ruleAlertId: rule.id, logger, }); - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 1, + const currentStatus = await ruleStatusClient.getCurrentStatus({ ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - const [currentStatus] = ruleStatuses; if (currentStatus != null && rule.executionStatus.status === 'error') { currentStatus.attributes.lastFailureMessage = `Reason: ${rule.executionStatus.error?.reason} Message: ${rule.executionStatus.error?.message}`; currentStatus.attributes.lastFailureAt = 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 d8b7e8cb2b7241..80b77722e79b0d 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 @@ -78,7 +78,7 @@ export const updateRulesBulkRoute = ( id: payloadRule.id, }); - await legacyMigrate({ + const migratedRule = await legacyMigrate({ rulesClient, savedObjectsClient, rule: existingRule, @@ -88,18 +88,18 @@ export const updateRulesBulkRoute = ( spaceId: context.securitySolution.getSpaceId(), rulesClient, ruleStatusClient, - savedObjectsClient, defaultOutputIndex: siemClient.getSignalsIndex(), + existingRule, + migratedRule, ruleUpdate: payloadRule, isRuleRegistryEnabled, }); if (rule != null) { - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 1, + const ruleStatus = await ruleStatusClient.getCurrentStatus({ ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled); + return transformValidateBulkError(rule.id, rule, ruleStatus, isRuleRegistryEnabled); } else { return getIdBulkError({ id: payloadRule.id, ruleId: payloadRule.rule_id }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index 37df792b421b06..131015880053c2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -12,6 +12,7 @@ import { getAlertMock, getUpdateRequest, getFindResultWithSingleHit, + getRuleExecutionStatusSucceeded, nonRuleFindResult, typicalMlRulePayload, } from '../__mocks__/request_responses'; @@ -43,8 +44,11 @@ describe.each([ clients.rulesClient.update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); // successful update - clients.ruleExecutionLogClient.find.mockResolvedValue([]); // successful transform: ; + clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue( + getRuleExecutionStatusSucceeded() + ); clients.appClient.getSignalsIndex.mockReturnValue('.siem-signals-test-index'); + updateRulesRoute(server.router, ml, 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 cf443e3293510b..1aad28d110bd9d 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 @@ -69,7 +69,7 @@ export const updateRulesRoute = ( id: request.body.id, }); - await legacyMigrate({ + const migratedRule = await legacyMigrate({ rulesClient, savedObjectsClient, rule: existingRule, @@ -79,22 +79,18 @@ export const updateRulesRoute = ( isRuleRegistryEnabled, rulesClient, ruleStatusClient, - savedObjectsClient, + existingRule, + migratedRule, ruleUpdate: request.body, spaceId: context.securitySolution.getSpaceId(), }); if (rule != null) { - const ruleStatuses = await ruleStatusClient.find({ - logsCount: 1, + const ruleStatus = await ruleStatusClient.getCurrentStatus({ ruleId: rule.id, spaceId: context.securitySolution.getSpaceId(), }); - const [validated, errors] = transformValidate( - rule, - ruleStatuses[0], - isRuleRegistryEnabled - ); + const [validated, errors] = transformValidate(rule, ruleStatus, isRuleRegistryEnabled); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index 366ae607f0ba8f..2dfc98fd3ba2f4 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 @@ -18,6 +18,7 @@ import { transformAlertsToRules, getDuplicates, getTupleDuplicateErrorsAndUniqueRules, + getInvalidConnectors, } from './utils'; import { getAlertMock } from '../__mocks__/request_responses'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; @@ -36,6 +37,8 @@ import { getQueryRuleParams, getThreatRuleParams, } from '../../schemas/rule_schemas.mock'; +import { requestContextMock } from '../__mocks__'; + // eslint-disable-next-line no-restricted-imports import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; // eslint-disable-next-line no-restricted-imports @@ -47,6 +50,8 @@ describe.each([ ['Legacy', false], ['RAC', true], ])('utils - %s', (_, isRuleRegistryEnabled) => { + const { clients } = requestContextMock.createTools(); + describe('transformAlertToRule', () => { test('should work with a full data set', () => { const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); @@ -529,6 +534,7 @@ describe.each([ describe('getTupleDuplicateErrorsAndUniqueRules', () => { test('returns tuple of empty duplicate errors array and rule array with instance of Syntax Error when imported rule contains parse error', async () => { + // This is a string because we have a double "::" below to make an error happen on purpose. const multipartPayload = '{"name"::"Simple Rule Query","description":"Simple Rule Query","risk_score":1,"rule_id":"rule-1","severity":"high","type":"query","query":"user.name: root or user.name: admin"}\n'; const ndJsonStream = new Readable({ @@ -645,4 +651,469 @@ describe.each([ expect(errors.length).toEqual(0); }); }); + + describe('getInvalidConnectors', () => { + beforeEach(() => { + clients.actionsClient.getAll.mockReset(); + }); + + test('returns empty errors array and rule array with instance of Syntax Error when imported rule contains parse error', async () => { + // This is a string because we have a double "::" below to make an error happen on purpose. + const multipartPayload = + '{"name"::"Simple Rule Query","description":"Simple Rule Query","risk_score":1,"rule_id":"rule-1","severity":"high","type":"query","query":"user.name: root or user.name: admin"}\n'; + const ndJsonStream = new Readable({ + read() { + this.push(multipartPayload); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + const isInstanceOfError = output[0] instanceof Error; + + expect(isInstanceOfError).toEqual(true); + expect(errors.length).toEqual(0); + }); + + test('creates error with a rule has an action that does not exist within the actions client', async () => { + const rule: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + ], + }; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + expect(output.length).toEqual(0); + expect(errors).toEqual([ + { + error: { + message: '1 connector is missing. Connector id missing is: 123', + status_code: 404, + }, + rule_id: 'rule-1', + }, + ]); + }); + + test('creates output with no errors if 1 rule with an action exists within the actions client', async () => { + const rule: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + ], + }; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([ + { + id: '123', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + ]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + expect(errors.length).toEqual(0); + expect(output.length).toEqual(1); + expect(output[0]).toEqual(expect.objectContaining(rule)); + }); + + test('creates output with no errors if 1 rule with 2 actions exists within the actions client', async () => { + const rule: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + { + group: 'default', + id: '789', + action_type_id: '101112', + params: {}, + }, + ], + }; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([ + { + id: '123', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + { + id: '789', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + ]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + expect(errors.length).toEqual(0); + expect(output.length).toEqual(1); + expect(output[0]).toEqual(expect.objectContaining(rule)); + }); + + test('creates output with no errors if 2 rules with 1 action each exists within the actions client', async () => { + const rule1: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + ], + }; + const rule2: ReturnType = { + ...getCreateRulesSchemaMock('rule-2'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + ], + }; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule1)}\n`); + this.push(`${JSON.stringify(rule2)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([ + { + id: '123', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + { + id: '789', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + ]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + expect(errors.length).toEqual(0); + expect(output.length).toEqual(2); + expect(output[0]).toEqual(expect.objectContaining(rule1)); + expect(output[1]).toEqual(expect.objectContaining(rule2)); + }); + + test('creates output with 1 error if 2 rules with 1 action each exists within the actions client but 1 has a nonexistent action', async () => { + const rule1: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + ], + }; + const rule2: ReturnType = { + ...getCreateRulesSchemaMock('rule-2'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + { + group: 'default', + id: '456', // <--- Non-existent that triggers the error. + action_type_id: '456', + params: {}, + }, + ], + }; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule1)}\n`); + this.push(`${JSON.stringify(rule2)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([ + { + id: '123', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + { + id: '789', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + ]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + expect(errors.length).toEqual(1); + expect(output.length).toEqual(1); + expect(output[0]).toEqual(expect.objectContaining(rule1)); + expect(errors).toEqual([ + { + error: { + message: '1 connector is missing. Connector id missing is: 456', + status_code: 404, + }, + rule_id: 'rule-2', + }, + ]); + }); + + test('creates output with error if 1 rule with 2 actions but 1 action does not exist within the actions client', async () => { + const rule: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + { + group: 'default', + id: '789', + action_type_id: '101112', + params: {}, + }, + { + group: 'default', + id: '101112', // <-- Does not exist + action_type_id: '101112', + params: {}, + }, + ], + }; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([ + { + id: '123', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + { + id: '789', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + ]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + expect(errors.length).toEqual(1); + expect(output.length).toEqual(0); + expect(errors).toEqual([ + { + error: { + message: '1 connector is missing. Connector id missing is: 101112', + status_code: 404, + }, + rule_id: 'rule-1', + }, + ]); + }); + + test('creates output with 2 errors if 3 rules with actions but 1 action does not exist within the actions client', async () => { + const rule1: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + { + group: 'default', + id: '789', + action_type_id: '101112', + params: {}, + }, + { + group: 'default', + id: '101112', // <-- Does not exist + action_type_id: '101112', + params: {}, + }, + ], + }; + const rule2: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + { + group: 'default', + id: '789', + action_type_id: '101112', + params: {}, + }, + ], + }; + const rule3: ReturnType = { + ...getCreateRulesSchemaMock('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + { + group: 'default', + id: '789', + action_type_id: '101112', + params: {}, + }, + { + group: 'default', + id: '101112', // <-- Does not exist + action_type_id: '101112', + params: {}, + }, + ], + }; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule1)}\n`); + this.push(`${JSON.stringify(rule2)}\n`); + this.push(`${JSON.stringify(rule3)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + clients.actionsClient.getAll.mockResolvedValue([ + { + id: '123', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + { + id: '789', + referencedByCount: 1, + actionTypeId: 'default', + name: 'name', + isPreconfigured: false, + }, + ]); + const [errors, output] = await getInvalidConnectors(parsedObjects, clients.actionsClient); + expect(errors.length).toEqual(2); + expect(output.length).toEqual(1); + expect(output[0]).toEqual(expect.objectContaining(rule2)); + expect(errors).toEqual([ + { + error: { + message: '1 connector is missing. Connector id missing is: 101112', + status_code: 404, + }, + rule_id: 'rule-1', + }, + { + error: { + message: '1 connector is missing. Connector id missing is: 101112', + status_code: 404, + }, + rule_id: 'rule-1', + }, + ]); + }); + }); }); 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 bb2e35d189ca1e..e706a3c9149745 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 @@ -6,19 +6,18 @@ */ import { countBy } from 'lodash/fp'; -import { SavedObject } from 'kibana/server'; import uuid from 'uuid'; import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { PartialAlert, FindResult } from '../../../../../../alerting/server'; +import { ActionsClient } from '../../../../../../actions/server'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, isAlertType, - IRuleSavedAttributesSavedObjectAttributes, - isRuleStatusSavedObjectType, + isRuleStatusSavedObjectAttributes, IRuleStatusSOAttributes, } from '../../rules/types'; import { createBulkErrorObject, BulkError, OutputError } from '../utils'; @@ -97,10 +96,10 @@ export const transformTags = (tags: string[]): string[] => { // those on the export export const transformAlertToRule = ( alert: SanitizedAlert, - ruleStatus?: SavedObject, + ruleStatus?: IRuleStatusSOAttributes, legacyRuleActions?: LegacyRulesActionsSavedObject | null ): Partial => { - return internalRuleToAPIResponse(alert, ruleStatus?.attributes, legacyRuleActions); + return internalRuleToAPIResponse(alert, ruleStatus, legacyRuleActions); }; export const transformAlertsToRules = ( @@ -112,7 +111,7 @@ export const transformAlertsToRules = ( export const transformFindAlerts = ( findResults: FindResult, - ruleStatuses: { [key: string]: IRuleStatusSOAttributes[] | undefined }, + currentStatusesByRuleId: { [key: string]: IRuleStatusSOAttributes | undefined }, legacyRuleActions: Record ): { page: number; @@ -125,8 +124,7 @@ export const transformFindAlerts = ( perPage: findResults.perPage, total: findResults.total, data: findResults.data.map((alert) => { - const statuses = ruleStatuses[alert.id]; - const status = statuses ? statuses[0] : undefined; + const status = currentStatusesByRuleId[alert.id]; return internalRuleToAPIResponse(alert, status, legacyRuleActions[alert.id]); }), }; @@ -134,14 +132,14 @@ export const transformFindAlerts = ( export const transform = ( alert: PartialAlert, - ruleStatus?: SavedObject, + ruleStatus?: IRuleStatusSOAttributes, isRuleRegistryEnabled?: boolean, legacyRuleActions?: LegacyRulesActionsSavedObject | null ): Partial | null => { if (isAlertType(isRuleRegistryEnabled ?? false, alert)) { return transformAlertToRule( alert, - isRuleStatusSavedObjectType(ruleStatus) ? ruleStatus : undefined, + isRuleStatusSavedObjectAttributes(ruleStatus) ? ruleStatus : undefined, legacyRuleActions ); } @@ -194,3 +192,57 @@ export const getTupleDuplicateErrorsAndUniqueRules = ( return [Array.from(errors.values()), Array.from(rulesAcc.values())]; }; + +/** + * Given a set of rules and an actions client this will return connectors that are invalid + * such as missing connectors and filter out the rules that have invalid connectors. + * @param rules The rules to check for invalid connectors + * @param actionsClient The actions client to get all the connectors. + * @returns An array of connector errors if it found any and then the promise stream of valid and invalid connectors. + */ +export const getInvalidConnectors = async ( + rules: PromiseFromStreams[], + actionsClient: ActionsClient +): Promise<[BulkError[], PromiseFromStreams[]]> => { + const actionsFind = await actionsClient.getAll(); + const actionIds = new Set(actionsFind.map((action) => action.id)); + const { errors, rulesAcc } = rules.reduce( + (acc, parsedRule) => { + if (parsedRule instanceof Error) { + acc.rulesAcc.set(uuid.v4(), parsedRule); + } else { + const { rule_id: ruleId, actions } = parsedRule; + const missingActionIds = actions.flatMap((action) => { + if (!actionIds.has(action.id)) { + return [action.id]; + } else { + return []; + } + }); + if (missingActionIds.length === 0) { + acc.rulesAcc.set(ruleId, parsedRule); + } else { + const errorMessage = + missingActionIds.length > 1 + ? 'connectors are missing. Connector ids missing are:' + : 'connector is missing. Connector id missing is:'; + acc.errors.set( + uuid.v4(), + createBulkErrorObject({ + ruleId, + statusCode: 404, + message: `${missingActionIds.length} ${errorMessage} ${missingActionIds.join(', ')}`, + }) + ); + } + } + return acc; + }, // using map (preserves ordering) + { + errors: new Map(), + rulesAcc: new Map(), + } + ); + + return [Array.from(errors.values()), Array.from(rulesAcc.values())]; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index a7ba1ac77b7bf7..032988bcca8be1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -8,7 +8,7 @@ import { transformValidate, transformValidateBulkError } from './validate'; import { BulkError } from '../utils'; import { RulesSchema } from '../../../../../common/detection_engine/schemas/response'; -import { getAlertMock, getRuleExecutionStatuses } from '../__mocks__/request_responses'; +import { getAlertMock, getRuleExecutionStatusSucceeded } from '../__mocks__/request_responses'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; @@ -121,12 +121,12 @@ describe.each([ }); test('it should do a validation correctly of a rule id with ruleStatus passed in', () => { - const ruleStatuses = getRuleExecutionStatuses(); + const ruleStatus = getRuleExecutionStatusSucceeded(); const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); const validatedOrError = transformValidateBulkError( 'rule-1', ruleAlert, - ruleStatuses, + ruleStatus, isRuleRegistryEnabled ); const expected: RulesSchema = { 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 307b6c96da3e52..d4bb020cfb6728 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 @@ -5,8 +5,6 @@ * 2.0. */ -import { SavedObject, SavedObjectsFindResult } from 'kibana/server'; - import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { FullResponseSchema, @@ -19,9 +17,8 @@ import { import { PartialAlert } from '../../../../../../alerting/server'; import { isAlertType, - IRuleSavedAttributesSavedObjectAttributes, IRuleStatusSOAttributes, - isRuleStatusSavedObjectType, + isRuleStatusSavedObjectAttributes, } from '../../rules/types'; import { createBulkErrorObject, BulkError } from '../utils'; import { transform, transformAlertToRule } from './utils'; @@ -31,7 +28,7 @@ import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rul export const transformValidate = ( alert: PartialAlert, - ruleStatus?: SavedObject, + ruleStatus?: IRuleStatusSOAttributes, isRuleRegistryEnabled?: boolean, legacyRuleActions?: LegacyRulesActionsSavedObject | null ): [RulesSchema | null, string | null] => { @@ -45,7 +42,7 @@ export const transformValidate = ( export const newTransformValidate = ( alert: PartialAlert, - ruleStatus?: SavedObject, + ruleStatus?: IRuleStatusSOAttributes, isRuleRegistryEnabled?: boolean, legacyRuleActions?: LegacyRulesActionsSavedObject | null ): [FullResponseSchema | null, string | null] => { @@ -60,12 +57,12 @@ export const newTransformValidate = ( export const transformValidateBulkError = ( ruleId: string, alert: PartialAlert, - ruleStatus?: Array>, + ruleStatus?: IRuleStatusSOAttributes, isRuleRegistryEnabled?: boolean ): RulesSchema | BulkError => { if (isAlertType(isRuleRegistryEnabled ?? false, alert)) { - if (ruleStatus && ruleStatus?.length > 0 && isRuleStatusSavedObjectType(ruleStatus[0])) { - const transformed = transformAlertToRule(alert, ruleStatus[0]); + if (ruleStatus && isRuleStatusSavedObjectAttributes(ruleStatus)) { + const transformed = transformAlertToRule(alert, ruleStatus); 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_execution_log/__mocks__/rule_execution_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/__mocks__/rule_execution_log_client.ts index 910e1ecaa508fd..518e4aa903d957 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/__mocks__/rule_execution_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/__mocks__/rule_execution_log_client.ts @@ -11,8 +11,13 @@ export const ruleExecutionLogClientMock = { create: (): jest.Mocked => ({ find: jest.fn(), findBulk: jest.fn(), - update: jest.fn(), - delete: jest.fn(), + + getLastFailures: jest.fn(), + getCurrentStatus: jest.fn(), + getCurrentStatusBulk: jest.fn(), + + deleteCurrentStatus: jest.fn(), + logStatusChange: jest.fn(), logExecutionMetrics: jest.fn(), }), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts index a3fb50f1f6b0b9..e5660da8d4cf47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts @@ -7,18 +7,25 @@ import { sum } from 'lodash'; import { SavedObjectsClientContract } from '../../../../../../../../src/core/server'; -import { IEventLogService } from '../../../../../../event_log/server'; +import { IEventLogClient, IEventLogService } from '../../../../../../event_log/server'; +import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { IRuleStatusSOAttributes } from '../../rules/types'; import { SavedObjectsAdapter } from '../saved_objects_adapter/saved_objects_adapter'; import { FindBulkExecutionLogArgs, FindExecutionLogArgs, + GetCurrentStatusArgs, + GetCurrentStatusBulkArgs, + GetCurrentStatusBulkResult, + GetLastFailuresArgs, IRuleExecutionLogClient, LogExecutionMetricsArgs, LogStatusChangeArgs, - UpdateExecutionLogArgs, } from '../types'; import { EventLogClient } from './event_log_client'; +const MAX_LAST_FAILURES = 5; + export class EventLogAdapter implements IRuleExecutionLogClient { private eventLogClient: EventLogClient; /** @@ -28,38 +35,46 @@ export class EventLogAdapter implements IRuleExecutionLogClient { */ private savedObjectsAdapter: IRuleExecutionLogClient; - constructor(eventLogService: IEventLogService, savedObjectsClient: SavedObjectsClientContract) { - this.eventLogClient = new EventLogClient(eventLogService); + constructor( + eventLogService: IEventLogService, + eventLogClient: IEventLogClient | undefined, + savedObjectsClient: SavedObjectsClientContract + ) { + this.eventLogClient = new EventLogClient(eventLogService, eventLogClient); this.savedObjectsAdapter = new SavedObjectsAdapter(savedObjectsClient); } + /** @deprecated */ public async find(args: FindExecutionLogArgs) { return this.savedObjectsAdapter.find(args); } + /** @deprecated */ public async findBulk(args: FindBulkExecutionLogArgs) { return this.savedObjectsAdapter.findBulk(args); } - public async update(args: UpdateExecutionLogArgs) { - const { attributes, spaceId, ruleId, ruleName, ruleType } = args; + public getLastFailures(args: GetLastFailuresArgs): Promise { + const { ruleId } = args; + return this.eventLogClient.getLastStatusChanges({ + ruleId, + count: MAX_LAST_FAILURES, + includeStatuses: [RuleExecutionStatus.failed], + }); + } - await this.savedObjectsAdapter.update(args); + public getCurrentStatus( + args: GetCurrentStatusArgs + ): Promise { + return this.savedObjectsAdapter.getCurrentStatus(args); + } - // EventLog execution events are immutable, so we just log a status change istead of updating previous - if (attributes.status) { - this.eventLogClient.logStatusChange({ - ruleName, - ruleType, - ruleId, - newStatus: attributes.status, - spaceId, - }); - } + public getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise { + return this.savedObjectsAdapter.getCurrentStatusBulk(args); } - public async delete(id: string) { - await this.savedObjectsAdapter.delete(id); + public async deleteCurrentStatus(ruleId: string): Promise { + await this.savedObjectsAdapter.deleteCurrentStatus(ruleId); // EventLog execution events are immutable, nothing to do here } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts index d85c67e4220350..6ce9d3d1c26ee0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts @@ -7,11 +7,14 @@ import { SavedObjectsUtils } from '../../../../../../../../src/core/server'; import { + IEventLogClient, IEventLogger, IEventLogService, SAVED_OBJECT_REL_PRIMARY, } from '../../../../../../event_log/server'; import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { invariant } from '../../../../../common/utils/invariant'; +import { IRuleStatusSOAttributes } from '../../rules/types'; import { LogStatusChangeArgs } from '../types'; import { RuleExecutionLogAction, @@ -21,6 +24,8 @@ import { const spaceIdToNamespace = SavedObjectsUtils.namespaceStringToId; +const now = () => new Date().toISOString(); + const statusSeverityDict: Record = { [RuleExecutionStatus.succeeded]: 0, [RuleExecutionStatus['going to run']]: 10, @@ -29,13 +34,6 @@ const statusSeverityDict: Record = { [RuleExecutionStatus.failed]: 30, }; -interface FindExecutionLogArgs { - ruleIds: string[]; - spaceId: string; - logsCount?: number; - statuses?: RuleExecutionStatus[]; -} - interface LogExecutionMetricsArgs { ruleId: string; ruleName: string; @@ -50,24 +48,88 @@ interface EventLogExecutionMetrics { executionGapDuration?: number; } +interface GetLastStatusChangesArgs { + ruleId: string; + count: number; + includeStatuses?: RuleExecutionStatus[]; +} + interface IExecLogEventLogClient { - find: (args: FindExecutionLogArgs) => Promise<{}>; + getLastStatusChanges(args: GetLastStatusChangesArgs): Promise; logStatusChange: (args: LogStatusChangeArgs) => void; logExecutionMetrics: (args: LogExecutionMetricsArgs) => void; } export class EventLogClient implements IExecLogEventLogClient { + private readonly eventLogClient: IEventLogClient | undefined; + private readonly eventLogger: IEventLogger; private sequence = 0; - private eventLogger: IEventLogger; - constructor(eventLogService: IEventLogService) { + constructor(eventLogService: IEventLogService, eventLogClient: IEventLogClient | undefined) { + this.eventLogClient = eventLogClient; this.eventLogger = eventLogService.getLogger({ event: { provider: RULE_EXECUTION_LOG_PROVIDER }, }); } - public async find({ ruleIds, spaceId, statuses, logsCount = 1 }: FindExecutionLogArgs) { - return {}; // TODO implement + public async getLastStatusChanges( + args: GetLastStatusChangesArgs + ): Promise { + if (!this.eventLogClient) { + throw new Error('Querying Event Log from a rule executor is not supported at this moment'); + } + + const soType = ALERT_SAVED_OBJECT_TYPE; + const soIds = [args.ruleId]; + const count = args.count; + const includeStatuses = (args.includeStatuses ?? []).map((status) => `"${status}"`); + + const filterBy: string[] = [ + `event.provider: ${RULE_EXECUTION_LOG_PROVIDER}`, + 'event.kind: event', + `event.action: ${RuleExecutionLogAction['status-change']}`, + includeStatuses.length > 0 + ? `kibana.alert.rule.execution.status:${includeStatuses.join(' ')}` + : '', + ]; + + const kqlFilter = filterBy + .filter(Boolean) + .map((item) => `(${item})`) + .join(' and '); + + const findResult = await this.eventLogClient.findEventsBySavedObjectIds(soType, soIds, { + page: 1, + per_page: count, + sort_field: '@timestamp', + sort_order: 'desc', + filter: kqlFilter, + }); + + return findResult.data.map((event) => { + invariant(event, 'Event not found'); + invariant(event['@timestamp'], 'Required "@timestamp" field is not found'); + + const statusDate = event['@timestamp']; + const status = event.kibana?.alert?.rule?.execution?.status as + | RuleExecutionStatus + | undefined; + const isStatusFailed = status === RuleExecutionStatus.failed; + const message = event.message ?? ''; + + return { + statusDate, + status, + lastFailureAt: isStatusFailed ? statusDate : undefined, + lastFailureMessage: isStatusFailed ? message : undefined, + lastSuccessAt: !isStatusFailed ? statusDate : undefined, + lastSuccessMessage: !isStatusFailed ? message : undefined, + lastLookBackDate: undefined, + gap: undefined, + bulkCreateTimeDurations: undefined, + searchAfterTimeDurations: undefined, + }; + }); } public logExecutionMetrics({ @@ -78,6 +140,7 @@ export class EventLogClient implements IExecLogEventLogClient { spaceId, }: LogExecutionMetricsArgs) { this.eventLogger.logEvent({ + '@timestamp': now(), rule: { id: ruleId, name: ruleName, @@ -122,6 +185,8 @@ export class EventLogClient implements IExecLogEventLogClient { spaceId, }: LogStatusChangeArgs) { this.eventLogger.logEvent({ + '@timestamp': now(), + message, rule: { id: ruleId, name: ruleName, @@ -132,7 +197,6 @@ export class EventLogClient implements IExecLogEventLogClient { action: RuleExecutionLogAction['status-change'], sequence: this.sequence++, }, - message, kibana: { alert: { rule: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts index 7ae2f179f9692d..005097ac3fd826 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts @@ -6,7 +6,8 @@ */ import { SavedObjectsClientContract } from '../../../../../../../src/core/server'; -import { IEventLogService } from '../../../../../event_log/server'; +import { IEventLogClient, IEventLogService } from '../../../../../event_log/server'; +import { IRuleStatusSOAttributes } from '../rules/types'; import { EventLogAdapter } from './event_log_adapter/event_log_adapter'; import { SavedObjectsAdapter } from './saved_objects_adapter/saved_objects_adapter'; import { @@ -15,58 +16,63 @@ import { FindExecutionLogArgs, IRuleExecutionLogClient, LogStatusChangeArgs, - UpdateExecutionLogArgs, UnderlyingLogClient, + GetLastFailuresArgs, + GetCurrentStatusArgs, + GetCurrentStatusBulkArgs, + GetCurrentStatusBulkResult, } from './types'; import { truncateMessage } from './utils/normalization'; -export interface RuleExecutionLogClientArgs { +interface ConstructorParams { + underlyingClient: UnderlyingLogClient; savedObjectsClient: SavedObjectsClientContract; eventLogService: IEventLogService; - underlyingClient: UnderlyingLogClient; + eventLogClient?: IEventLogClient; } export class RuleExecutionLogClient implements IRuleExecutionLogClient { private client: IRuleExecutionLogClient; - constructor({ - savedObjectsClient, - eventLogService, - underlyingClient, - }: RuleExecutionLogClientArgs) { + constructor(params: ConstructorParams) { + const { underlyingClient, eventLogService, eventLogClient, savedObjectsClient } = params; + switch (underlyingClient) { case UnderlyingLogClient.savedObjects: this.client = new SavedObjectsAdapter(savedObjectsClient); break; case UnderlyingLogClient.eventLog: - this.client = new EventLogAdapter(eventLogService, savedObjectsClient); + this.client = new EventLogAdapter(eventLogService, eventLogClient, savedObjectsClient); break; } } + /** @deprecated */ public find(args: FindExecutionLogArgs) { return this.client.find(args); } + /** @deprecated */ public findBulk(args: FindBulkExecutionLogArgs) { return this.client.findBulk(args); } - public async update(args: UpdateExecutionLogArgs) { - const { lastFailureMessage, lastSuccessMessage, ...restAttributes } = args.attributes; + public getLastFailures(args: GetLastFailuresArgs): Promise { + return this.client.getLastFailures(args); + } + + public getCurrentStatus( + args: GetCurrentStatusArgs + ): Promise { + return this.client.getCurrentStatus(args); + } - return this.client.update({ - ...args, - attributes: { - lastFailureMessage: truncateMessage(lastFailureMessage), - lastSuccessMessage: truncateMessage(lastSuccessMessage), - ...restAttributes, - }, - }); + public getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise { + return this.client.getCurrentStatusBulk(args); } - public async delete(id: string) { - return this.client.delete(id); + public deleteCurrentStatus(ruleId: string): Promise { + return this.client.deleteCurrentStatus(ruleId); } public async logExecutionMetrics(args: LogExecutionMetricsArgs) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts index 70db3a768fdb19..53b50bb8fe638e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { mapValues } from 'lodash'; import { SavedObject, SavedObjectReference } from 'src/core/server'; import { SavedObjectsClientContract } from '../../../../../../../../src/core/server'; import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; @@ -23,7 +24,10 @@ import { IRuleExecutionLogClient, ExecutionMetrics, LogStatusChangeArgs, - UpdateExecutionLogArgs, + GetLastFailuresArgs, + GetCurrentStatusArgs, + GetCurrentStatusBulkArgs, + GetCurrentStatusBulkResult, } from '../types'; import { assertUnreachable } from '../../../../../common'; @@ -48,26 +52,52 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { this.ruleStatusClient = ruleStatusSavedObjectsClientFactory(savedObjectsClient); } - public find({ ruleId, logsCount = 1 }: FindExecutionLogArgs) { + private findRuleStatusSavedObjects(ruleId: string, count: number) { return this.ruleStatusClient.find({ - perPage: logsCount, + perPage: count, sortField: 'statusDate', sortOrder: 'desc', ruleId, }); } + /** @deprecated */ + public find({ ruleId, logsCount = 1 }: FindExecutionLogArgs) { + return this.findRuleStatusSavedObjects(ruleId, logsCount); + } + + /** @deprecated */ public findBulk({ ruleIds, logsCount = 1 }: FindBulkExecutionLogArgs) { return this.ruleStatusClient.findBulk(ruleIds, logsCount); } - public async update({ id, attributes, ruleId }: UpdateExecutionLogArgs) { - const references: SavedObjectReference[] = [legacyGetRuleReference(ruleId)]; - await this.ruleStatusClient.update(id, attributes, { references }); + public async getLastFailures(args: GetLastFailuresArgs): Promise { + const result = await this.findRuleStatusSavedObjects(args.ruleId, MAX_RULE_STATUSES); + + // The first status is always the current one followed by 5 last failures. + // We skip the current status and return only the failures. + return result.map((so) => so.attributes).slice(1); + } + + public async getCurrentStatus( + args: GetCurrentStatusArgs + ): Promise { + const result = await this.findRuleStatusSavedObjects(args.ruleId, 1); + const currentStatusSavedObject = result[0]; + return currentStatusSavedObject?.attributes; + } + + public async getCurrentStatusBulk( + args: GetCurrentStatusBulkArgs + ): Promise { + const { ruleIds } = args; + const result = await this.ruleStatusClient.findBulk(ruleIds, 1); + return mapValues(result, (attributes = []) => attributes[0]); } - public async delete(id: string) { - await this.ruleStatusClient.delete(id); + public async deleteCurrentStatus(ruleId: string): Promise { + const statusSavedObjects = await this.findRuleStatusSavedObjects(ruleId, MAX_RULE_STATUSES); + await Promise.all(statusSavedObjects.map((so) => this.ruleStatusClient.delete(so.id))); } public async logExecutionMetrics({ ruleId, metrics }: LogExecutionMetricsArgs) { @@ -109,16 +139,12 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { private getOrCreateRuleStatuses = async ( ruleId: string ): Promise>> => { - const ruleStatuses = await this.find({ - spaceId: '', // spaceId is a required argument but it's not used by savedObjectsClient, any string would work here - ruleId, - logsCount: MAX_RULE_STATUSES, - }); - if (ruleStatuses.length > 0) { - return ruleStatuses; + const existingStatuses = await this.findRuleStatusSavedObjects(ruleId, MAX_RULE_STATUSES); + if (existingStatuses.length > 0) { + return existingStatuses; } - const newStatus = await this.createNewRuleStatus(ruleId); + const newStatus = await this.createNewRuleStatus(ruleId); return [newStatus]; }; @@ -159,7 +185,7 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { // drop oldest failures const oldStatuses = [lastStatus, ...ruleStatuses].slice(MAX_RULE_STATUSES); - await Promise.all(oldStatuses.map((status) => this.delete(status.id))); + await Promise.all(oldStatuses.map((status) => this.ruleStatusClient.delete(status.id))); return; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts index 564145cfc5d1f8..88802f9f28822e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts @@ -15,74 +15,92 @@ export enum UnderlyingLogClient { 'eventLog' = 'eventLog', } +export interface IRuleExecutionLogClient { + /** @deprecated */ + find(args: FindExecutionLogArgs): Promise>>; + /** @deprecated */ + findBulk(args: FindBulkExecutionLogArgs): Promise; + + getLastFailures(args: GetLastFailuresArgs): Promise; + getCurrentStatus(args: GetCurrentStatusArgs): Promise; + getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise; + + deleteCurrentStatus(ruleId: string): Promise; + + logStatusChange(args: LogStatusChangeArgs): Promise; + logExecutionMetrics(args: LogExecutionMetricsArgs): Promise; +} + +/** @deprecated */ export interface FindExecutionLogArgs { ruleId: string; spaceId: string; logsCount?: number; } +/** @deprecated */ export interface FindBulkExecutionLogArgs { ruleIds: string[]; spaceId: string; logsCount?: number; } -export interface ExecutionMetrics { - searchDurations?: string[]; - indexingDurations?: string[]; - /** - * @deprecated lastLookBackDate is logged only by SavedObjectsAdapter and should be removed in the future - */ - lastLookBackDate?: string; - executionGap?: Duration; +/** @deprecated */ +export interface FindBulkExecutionLogResponse { + [ruleId: string]: IRuleStatusSOAttributes[] | undefined; } -export interface LogStatusChangeArgs { +export interface GetLastFailuresArgs { ruleId: string; - ruleName: string; - ruleType: string; spaceId: string; - newStatus: RuleExecutionStatus; - message?: string; - /** - * @deprecated Use RuleExecutionLogClient.logExecutionMetrics to write metrics instead - */ - metrics?: ExecutionMetrics; } -export interface UpdateExecutionLogArgs { - id: string; - attributes: IRuleStatusSOAttributes; +export interface GetCurrentStatusArgs { ruleId: string; - ruleName: string; - ruleType: string; spaceId: string; } +export interface GetCurrentStatusBulkArgs { + ruleIds: string[]; + spaceId: string; +} + +export interface GetCurrentStatusBulkResult { + [ruleId: string]: IRuleStatusSOAttributes; +} + export interface CreateExecutionLogArgs { attributes: IRuleStatusSOAttributes; spaceId: string; } -export interface LogExecutionMetricsArgs { +export interface LogStatusChangeArgs { ruleId: string; ruleName: string; ruleType: string; spaceId: string; - metrics: ExecutionMetrics; + newStatus: RuleExecutionStatus; + message?: string; + /** + * @deprecated Use RuleExecutionLogClient.logExecutionMetrics to write metrics instead + */ + metrics?: ExecutionMetrics; } -export interface FindBulkExecutionLogResponse { - [ruleId: string]: IRuleStatusSOAttributes[] | undefined; +export interface LogExecutionMetricsArgs { + ruleId: string; + ruleName: string; + ruleType: string; + spaceId: string; + metrics: ExecutionMetrics; } -export interface IRuleExecutionLogClient { - find: ( - args: FindExecutionLogArgs - ) => Promise>>; - findBulk: (args: FindBulkExecutionLogArgs) => Promise; - update: (args: UpdateExecutionLogArgs) => Promise; - delete: (id: string) => Promise; - logStatusChange: (args: LogStatusChangeArgs) => Promise; - logExecutionMetrics: (args: LogExecutionMetricsArgs) => Promise; +export interface ExecutionMetrics { + searchDurations?: string[]; + indexingDurations?: string[]; + /** + * @deprecated lastLookBackDate is logged only by SavedObjectsAdapter and should be removed in the future + */ + lastLookBackDate?: string; + executionGap?: Duration; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts index 73029689deb196..e7ab48e807d54e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts @@ -27,12 +27,12 @@ import { TypeOfFieldMap } from '../../../../../../rule_registry/common/field_map import { SERVER_APP_ID } from '../../../../../common/constants'; import { ANCHOR_DATE } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; -import { RulesFieldMap } from '../field_maps'; +import { RulesFieldMap } from '../../../../../common/field_maps'; import { ALERT_ANCESTORS, ALERT_ORIGINAL_TIME, ALERT_ORIGINAL_EVENT, -} from '../field_maps/field_names'; +} from '../../../../../common/field_maps/field_names'; import { WrappedRACAlert } from '../types'; export const mockThresholdResults = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 5f70a5ec20bf2f..bc13a12e01ca4a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -37,6 +37,7 @@ import { bulkCreateFactory, wrapHitsFactory, wrapSequencesFactory } from './fact import { RuleExecutionLogClient, truncateMessageList } from '../rule_execution_log'; import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas'; import { scheduleThrottledNotificationActions } from '../notifications/schedule_throttle_notification_actions'; +import aadFieldConversion from '../routes/index/signal_aad_mapping.json'; /* eslint-disable complexity */ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = @@ -66,9 +67,9 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const esClient = scopedClusterClient.asCurrentUser; const ruleStatusClient = new RuleExecutionLogClient({ + underlyingClient: config.ruleExecutionLog.underlyingClient, savedObjectsClient, eventLogService, - underlyingClient: config.ruleExecutionLog.underlyingClient, }); const completeRule = { @@ -225,8 +226,9 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = refresh ); + const legacySignalFields: string[] = Object.keys(aadFieldConversion); const wrapHits = wrapHitsFactory({ - ignoreFields, + ignoreFields: [...ignoreFields, ...legacySignalFields], mergeStrategy, completeRule, spaceId, @@ -234,7 +236,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const wrapSequences = wrapSequencesFactory({ logger, - ignoreFields, + ignoreFields: [...ignoreFields, ...legacySignalFields], mergeStrategy, completeRule, spaceId, @@ -300,7 +302,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = ?.kibana_siem_app_url, }); - logger.info( + logger.debug( buildRuleMessage(`Found ${createdSignalsCount} signals for notification.`) ); @@ -351,8 +353,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = }); } - // adding this log line so we can get some information from cloud - logger.info( + logger.debug( buildRuleMessage( `[+] Finished indexing ${createdSignalsCount} ${ !isEmpty(tuples) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts index 39ee8788d3ee03..d6bad86456493e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts @@ -31,14 +31,14 @@ import { ANCHOR_DATE, } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; import { getListArrayMock } from '../../../../../../common/detection_engine/schemas/types/lists.mock'; +import { SERVER_APP_ID } from '../../../../../../common/constants'; +import { EVENT_DATASET } from '../../../../../../common/cti/constants'; import { ALERT_ANCESTORS, + ALERT_ORIGINAL_TIME, ALERT_DEPTH, ALERT_ORIGINAL_EVENT, - ALERT_ORIGINAL_TIME, -} from '../../field_maps/field_names'; -import { SERVER_APP_ID } from '../../../../../../common/constants'; -import { EVENT_DATASET } from '../../../../../../common/cti/constants'; +} from '../../../../../../common/field_maps/field_names'; type SignalDoc = SignalSourceHit & { _source: Required['_source'] & { [TIMESTAMP]: string }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts index bfd79d67bb74d2..bf07d2bb8515f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts @@ -29,14 +29,14 @@ import { isWrappedSignalHit, } from '../../../signals/utils'; import { RACAlert } from '../../types'; +import { SERVER_APP_ID } from '../../../../../../common/constants'; +import { SearchTypes } from '../../../../telemetry/types'; import { ALERT_ANCESTORS, ALERT_DEPTH, - ALERT_ORIGINAL_EVENT, ALERT_ORIGINAL_TIME, -} from '../../field_maps/field_names'; -import { SERVER_APP_ID } from '../../../../../../common/constants'; -import { SearchTypes } from '../../../../telemetry/types'; + ALERT_ORIGINAL_EVENT, +} from '../../../../../../common/field_maps/field_names'; export const generateAlertId = (alert: RACAlert) => { return createHash('sha256') diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts index 3f6d419e6ddd0e..86b57b1ed16987 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts @@ -11,15 +11,15 @@ import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils'; import { sampleDocNoSortId, sampleRuleGuid } from '../../../signals/__mocks__/es_results'; import { buildAlertGroupFromSequence } from './build_alert_group_from_sequence'; +import { SERVER_APP_ID } from '../../../../../../common/constants'; +import { getCompleteRuleMock, getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; +import { QueryRuleParams } from '../../../schemas/rule_schemas'; import { ALERT_ANCESTORS, - ALERT_BUILDING_BLOCK_TYPE, ALERT_DEPTH, + ALERT_BUILDING_BLOCK_TYPE, ALERT_GROUP_ID, -} from '../../field_maps/field_names'; -import { SERVER_APP_ID } from '../../../../../../common/constants'; -import { getCompleteRuleMock, getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; -import { QueryRuleParams } from '../../../schemas/rule_schemas'; +} from '../../../../../../common/field_maps/field_names'; const SPACE_ID = 'space'; 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 451f322f72799e..23958451800b06 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 @@ -19,12 +19,12 @@ import { EqlSequence } from '../../../../../../common/detection_engine/types'; import { generateBuildingBlockIds } from './generate_building_block_ids'; import { objectArrayIntersection } from '../../../signals/build_bulk_body'; import { BuildReasonMessage } from '../../../signals/reason_formatters'; +import { CompleteRule, RuleParams } from '../../../schemas/rule_schemas'; import { ALERT_BUILDING_BLOCK_TYPE, ALERT_GROUP_ID, ALERT_GROUP_INDEX, -} from '../../field_maps/field_names'; -import { CompleteRule, RuleParams } from '../../../schemas/rule_schemas'; +} from '../../../../../../common/field_maps/field_names'; /** * Takes N raw documents from ES that form a sequence and builds them into N+1 signals ready to be indexed - diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/generate_building_block_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/generate_building_block_ids.ts index 84e7f9e3ecef25..a603946b03d3b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/generate_building_block_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/generate_building_block_ids.ts @@ -7,8 +7,8 @@ import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import { createHash } from 'crypto'; +import { ALERT_ANCESTORS } from '../../../../../../common/field_maps/field_names'; import { Ancestor } from '../../../signals/types'; -import { ALERT_ANCESTORS } from '../../field_maps/field_names'; import { RACAlert } from '../../types'; /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts index a66703e3a50bd7..81a4af31881fb1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts @@ -52,5 +52,5 @@ export const wrapHitsFactory = }; }); - return filterDuplicateSignals(completeRule.alertId, wrappedDocs, false); + return filterDuplicateSignals(completeRule.alertId, wrappedDocs, true); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 89c01f65b4156e..d5b4d1f1eec773 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -33,9 +33,9 @@ import { WrapHits, WrapSequences, } from '../signals/types'; -import { AlertsFieldMap, RulesFieldMap } from './field_maps'; import { ExperimentalFeatures } from '../../../../common/experimental_features'; import { IEventLogService } from '../../../../../event_log/server'; +import { AlertsFieldMap, RulesFieldMap } from '../../../../common/field_maps'; export interface SecurityAlertTypeReturnValue { bulkCreateTimes: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/expand_dotted.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/expand_dotted.test.ts new file mode 100644 index 00000000000000..018220e4009375 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/expand_dotted.test.ts @@ -0,0 +1,89 @@ +/* + * 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 { expandDottedObject } from './expand_dotted'; + +describe('Expand Dotted', () => { + it('expands simple dotted fields to nested objects', () => { + const simpleDottedObj = { + 'kibana.test.1': 'the spice must flow', + 'kibana.test.2': 2, + 'kibana.test.3': null, + }; + expect(expandDottedObject(simpleDottedObj)).toEqual({ + kibana: { + test: { + 1: 'the spice must flow', + 2: 2, + 3: null, + }, + }, + }); + }); + + it('expands complex dotted fields to nested objects', () => { + const complexDottedObj = { + 'kibana.test.1': 'the spice must flow', + 'kibana.test.2': ['a', 'b', 'c', 'd'], + 'kibana.test.3': null, + 'signal.test': { + key: 'val', + }, + 'kibana.alert.ancestors': [ + { + name: 'ancestor1', + }, + { + name: 'ancestor2', + }, + ], + flat: 'yep', + }; + expect(expandDottedObject(complexDottedObj)).toEqual({ + kibana: { + alert: { + ancestors: [ + { + name: 'ancestor1', + }, + { + name: 'ancestor2', + }, + ], + }, + test: { + 1: 'the spice must flow', + 2: ['a', 'b', 'c', 'd'], + 3: null, + }, + }, + signal: { + test: { + key: 'val', + }, + }, + flat: 'yep', + }); + }); + + it('expands non dotted field without changing it other than reference', () => { + const simpleDottedObj = { + test: { value: '123' }, + }; + expect(expandDottedObject(simpleDottedObj)).toEqual(simpleDottedObj); + }); + + it('expands empty object without changing it other than reference', () => { + const simpleDottedObj = {}; + expect(expandDottedObject(simpleDottedObj)).toEqual(simpleDottedObj); + }); + + it('if we allow arrays as a type, it should not touch them', () => { + const simpleDottedObj: string[] = ['hello']; + expect(expandDottedObject(simpleDottedObj)).toEqual(simpleDottedObj); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/expand_dotted.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/expand_dotted.ts new file mode 100644 index 00000000000000..f90f589486ff50 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/expand_dotted.ts @@ -0,0 +1,55 @@ +/* + * 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 { merge } from '@kbn/std'; + +const expandDottedField = (dottedFieldName: string, val: unknown): object => { + const parts = dottedFieldName.split('.'); + if (parts.length === 1) { + return { [parts[0]]: val }; + } else { + return { [parts[0]]: expandDottedField(parts.slice(1).join('.'), val) }; + } +}; + +/* + * Expands an object with "dotted" fields to a nested object with unflattened fields. + * + * Example: + * expandDottedObject({ + * "kibana.alert.depth": 1, + * "kibana.alert.ancestors": [{ + * id: "d5e8eb51-a6a0-456d-8a15-4b79bfec3d71", + * type: "event", + * index: "signal_index", + * depth: 0, + * }], + * }) + * + * => { + * kibana: { + * alert: { + * ancestors: [ + * id: "d5e8eb51-a6a0-456d-8a15-4b79bfec3d71", + * type: "event", + * index: "signal_index", + * depth: 0, + * ], + * depth: 1, + * }, + * }, + * } + */ +export const expandDottedObject = (dottedObj: object) => { + if (Array.isArray(dottedObj)) { + return dottedObj; + } + return Object.entries(dottedObj).reduce( + (acc, [key, val]) => merge(acc, expandDottedField(key, val)), + {} + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts index 03cdcc6ef5c52f..d60a190f94d19b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts @@ -23,3 +23,6 @@ export const createResultObject = (state: TState) }; return result; }; + +export * from './expand_dotted'; +export * from './get_list_client'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts index f56d1d83eb873b..434da08791c06e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -10,6 +10,10 @@ import { createPromiseFromStreams } from '@kbn/utils'; import { createRulesStreamFromNdJson } from './create_rules_stream_from_ndjson'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; import { ImportRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { + getOutputDetailsSample, + getSampleDetailsAsNdjson, +} from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; type PromiseFromStreams = ImportRulesSchemaDecoded | Error; @@ -202,12 +206,13 @@ describe('create_rules_stream_from_ndjson', () => { test('filters the export details entry from the stream', async () => { const sample1 = getOutputSample(); const sample2 = getOutputSample(); + const details = getOutputDetailsSample({ totalCount: 1, rulesCount: 1 }); sample2.rule_id = 'rule-2'; const ndJsonStream = new Readable({ read() { this.push(getSampleAsNdjson(sample1)); this.push(getSampleAsNdjson(sample2)); - this.push('{"exported_rules_count":1,"missing_rules":[],"missing_rules_count":0}\n'); + this.push(getSampleDetailsAsNdjson(details)); this.push(null); }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts index 799412a33ffbcf..00dc6fe428ac78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts @@ -21,7 +21,6 @@ import { } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; import { parseNdjsonStrings, - filterExportedRulesCounts, filterExceptions, createLimitStream, filterExportedCounts, @@ -62,7 +61,6 @@ export const createRulesStreamFromNdJson = (ruleLimit: number) => { createSplitStream('\n'), parseNdjsonStrings(), filterExportedCounts(), - filterExportedRulesCounts(), filterExceptions(), validateRules(), createLimitStream(ruleLimit), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.test.ts index 2d82cd7f8732af..42d7f960beb22e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.test.ts @@ -6,10 +6,9 @@ */ import { rulesClientMock } from '../../../../../alerting/server/mocks'; -import { deleteRules } from './delete_rules'; -import { SavedObjectsFindResult } from '../../../../../../../src/core/server'; -import { DeleteRuleOptions, IRuleStatusSOAttributes } from './types'; import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client'; +import { deleteRules } from './delete_rules'; +import { DeleteRuleOptions } from './types'; describe('deleteRules', () => { let rulesClient: ReturnType; @@ -21,35 +20,15 @@ describe('deleteRules', () => { }); it('should delete the rule along with its actions, and statuses', async () => { - const ruleStatus: SavedObjectsFindResult = { - id: 'statusId', - type: '', - references: [], - attributes: { - statusDate: '', - lastFailureAt: null, - lastFailureMessage: null, - lastSuccessAt: null, - lastSuccessMessage: null, - status: null, - lastLookBackDate: null, - gap: null, - bulkCreateTimeDurations: null, - searchAfterTimeDurations: null, - }, - score: 0, - }; - - const rule: DeleteRuleOptions = { + const options: DeleteRuleOptions = { + ruleId: 'ruleId', rulesClient, ruleStatusClient, - id: 'ruleId', - ruleStatuses: [ruleStatus], }; - await deleteRules(rule); + await deleteRules(options); - expect(rulesClient.delete).toHaveBeenCalledWith({ id: rule.id }); - expect(ruleStatusClient.delete).toHaveBeenCalledWith(ruleStatus.id); + expect(rulesClient.delete).toHaveBeenCalledWith({ id: options.ruleId }); + expect(ruleStatusClient.deleteCurrentStatus).toHaveBeenCalledWith(options.ruleId); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts index 5003dbf0279e47..880132434f7cc8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts @@ -5,17 +5,9 @@ * 2.0. */ -import { asyncForEach } from '@kbn/std'; import { DeleteRuleOptions } from './types'; -export const deleteRules = async ({ - rulesClient, - ruleStatusClient, - ruleStatuses, - id, -}: DeleteRuleOptions) => { - await rulesClient.delete({ id }); - await asyncForEach(ruleStatuses, async (obj) => { - await ruleStatusClient.delete(obj.id); - }); +export const deleteRules = async ({ ruleId, rulesClient, ruleStatusClient }: DeleteRuleOptions) => { + await rulesClient.delete({ id: ruleId }); + await ruleStatusClient.deleteCurrentStatus(ruleId); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts index b75a1b0d80e9a0..e24da8a2ba0d48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts @@ -33,25 +33,11 @@ export const enableRule = async ({ }: EnableRuleArgs) => { await rulesClient.enable({ id: rule.id }); - const ruleCurrentStatus = await ruleStatusClient.find({ - logsCount: 1, + await ruleStatusClient.logStatusChange({ ruleId: rule.id, + ruleName: rule.name, + ruleType: rule.alertTypeId, spaceId, + newStatus: RuleExecutionStatus['going to run'], }); - - // set current status for this rule to be 'going to run' - if (ruleCurrentStatus && ruleCurrentStatus.length > 0) { - const currentStatusToDisable = ruleCurrentStatus[0]; - await ruleStatusClient.update({ - id: currentStatusToDisable.id, - ruleId: rule.id, - ruleName: rule.name, - ruleType: rule.alertTypeId, - attributes: { - ...currentStatusToDisable.attributes, - status: RuleExecutionStatus['going to run'], - }, - spaceId, - }); - } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts index 80df4c94971cc5..304582378c2f18 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts @@ -15,6 +15,10 @@ import { rulesClientMock } from '../../../../../alerting/server/mocks'; import { getExportAll } from './get_export_all'; import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock'; +import { + getOutputDetailsSampleWithExceptions, + getSampleDetailsAsNdjson, +} from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; import { getExceptionListClientMock } from '../../../../../lists/server/services/exception_lists/exception_list_client.mock'; @@ -101,8 +105,9 @@ describe.each([ exceptions_list: getListArrayMock(), }); expect(detailsJson).toEqual({ - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, + exported_exception_list_count: 1, + exported_exception_list_item_count: 1, + exported_count: 3, exported_rules_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], @@ -121,6 +126,7 @@ describe.each([ total: 0, data: [], }; + const details = getOutputDetailsSampleWithExceptions(); rulesClient.find.mockResolvedValue(findResult); @@ -133,8 +139,7 @@ describe.each([ ); expect(exports).toEqual({ rulesNdjson: '', - exportDetails: - '{"exported_rules_count":0,"missing_rules":[],"missing_rules_count":0,"exported_exception_list_count":0,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n', + exportDetails: getSampleDetailsAsNdjson(details), exceptionLists: '', }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 7aa55a8163e1a6..0e07ac4e56a9fc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -15,6 +15,10 @@ import { import { rulesClientMock } from '../../../../../alerting/server/mocks'; import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock'; +import { + getSampleDetailsAsNdjson, + getOutputDetailsSampleWithExceptions, +} from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; import { getExceptionListClientMock } from '../../../../../lists/server/services/exception_lists/exception_list_client.mock'; @@ -100,6 +104,7 @@ describe.each([ exportDetails: { exported_exception_list_count: 0, exported_exception_list_item_count: 0, + exported_count: 1, exported_rules_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], @@ -135,10 +140,13 @@ describe.each([ logger, isRuleRegistryEnabled ); + const details = getOutputDetailsSampleWithExceptions({ + missingRules: [{ rule_id: 'rule-1' }], + missingCount: 1, + }); expect(exports).toEqual({ rulesNdjson: '', - exportDetails: - '{"exported_rules_count":0,"missing_rules":[{"rule_id":"rule-1"}],"missing_rules_count":1,"exported_exception_list_count":0,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n', + exportDetails: getSampleDetailsAsNdjson(details), exceptionLists: '', }); }); 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 81295c91976443..b8f1467cffe872 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 @@ -72,7 +72,11 @@ export const getExportByObjectIds = async ( exceptionDetails ); - return { rulesNdjson, exportDetails, exceptionLists }; + return { + rulesNdjson, + exportDetails, + exceptionLists, + }; }; export const getRulesFromObjects = async ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts index 171233a8614665..e58d1b5088fcec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts @@ -20,6 +20,7 @@ describe('getExportDetailsNdjson', () => { const details = getExportDetailsNdjson([rule]); const reParsed = JSON.parse(details); expect(reParsed).toEqual({ + exported_count: 1, exported_rules_count: 1, missing_rules: [], missing_rules_count: 0, @@ -31,6 +32,7 @@ describe('getExportDetailsNdjson', () => { const details = getExportDetailsNdjson([], [missingRule]); const reParsed = JSON.parse(details); expect(reParsed).toEqual({ + exported_count: 0, exported_rules_count: 0, missing_rules: [{ rule_id: 'rule-1' }], missing_rules_count: 1, @@ -49,6 +51,7 @@ describe('getExportDetailsNdjson', () => { const details = getExportDetailsNdjson([rule1, rule2], [missingRule1, missingRule2]); const reParsed = JSON.parse(details); expect(reParsed).toEqual({ + exported_count: 2, exported_rules_count: 2, missing_rules: [missingRule1, missingRule2], missing_rules_count: 2, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts index 429bf4f2926bfd..ad6b55272a52b5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts @@ -5,18 +5,27 @@ * 2.0. */ +import type { ExportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types'; + +import { ExportRulesDetails } from '../../../../common/detection_engine/schemas/response/export_rules_details_schema'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; export const getExportDetailsNdjson = ( rules: Array>, missingRules: Array<{ rule_id: string }> = [], - extraMeta: Record = {} + exceptionDetails?: ExportExceptionDetails ): string => { - const stringified = JSON.stringify({ + const stringified: ExportRulesDetails = { + exported_count: + exceptionDetails == null + ? rules.length + : rules.length + + exceptionDetails.exported_exception_list_count + + exceptionDetails.exported_exception_list_item_count, exported_rules_count: rules.length, missing_rules: missingRules, missing_rules_count: missingRules.length, - ...extraMeta, - }); - return `${stringified}\n`; + ...exceptionDetails, + }; + return `${JSON.stringify(stringified)}\n`; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts index dd7e59c74601ce..614c0ae0a12810 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts @@ -8,15 +8,15 @@ import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import { getExceptionListClientMock } from '../../../../../lists/server/services/exception_lists/exception_list_client.mock'; +import { getDetectionsExceptionListSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_schema.mock'; +import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; + import { getRuleExceptionsForExport, getExportableExceptions, getDefaultExportDetails, } from './get_export_rule_exceptions'; -import { - getListArrayMock, - getListMock, -} from '../../../../common/detection_engine/schemas/types/lists.mock'; +import { getListMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; describe('get_export_rule_exceptions', () => { describe('getRuleExceptionsForExport', () => { @@ -36,7 +36,24 @@ describe('get_export_rule_exceptions', () => { getExceptionListClientMock() ); - expect(exportData).toEqual('exportString'); + expect(exportData).toEqual( + `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}` + ); + }); + + test('it does not return duplicate exception lists', async () => { + const { exportData } = await getRuleExceptionsForExport( + [getListMock(), getListMock()], + getExceptionListClientMock() + ); + + expect(exportData).toEqual( + `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}` + ); }); test('it does not return a global endpoint list', async () => { @@ -60,11 +77,15 @@ describe('get_export_rule_exceptions', () => { test('it returns stringified exception lists and items', async () => { // This rule has 2 exception lists tied to it const { exportData } = await getExportableExceptions( - getListArrayMock(), + [getListMock()], getExceptionListClientMock() ); - expect(exportData).toEqual('exportStringexportString'); + expect(exportData).toEqual( + `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}` + ); }); test('it throws error if error occurs in getting exceptions', async () => { @@ -72,7 +93,7 @@ describe('get_export_rule_exceptions', () => { exceptionsClient.exportExceptionListAndItems = jest.fn().mockRejectedValue(new Error('oops')); // This rule has 2 exception lists tied to it await expect(async () => { - await getExportableExceptions(getListArrayMock(), exceptionsClient); + await getExportableExceptions([getListMock()], exceptionsClient); }).rejects.toThrowErrorMatchingInlineSnapshot(`"oops"`); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts index 719649d35c0f09..6faf3fdfe6104b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts @@ -21,10 +21,17 @@ export const getRuleExceptionsForExport = async ( exceptions: ListArray, exceptionsListClient: ExceptionListClient | undefined ): Promise => { + const uniqueExceptionLists = new Set(); + if (exceptionsListClient != null) { - const exceptionsWithoutUnexportableLists = exceptions.filter( - ({ list_id: listId }) => !NON_EXPORTABLE_LIST_IDS.includes(listId) - ); + const exceptionsWithoutUnexportableLists = exceptions.filter((list) => { + if (!uniqueExceptionLists.has(list.id)) { + uniqueExceptionLists.add(list.id); + return !NON_EXPORTABLE_LIST_IDS.includes(list.list_id); + } else { + return false; + } + }); return getExportableExceptions(exceptionsWithoutUnexportableLists, exceptionsListClient); } else { return { exportData: '', exportDetails: getDefaultExportDetails() }; @@ -72,9 +79,9 @@ export const getExportableExceptions = async ( }; /** - * Creates promises of the rules and returns them. + * Creates promises of the exceptions to be exported and returns them. * @param exceptionsListClient Exception Lists client - * @param exceptions The rules to apply the update for + * @param exceptions The exceptions to be exported * @returns Promise of export ready exceptions. */ export const createPromises = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md index 1b8516ee160125..09257371bca60c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md @@ -4,7 +4,7 @@ -1. [Have the env params set up](https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/server/lib/detection_engine/README.md) +1. [Have the env params set up](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/server/lib/detection_engine/README.md) 2. Create a new timelines template into `x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines` diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson index a02951e55580c9..6922cacd17b685 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson @@ -11,4 +11,4 @@ {"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1611609999115,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} {"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"and":[{"enabled":true,"excluded":false,"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayField":"destination.ip","displayValue":"{destination.ip}","field":"destination.ip","operator":":","value":"{destination.ip}"},"type":"template"}],"enabled":true,"excluded":false,"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","kqlQuery":"","name":"{source.ip}","queryMatch":{"displayField":"source.ip","displayValue":"{source.ip}","field":"source.ip","operator":":","value":"{source.ip}"},"type":"template"}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1611609960850,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} {"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1611609848602,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} -{"savedObjectId":null,"version":null,"columns":[{"columnHeaderType":"not-filtered","id":"@timestamp"},{"columnHeaderType":"not-filtered","id":"signal.rule.description"},{"aggregatable":true,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","example":"user-password-change"},{"aggregatable":true,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"columnHeaderType":"not-filtered","id":"process.pid"},{"aggregatable":true,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip"},{"aggregatable":true,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number"},{"aggregatable":true,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip"},{"columnHeaderType":"not-filtered","id":"destination.port"},{"aggregatable":true,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","example":"albert"},{"columnHeaderType":"not-filtered","id":"host.name"}],"dataProviders":[{"excluded":false,"and":[{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.type}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.type","displayField":null,"value":"{threat.enrichments.matched.type}","operator":":"},"id":"timeline-1-ae18ef4b-f690-4122-a24d-e13b6818fba8","type":"template","enabled":true},{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.field}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.field","displayField":null,"value":"{threat.enrichments.matched.field}","operator":":"},"id":"timeline-1-7b4cf27e-6788-4d8e-9188-7687f0eba0f2","type":"template","enabled":true}],"kqlQuery":"","name":"{threat.enrichments.matched.atomic}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.atomic","displayField":null,"value":"{threat.enrichments.matched.atomic}","operator":":"},"id":"timeline-1-7db7d278-a80a-4853-971a-904319c50777","type":"template","enabled":true}],"description":"This Timeline template is for alerts generated by Indicator Match detection rules.","eqlOptions":{"eventCategoryField":"event.category","tiebreakerField":"","timestampField":"@timestamp","query":"","size":100},"eventType":"alert","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"indexNames":[".siem-signals-default"],"title":"Generic Threat Match Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"495ad7a7-316e-4544-8a0f-9c098daee76e","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":[{"sortDirection":"desc","columnId":"@timestamp"}],"created":1616696609311,"createdBy":"elastic","updated":1616788372794,"updatedBy":"elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"columnHeaderType":"not-filtered","id":"@timestamp"},{"columnHeaderType":"not-filtered","id":"signal.rule.description"},{"aggregatable":true,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","example":"user-password-change"},{"aggregatable":true,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"columnHeaderType":"not-filtered","id":"process.pid"},{"aggregatable":true,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip"},{"aggregatable":true,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number"},{"aggregatable":true,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip"},{"columnHeaderType":"not-filtered","id":"destination.port"},{"aggregatable":true,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","example":"albert"},{"columnHeaderType":"not-filtered","id":"host.name"}],"dataProviders":[{"excluded":false,"and":[{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.type}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.type","displayField":null,"value":"{threat.enrichments.matched.type}","operator":":"},"id":"timeline-1-ae18ef4b-f690-4122-a24d-e13b6818fba8","type":"template","enabled":true},{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.field}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.field","displayField":null,"value":"{threat.enrichments.matched.field}","operator":":"},"id":"timeline-1-7b4cf27e-6788-4d8e-9188-7687f0eba0f2","type":"template","enabled":true}],"kqlQuery":"","name":"{threat.enrichments.matched.atomic}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.atomic","displayField":null,"value":"{threat.enrichments.matched.atomic}","operator":":"},"id":"timeline-1-7db7d278-a80a-4853-971a-904319c50777","type":"template","enabled":true}],"description":"This Timeline template is for alerts generated by Indicator Match detection rules.","eqlOptions":{"eventCategoryField":"event.category","tiebreakerField":"","timestampField":"@timestamp","query":"","size":100},"eventType":"alert","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"dataViewId": "security-solution","indexNames":[".siem-signals-default"],"title":"Generic Threat Match Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"495ad7a7-316e-4544-8a0f-9c098daee76e","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":[{"sortDirection":"desc","columnId":"@timestamp"}],"created":1616696609311,"createdBy":"elastic","updated":1616788372794,"updatedBy":"elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/threat.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/threat.json index d777fdf17d6579..0d74cc6e436193 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/threat.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/threat.json @@ -1 +1 @@ -{"savedObjectId":null,"version":null,"columns":[{"columnHeaderType":"not-filtered","id":"@timestamp"},{"columnHeaderType":"not-filtered","id":"signal.rule.description"},{"aggregatable":true,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","example":"user-password-change"},{"aggregatable":true,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"columnHeaderType":"not-filtered","id":"process.pid"},{"aggregatable":true,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip"},{"aggregatable":true,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number"},{"aggregatable":true,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip"},{"columnHeaderType":"not-filtered","id":"destination.port"},{"aggregatable":true,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","example":"albert"},{"columnHeaderType":"not-filtered","id":"host.name"}],"dataProviders":[{"excluded":false,"and":[{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.type}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.type","displayField":null,"value":"{threat.enrichments.matched.type}","operator":":"},"id":"timeline-1-ae18ef4b-f690-4122-a24d-e13b6818fba8","type":"template","enabled":true},{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.field}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.field","displayField":null,"value":"{threat.enrichments.matched.field}","operator":":"},"id":"timeline-1-7b4cf27e-6788-4d8e-9188-7687f0eba0f2","type":"template","enabled":true}],"kqlQuery":"","name":"{threat.enrichments.matched.atomic}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.atomic","displayField":null,"value":"{threat.enrichments.matched.atomic}","operator":":"},"id":"timeline-1-7db7d278-a80a-4853-971a-904319c50777","type":"template","enabled":true}],"description":"This Timeline template is for alerts generated by Indicator Match detection rules.","eqlOptions":{"eventCategoryField":"event.category","tiebreakerField":"","timestampField":"@timestamp","query":"","size":100},"eventType":"alert","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"indexNames":[".siem-signals-default"],"title":"Generic Threat Match Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"495ad7a7-316e-4544-8a0f-9c098daee76e","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":[{"sortDirection":"desc","columnId":"@timestamp"}],"created":1616696609311,"createdBy":"elastic","updated":1616788372794,"updatedBy":"elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"columnHeaderType":"not-filtered","id":"@timestamp"},{"columnHeaderType":"not-filtered","id":"signal.rule.description"},{"aggregatable":true,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","example":"user-password-change"},{"aggregatable":true,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"columnHeaderType":"not-filtered","id":"process.pid"},{"aggregatable":true,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip"},{"aggregatable":true,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number"},{"aggregatable":true,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip"},{"columnHeaderType":"not-filtered","id":"destination.port"},{"aggregatable":true,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","example":"albert"},{"columnHeaderType":"not-filtered","id":"host.name"}],"dataProviders":[{"excluded":false,"and":[{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.type}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.type","displayField":null,"value":"{threat.enrichments.matched.type}","operator":":"},"id":"timeline-1-ae18ef4b-f690-4122-a24d-e13b6818fba8","type":"template","enabled":true},{"excluded":false,"kqlQuery":"","name":"{threat.enrichments.matched.field}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.field","displayField":null,"value":"{threat.enrichments.matched.field}","operator":":"},"id":"timeline-1-7b4cf27e-6788-4d8e-9188-7687f0eba0f2","type":"template","enabled":true}],"kqlQuery":"","name":"{threat.enrichments.matched.atomic}","queryMatch":{"displayValue":null,"field":"threat.enrichments.matched.atomic","displayField":null,"value":"{threat.enrichments.matched.atomic}","operator":":"},"id":"timeline-1-7db7d278-a80a-4853-971a-904319c50777","type":"template","enabled":true}],"description":"This Timeline template is for alerts generated by Indicator Match detection rules.","eqlOptions":{"eventCategoryField":"event.category","tiebreakerField":"","timestampField":"@timestamp","query":"","size":100},"eventType":"alert","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"dataViewId": "security-solution","indexNames":[".siem-signals-default"],"title":"Generic Threat Match Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"495ad7a7-316e-4544-8a0f-9c098daee76e","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":[{"sortDirection":"desc","columnId":"@timestamp"}],"created":1616696609311,"createdBy":"elastic","updated":1616788372794,"updatedBy":"elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} 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 dafb99c6df970b..ed0f0447ad3b00 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 @@ -8,12 +8,7 @@ import { get } from 'lodash/fp'; import { Readable } from 'stream'; -import { - SavedObject, - SavedObjectAttributes, - SavedObjectsClientContract, - SavedObjectsFindResult, -} from 'kibana/server'; +import { SavedObject, SavedObjectAttributes, SavedObjectsClientContract } from 'kibana/server'; import type { MachineLearningJobIdOrUndefined, From, @@ -207,10 +202,8 @@ export const isAlertType = ( : partialAlert.alertTypeId === SIGNALS_ID; }; -export const isRuleStatusSavedObjectType = ( - obj: unknown -): obj is SavedObject => { - return get('attributes', obj) != null; +export const isRuleStatusSavedObjectAttributes = (obj: unknown): obj is IRuleStatusSOAttributes => { + return get('status', obj) != null; }; export interface CreateRulesOptions { @@ -274,8 +267,9 @@ export interface UpdateRulesOptions { ruleStatusClient: IRuleExecutionLogClient; rulesClient: RulesClient; defaultOutputIndex: string; + existingRule: SanitizedAlert | null | undefined; + migratedRule: SanitizedAlert | null | undefined; ruleUpdate: UpdateRulesSchema; - savedObjectsClient: SavedObjectsClientContract; } export interface PatchRulesOptions { @@ -341,10 +335,9 @@ export interface ReadRuleOptions { } export interface DeleteRuleOptions { + ruleId: Id; rulesClient: RulesClient; ruleStatusClient: IRuleExecutionLogClient; - ruleStatuses: Array>; - id: Id; } export interface FindRuleOptions { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts index 9a7711fcc8987e..b98a95ed1aabc0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts @@ -12,6 +12,8 @@ import { getUpdateRulesSchemaMock, } from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client'; +import { getAlertMock } from '../routes/__mocks__/request_responses'; +import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; export const getUpdateRulesOptionsMock = (isRuleRegistryEnabled: boolean) => ({ spaceId: 'default', @@ -19,6 +21,8 @@ export const getUpdateRulesOptionsMock = (isRuleRegistryEnabled: boolean) => ({ ruleStatusClient: ruleExecutionLogClientMock.create(), savedObjectsClient: savedObjectsClientMock.create(), defaultOutputIndex: '.siem-signals-default', + existingRule: getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), + migratedRule: getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), ruleUpdate: getUpdateRulesSchemaMock(), isRuleRegistryEnabled, }); @@ -29,6 +33,8 @@ export const getUpdateMlRulesOptionsMock = (isRuleRegistryEnabled: boolean) => ( ruleStatusClient: ruleExecutionLogClientMock.create(), savedObjectsClient: savedObjectsClientMock.create(), defaultOutputIndex: '.siem-signals-default', + existingRule: getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), + migratedRule: getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), ruleUpdate: getUpdateMachineLearningSchemaMock(), isRuleRegistryEnabled, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index 4268ed9014066b..ae16d0435e3dc0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -10,13 +10,19 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { PartialAlert } from '../../../../../alerting/server'; -import { readRules } from './read_rules'; + import { UpdateRulesOptions } from './types'; import { addTags } from './add_tags'; import { typeSpecificSnakeToCamel } from '../schemas/rule_converters'; import { internalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; import { enableRule } from './enable_rule'; -import { maybeMute, transformToAlertThrottle, transformToNotifyWhen } from './utils'; +import { + maybeMute, + transformToAlertThrottle, + transformToNotifyWhen, + updateActions, + updateThrottleNotifyWhen, +} from './utils'; class UpdateError extends Error { public readonly statusCode: number; @@ -27,20 +33,14 @@ class UpdateError extends Error { } export const updateRules = async ({ - isRuleRegistryEnabled, spaceId, rulesClient, ruleStatusClient, defaultOutputIndex, + existingRule, + migratedRule, ruleUpdate, - savedObjectsClient, }: UpdateRulesOptions): Promise | null> => { - const existingRule = await readRules({ - isRuleRegistryEnabled, - rulesClient, - ruleId: ruleUpdate.rule_id, - id: ruleUpdate.id, - }); if (existingRule == null) { return null; } @@ -86,9 +86,24 @@ export const updateRules = async ({ ...typeSpecificParams, }, schedule: { interval: ruleUpdate.interval ?? '5m' }, - actions: ruleUpdate.actions != null ? ruleUpdate.actions.map(transformRuleToAlertAction) : [], - throttle: transformToAlertThrottle(ruleUpdate.throttle), - notifyWhen: transformToNotifyWhen(ruleUpdate.throttle), + actions: updateActions( + transformRuleToAlertAction, + migratedRule?.actions, + existingRule.actions, + ruleUpdate?.actions + ), + throttle: updateThrottleNotifyWhen( + transformToAlertThrottle, + migratedRule?.throttle, + existingRule.throttle, + ruleUpdate?.throttle + ), + notifyWhen: updateThrottleNotifyWhen( + transformToNotifyWhen, + migratedRule?.notifyWhen, + existingRule.notifyWhen, + ruleUpdate?.throttle + ), }; const [validated, errors] = validate(newInternalRule, internalRuleUpdate); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index a558024a73e344..c9e00486dc1304 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { pickBy, isEmpty } from 'lodash/fp'; +import { pickBy, isEmpty, isEqual } from 'lodash/fp'; import type { FromOrUndefined, MachineLearningJobIdOrUndefined, @@ -64,10 +64,14 @@ import { RulesClient } from '../../../../../alerting/server'; // eslint-disable-next-line no-restricted-imports import { LegacyRuleActions } from '../rule_actions/legacy_types'; import { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; -import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions'; +import { + transformAlertToRuleAction, + transformRuleToAlertAction, +} from '../../../../common/detection_engine/transform_actions'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectType } from '../rule_actions/legacy_saved_object_mappings'; import { LegacyMigrateParams } from './types'; +import { RuleAlertAction } from '../../../../common/detection_engine/types'; export const calculateInterval = ( interval: string | undefined, @@ -331,6 +335,10 @@ export const legacyMigrate = async ({ }), savedObjectsClient.find({ type: legacyRuleActionsSavedObjectType, + hasReference: { + type: 'alert', + id: rule.id, + }, }), ]); @@ -344,8 +352,10 @@ export const legacyMigrate = async ({ ) : null, ]); + + const { id, ...restOfRule } = rule; const migratedRule = { - ...rule, + ...restOfRule, actions: siemNotification.data[0].actions, throttle: siemNotification.data[0].schedule.interval, notifyWhen: transformToNotifyWhen(siemNotification.data[0].throttle), @@ -354,7 +364,39 @@ export const legacyMigrate = async ({ id: rule.id, data: migratedRule, }); - return migratedRule; + return { id: rule.id, ...migratedRule }; } return rule; }; + +export const updateThrottleNotifyWhen = ( + transform: typeof transformToAlertThrottle | typeof transformToNotifyWhen, + migratedRuleThrottle: string | null | undefined, + existingRuleThrottle: string | null | undefined, + ruleUpdateThrottle: string | null | undefined +) => { + if (existingRuleThrottle == null && ruleUpdateThrottle == null && migratedRuleThrottle != null) { + return migratedRuleThrottle; + } + return transform(ruleUpdateThrottle); +}; + +export const updateActions = ( + transform: typeof transformRuleToAlertAction, + migratedRuleActions: AlertAction[] | null | undefined, + existingRuleActions: AlertAction[] | null | undefined, + ruleUpdateActions: RuleAlertAction[] | null | undefined +) => { + // if the existing rule actions and the rule update actions are equivalent (aka no change) + // but the migrated actions and the ruleUpdateActions (or existing rule actions, associatively) + // are not equivalent, we know that the rules' actions were migrated and we need to apply + // that change to the update request so it is not overwritten by the rule update payload + if ( + existingRuleActions?.length === 0 && + ruleUpdateActions == null && + !isEqual(existingRuleActions, migratedRuleActions) + ) { + return migratedRuleActions; + } + return ruleUpdateActions != null ? ruleUpdateActions.map(transform) : []; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh index 4df0e42adf9f38..df2354ed8398a9 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh @@ -30,13 +30,3 @@ if [ -z "${KIBANA_URL}" ]; then echo "Set KIBANA_URL in your environment" exit 1 fi - -if [ -z "${TASK_MANAGER_INDEX}" ]; then - echo "Set TASK_MANAGER_INDEX in your environment" - exit 1 -fi - -if [ -z "${KIBANA_INDEX}" ]; then - echo "Set KIBANA_INDEX in your environment" - exit 1 -fi diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_instances.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_instances.sh index 01248d32cf0252..90c7529c298021 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_instances.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_instances.sh @@ -11,7 +11,7 @@ set -e ./check_env_variables.sh # Example: ./get_action_instances.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/actions/README.md#get-apiaction_find-find-actions +# https://github.com/elastic/kibana/blob/main/x-pack/plugins/actions/README.md#get-apiaction_find-find-actions curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X GET ${KIBANA_URL}${SPACE_URL}/api/actions \ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_types.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_types.sh index 0aa6eeb04c28e6..3b7006e02a52d8 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_types.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_action_types.sh @@ -11,7 +11,7 @@ set -e ./check_env_variables.sh # Example: ./get_action_types.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/actions/README.md +# https://github.com/elastic/kibana/blob/main/x-pack/plugins/actions/README.md curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X GET ${KIBANA_URL}${SPACE_URL}/api/actions/connector_types \ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_instances.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_instances.sh index f5df0c368300c9..f2ba9bb70a7c67 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_instances.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_instances.sh @@ -11,7 +11,7 @@ set -e ./check_env_variables.sh # Example: ./get_alert_instances.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md#get-apialert_find-find-alerts +# https://github.com/elastic/kibana/blob/main/x-pack/plugins/alerting/README.md#get-apialert_find-find-alerts curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X GET ${KIBANA_URL}${SPACE_URL}/api/alerts/_find \ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_types.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_types.sh index 46659766bce16c..9b51c289ac2c3b 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_types.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/get_alert_types.sh @@ -11,7 +11,7 @@ set -e ./check_env_variables.sh # Example: ./get_alert_types.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md#get-apialerttypes-list-alert-types +# https://github.com/elastic/kibana/blob/main/x-pack/plugins/alerting/README.md#get-apialerttypes-list-alert-types curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X GET ${KIBANA_URL}${SPACE_URL}/api/alerts/list_alert_types \ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json index 7d818977084226..e6fbef08d25ee4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json @@ -34,6 +34,8 @@ "ml": ["all"], "siem": ["all", "read_alerts", "crud_alerts"], "securitySolutionCases": ["all"], + "indexPatterns": ["all"], + "savedObjectsManagement": ["all"], "actions": ["read"], "builtInAlerts": ["all"], "dev_tools": ["all"] diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json index 34d8b7b4d44469..af12a2cb674d57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json @@ -39,6 +39,8 @@ "ml": ["read"], "siem": ["all", "read_alerts", "crud_alerts"], "securitySolutionCases": ["all"], + "indexPatterns": ["read"], + "savedObjectsManagement": ["read"], "actions": ["read"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts index bcdc4724775319..c18e23b7a3cdff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts @@ -13,3 +13,8 @@ export * from './rule_author'; export * from './soc_manager'; export * from './t1_analyst'; export * from './t2_analyst'; + +// TODO: Steph/sourcerer remove from detections_role.json once we have our internal saved object client +// https://github.com/elastic/security-team/issues/1978 +// "indexPatterns": ["read"], +// "savedObjectsManagement": ["read"], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json index f6b31d4b3ed812..18effae645c427 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json @@ -34,6 +34,8 @@ "ml": ["all"], "siem": ["all", "read_alerts", "crud_alerts"], "securitySolutionCases": ["all"], + "indexPatterns": ["all"], + "savedObjectsManagement": ["all"], "actions": ["all"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json index 4cfc6a3ec80ed1..8f9434d9a3623c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json @@ -27,6 +27,8 @@ "ml": ["read"], "siem": ["read", "read_alerts"], "securitySolutionCases": ["read"], + "indexPatterns": ["read"], + "savedObjectsManagement": ["read"], "actions": ["read"], "builtInAlerts": ["read"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json index a23aec6d6e403a..d6bee8ce9dc165 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json @@ -37,6 +37,8 @@ "ml": ["read"], "siem": ["all", "read_alerts", "crud_alerts"], "securitySolutionCases": ["all"], + "indexPatterns": ["read"], + "savedObjectsManagement": ["read"], "actions": ["read"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json index da855c69264381..46f7ca1d0067dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json @@ -37,6 +37,8 @@ "ml": ["read"], "siem": ["all", "read_alerts", "crud_alerts"], "securitySolutionCases": ["all"], + "indexPatterns": ["all"], + "savedObjectsManagement": ["all"], "actions": ["all"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json index a63d0186a2a91c..ea3bd7b97e3caf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json @@ -27,6 +27,8 @@ "ml": ["read"], "siem": ["read", "read_alerts"], "securitySolutionCases": ["read"], + "indexPatterns": ["read"], + "savedObjectsManagement": ["read"], "actions": ["read"], "builtInAlerts": ["read"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json index de1ff5af99c1bd..209e57eba2cfdd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json @@ -29,6 +29,8 @@ "ml": ["read"], "siem": ["read", "read_alerts"], "securitySolutionCases": ["read"], + "indexPatterns": ["read"], + "savedObjectsManagement": ["read"], "actions": ["read"], "builtInAlerts": ["read"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts index 155334709e980e..3db8d51ab76ede 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts @@ -104,7 +104,7 @@ export const mlExecutor = async ({ const anomalyCount = filteredAnomalyResults.hits.hits.length; if (anomalyCount) { - logger.info(buildRuleMessage(`Found ${anomalyCount} signals from ML anomalies.`)); + logger.debug(buildRuleMessage(`Found ${anomalyCount} signals from ML anomalies.`)); } const { success, errors, bulkCreateDuration, createdItemsCount, createdItems } = await bulkCreateMlSignals({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/preview_rule_execution_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/preview_rule_execution_log_client.ts index d3ccafddab6e4e..c2c1b5d7615c20 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/preview_rule_execution_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/preview_rule_execution_log_client.ts @@ -7,13 +7,16 @@ import { SavedObjectsFindResult } from 'kibana/server'; import { - LogExecutionMetricsArgs, IRuleExecutionLogClient, + LogStatusChangeArgs, + LogExecutionMetricsArgs, FindBulkExecutionLogArgs, FindBulkExecutionLogResponse, FindExecutionLogArgs, - LogStatusChangeArgs, - UpdateExecutionLogArgs, + GetLastFailuresArgs, + GetCurrentStatusArgs, + GetCurrentStatusBulkArgs, + GetCurrentStatusBulkResult, } from '../../rule_execution_log'; import { IRuleStatusSOAttributes } from '../../rules/types'; @@ -21,26 +24,50 @@ export const createWarningsAndErrors = () => { const warningsAndErrorsStore: LogStatusChangeArgs[] = []; const previewRuleExecutionLogClient: IRuleExecutionLogClient = { - async delete(id: string): Promise { - return Promise.resolve(undefined); - }, - async find( + find( args: FindExecutionLogArgs ): Promise>> { return Promise.resolve([]); }, - async findBulk(args: FindBulkExecutionLogArgs): Promise { + + findBulk(args: FindBulkExecutionLogArgs): Promise { return Promise.resolve({}); }, - async logStatusChange(args: LogStatusChangeArgs): Promise { - warningsAndErrorsStore.push(args); - return Promise.resolve(undefined); + + getLastFailures(args: GetLastFailuresArgs): Promise { + return Promise.resolve([]); }, - async update(args: UpdateExecutionLogArgs): Promise { - return Promise.resolve(undefined); + + getCurrentStatus(args: GetCurrentStatusArgs): Promise { + return Promise.resolve({ + statusDate: new Date().toISOString(), + status: null, + lastFailureAt: null, + lastFailureMessage: null, + lastSuccessAt: null, + lastSuccessMessage: null, + lastLookBackDate: null, + gap: null, + bulkCreateTimeDurations: null, + searchAfterTimeDurations: null, + }); }, - async logExecutionMetrics(args: LogExecutionMetricsArgs): Promise { - return Promise.resolve(undefined); + + getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise { + return Promise.resolve({}); + }, + + deleteCurrentStatus(ruleId: string): Promise { + return Promise.resolve(); + }, + + logStatusChange(args: LogStatusChangeArgs): Promise { + warningsAndErrorsStore.push(args); + return Promise.resolve(); + }, + + logExecutionMetrics(args: LogExecutionMetricsArgs): Promise { + return Promise.resolve(); }, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index cd301511d9ac55..85285eed2817ac 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -142,12 +142,13 @@ export const signalRulesAlertType = ({ const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE); let hasError: boolean = false; let result = createSearchAfterReturnType(); + const ruleStatusClient = ruleExecutionLogClientOverride ? ruleExecutionLogClientOverride : new RuleExecutionLogClient({ - eventLogService, - savedObjectsClient: services.savedObjectsClient, underlyingClient: config.ruleExecutionLog.underlyingClient, + savedObjectsClient: services.savedObjectsClient, + eventLogService, }); const completeRule: CompleteRule = { @@ -426,7 +427,7 @@ export const signalRulesAlertType = ({ ?.kibana_siem_app_url, }); - logger.info( + logger.debug( buildRuleMessage(`Found ${result.createdSignalsCount} signals for notification.`) ); @@ -478,8 +479,7 @@ export const signalRulesAlertType = ({ }); } - // adding this log line so we can get some information from cloud - logger.info( + logger.debug( buildRuleMessage( `[+] Finished indexing ${result.createdSignalsCount} ${ !isEmpty(tuples) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts index f029b02127b088..ff4fbb58d74936 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts @@ -723,7 +723,7 @@ describe('utils', () => { it('throws an error if the validator is called after the specified interval', async () => { const validator = buildExecutionIntervalValidator('1s'); - await new Promise((r) => setTimeout(r, 1001)); + await new Promise((r) => setTimeout(r, 2000)); expect(() => validator()).toThrowError( 'Current rule execution has exceeded its allotted interval (1s) and has been stopped.' ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.test.ts index 8362942af15b94..dbf2fb7feac2ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ALERT_ORIGINAL_TIME } from '../../rule_types/field_maps/field_names'; +import { ALERT_ORIGINAL_TIME } from '../../../../../common/field_maps/field_names'; import { sampleThresholdAlert } from '../../rule_types/__mocks__/threshold'; import { buildThresholdSignalHistory } from './build_signal_history'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.ts index e5c21edbc9350b..b959f3de47a8af 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_signal_history.ts @@ -7,9 +7,9 @@ import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { - ALERT_ORIGINAL_TIME, ALERT_RULE_THRESHOLD_FIELD, -} from '../../rule_types/field_maps/field_names'; + ALERT_ORIGINAL_TIME, +} from '../../../../../common/field_maps/field_names'; import { SimpleHit, ThresholdSignalHistory } from '../types'; import { getThresholdTermsHash, isWrappedRACAlert, isWrappedSignalHit } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 48def86203e952..8da9267daabacf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -8,6 +8,7 @@ import moment from 'moment'; import sinon from 'sinon'; import { TransportResult } from '@elastic/elasticsearch'; +import { ALERT_UUID } from '@kbn/rule-data-utils'; import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; import { listMock } from '../../../../../lists/server/mocks'; @@ -41,6 +42,7 @@ import { getValidDateFromDoc, calculateTotal, getTotalHitsValue, + isRACAlert, } from './utils'; import { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types'; import { @@ -1519,4 +1521,52 @@ describe('utils', () => { expect(calculateTotal(undefined, 2)).toBe(-1); }); }); + + describe('isRACAlert', () => { + test('alert with dotted fields returns true', () => { + expect( + isRACAlert({ + [ALERT_UUID]: '123', + }) + ).toEqual(true); + }); + + test('alert with nested fields returns true', () => { + expect( + isRACAlert({ + kibana: { + alert: { uuid: '123' }, + }, + }) + ).toEqual(true); + }); + + test('undefined returns false', () => { + expect(isRACAlert(undefined)).toEqual(false); + }); + + test('null returns false', () => { + expect(isRACAlert(null)).toEqual(false); + }); + + test('number returns false', () => { + expect(isRACAlert(5)).toEqual(false); + }); + + test('string returns false', () => { + expect(isRACAlert('a')).toEqual(false); + }); + + test('array returns false', () => { + expect(isRACAlert([])).toEqual(false); + }); + + test('empty object returns false', () => { + expect(isRACAlert({})).toEqual(false); + }); + + test('alert with null value returns false', () => { + expect(isRACAlert({ 'kibana.alert.uuid': null })).toEqual(false); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 684d24738b8f9b..8a59d71fe74ec6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -58,7 +58,7 @@ import { ThreatRuleParams, ThresholdRuleParams, } from '../schemas/rule_schemas'; -import { WrappedRACAlert } from '../rule_types/types'; +import { RACAlert, WrappedRACAlert } from '../rule_types/types'; import { SearchTypes } from '../../../../common/detection_engine/types'; import { IRuleExecutionLogClient } from '../rule_execution_log/types'; interface SortExceptionsReturn { @@ -985,6 +985,10 @@ export const isWrappedRACAlert = (event: SimpleHit): event is WrappedRACAlert => return (event as WrappedRACAlert)?._source?.[ALERT_UUID] != null; }; +export const isRACAlert = (event: unknown): event is RACAlert => { + return get(event, ALERT_UUID) != null; +}; + export const racFieldMappings: Record = { 'signal.rule.id': ALERT_RULE_UUID, }; diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts index f8d767a371d9ac..b93e76c09282ea 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts @@ -11,7 +11,7 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import { buildExceptionFilter } from '@kbn/securitysolution-list-utils'; import { AnomalyRecordDoc as Anomaly } from '../../../../ml/server'; -export { Anomaly }; +export type { Anomaly }; export type AnomalyResults = estypes.SearchResponse; type MlAnomalySearch = ( searchParams: estypes.SearchRequest, diff --git a/x-pack/plugins/security_solution/server/lib/sourcerer/routes/helpers.ts b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/helpers.ts new file mode 100644 index 00000000000000..47218f2731e781 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/helpers.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 { ElasticsearchClient } from 'kibana/server'; + +export const findExistingIndices = async ( + indices: string[], + esClient: ElasticsearchClient +): Promise => + Promise.all( + indices + .map(async (index) => { + const searchResponse = await esClient.fieldCaps({ + index, + fields: '_id', + ignore_unavailable: true, + allow_no_indices: false, + }); + return searchResponse.body.indices.length > 0; + }) + .map((p) => p.catch((e) => false)) + ); diff --git a/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.test.ts b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.test.ts new file mode 100644 index 00000000000000..99b4f05abb1b1d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.test.ts @@ -0,0 +1,169 @@ +/* + * 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 { createSourcererDataViewRoute } from './'; +import { + requestMock, + serverMock, + requestContextMock, +} from '../../detection_engine/routes/__mocks__'; + +import { SOURCERER_API_URL } from '../../../../common/constants'; +import { StartServicesAccessor } from 'kibana/server'; +import { StartPlugins } from '../../../plugin'; + +jest.mock('./helpers', () => { + const original = jest.requireActual('./helpers'); + + return { + ...original, + findExistingIndices: () => new Promise((resolve) => resolve([true, true])), + }; +}); +const mockPattern = { + id: 'security-solution', + title: + 'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default', +}; +const mockPatternList = [ + 'apm-*-transaction*', + 'traces-apm*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + 'ml_host_risk_score_*', + '.siem-signals-default', +]; +const mockDataViews = [ + { + id: 'metrics-*', + title: 'metrics-*', + }, + { + id: 'logs-*', + title: 'logs-*', + }, + mockPattern, +]; +const getStartServices = jest.fn().mockReturnValue([ + null, + { + data: { + indexPatterns: { + indexPatternsServiceFactory: () => ({ + getIdsWithTitle: () => new Promise((rs) => rs(mockDataViews)), + get: () => new Promise((rs) => rs(mockPattern)), + createAndSave: () => new Promise((rs) => rs(mockPattern)), + updateSavedObject: () => new Promise((rs) => rs(mockPattern)), + }), + }, + }, + }, +] as unknown) as StartServicesAccessor; + +const getStartServicesNotSiem = jest.fn().mockReturnValue([ + null, + { + data: { + indexPatterns: { + indexPatternsServiceFactory: () => ({ + getIdsWithTitle: () => + new Promise((rs) => rs(mockDataViews.filter((v) => v.id !== mockPattern.id))), + get: () => new Promise((rs) => rs(mockPattern)), + createAndSave: () => new Promise((rs) => rs(mockPattern)), + updateSavedObject: () => new Promise((rs) => rs(mockPattern)), + }), + }, + }, + }, +] as unknown) as StartServicesAccessor; + +const mockDataViewsTransformed = { + defaultDataView: { + id: 'security-solution', + patternList: ['apm-*-transaction*', 'traces-apm*'], + title: + 'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default', + }, + kibanaDataViews: [ + { + id: 'metrics-*', + patternList: ['metrics-*'], + title: 'metrics-*', + }, + { + id: 'logs-*', + patternList: ['logs-*'], + title: 'logs-*', + }, + { + id: 'security-solution', + patternList: ['apm-*-transaction*', 'traces-apm*'], + title: + 'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default', + }, + ], +}; + +export const getSourcererRequest = (patternList: string[]) => + requestMock.create({ + method: 'post', + path: SOURCERER_API_URL, + body: { patternList }, + }); + +describe('sourcerer route', () => { + let server: ReturnType; + let { context } = requestContextMock.createTools(); + + beforeEach(() => { + server = serverMock.create(); + ({ context } = requestContextMock.createTools()); + }); + + test('returns sourcerer formatted Data Views when SIEM Data View does NOT exist', async () => { + createSourcererDataViewRoute(server.router, getStartServicesNotSiem); + const response = await server.inject(getSourcererRequest(mockPatternList), context); + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockDataViewsTransformed); + }); + + test('returns sourcerer formatted Data Views when SIEM Data View exists', async () => { + createSourcererDataViewRoute(server.router, getStartServices); + const response = await server.inject(getSourcererRequest(mockPatternList), context); + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockDataViewsTransformed); + }); + + test('returns sourcerer formatted Data Views when SIEM Data View exists and patternList input is changed', async () => { + createSourcererDataViewRoute(server.router, getStartServices); + mockPatternList.shift(); + const response = await server.inject(getSourcererRequest(mockPatternList), context); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + defaultDataView: { + id: 'security-solution', + patternList: ['traces-apm*', 'auditbeat-*'], + title: + 'traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default', + }, + kibanaDataViews: [ + mockDataViewsTransformed.kibanaDataViews[0], + mockDataViewsTransformed.kibanaDataViews[1], + { + id: 'security-solution', + patternList: ['traces-apm*', 'auditbeat-*'], + title: + 'traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts new file mode 100644 index 00000000000000..cdaaedf364eb60 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.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 { transformError } from '@kbn/securitysolution-es-utils'; +import { StartServicesAccessor } from 'kibana/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; +import { DEFAULT_TIME_FIELD, SOURCERER_API_URL } from '../../../../common/constants'; +import { buildSiemResponse } from '../../detection_engine/routes/utils'; +import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; +import { sourcererSchema } from './schema'; +import { StartPlugins } from '../../../plugin'; +import { findExistingIndices } from './helpers'; + +export const createSourcererDataViewRoute = ( + router: SecuritySolutionPluginRouter, + getStartServices: StartServicesAccessor +) => { + router.post( + { + path: SOURCERER_API_URL, + validate: { + body: buildRouteValidation(sourcererSchema), + }, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const siemClient = context.securitySolution?.getAppClient(); + const dataViewId = siemClient.getSourcererDataViewId(); + try { + const [ + , + { + data: { indexPatterns }, + }, + ] = await getStartServices(); + const dataViewService = await indexPatterns.indexPatternsServiceFactory( + context.core.savedObjects.client, + context.core.elasticsearch.client.asInternalUser + ); + + let allDataViews = await dataViewService.getIdsWithTitle(); + const { patternList } = request.body; + const siemDataView = allDataViews.find((v) => v.id === dataViewId); + const patternListAsTitle = patternList.join(); + + if (siemDataView == null) { + const defaultDataView = await dataViewService.createAndSave({ + allowNoIndex: true, + id: dataViewId, + title: patternListAsTitle, + timeFieldName: DEFAULT_TIME_FIELD, + }); + // ?? dataViewId -> type thing here, should never happen + allDataViews.push({ ...defaultDataView, id: defaultDataView.id ?? dataViewId }); + } else if (patternListAsTitle !== siemDataView.title) { + const defaultDataView = { ...siemDataView, id: siemDataView.id ?? '' }; + const wholeDataView = await dataViewService.get(defaultDataView.id); + wholeDataView.title = patternListAsTitle; + let didUpdate = true; + await dataViewService.updateSavedObject(wholeDataView).catch((err) => { + const error = transformError(err); + if (error.statusCode === 403) { + didUpdate = false; + // user doesnt have permissions to update, use existing pattern + wholeDataView.title = defaultDataView.title; + return; + } + throw err; + }); + + // update the data view in allDataViews + if (didUpdate) { + allDataViews = allDataViews.map((v) => + v.id === dataViewId ? { ...v, title: patternListAsTitle } : v + ); + } + } + + const patternLists: string[][] = allDataViews.map(({ title }) => title.split(',')); + const activePatternBools: boolean[][] = await Promise.all( + patternLists.map((pl) => + findExistingIndices(pl, context.core.elasticsearch.client.asCurrentUser) + ) + ); + + const activePatternLists = patternLists.map((pl, i) => + // also remove duplicates from active + pl.filter((pattern, j, self) => self.indexOf(pattern) === j && activePatternBools[i][j]) + ); + + const kibanaDataViews = allDataViews.map((kip, i) => ({ + ...kip, + patternList: activePatternLists[i], + })); + const body = { + defaultDataView: kibanaDataViews.find((p) => p.id === dataViewId) ?? {}, + kibanaDataViews, + }; + return response.ok({ body }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: + error.statusCode === 403 + ? 'Users with write permissions need to access the Elastic Security app to initialize the app source data.' + : error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/sourcerer/routes/schema.ts b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/schema.ts new file mode 100644 index 00000000000000..17f7f36a16341c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/schema.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +export const sourcererSchema = t.type({ + patternList: t.array(t.string), +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filters.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filters.test.ts index 4844a10d99f907..926816149d25cb 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filters.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filters.test.ts @@ -10,6 +10,7 @@ import { copyAllowlistedFields } from './filters'; describe('Security Telemetry filters', () => { describe('allowlistEventFields', () => { const allowlist = { + _id: true, a: true, b: true, c: { @@ -19,12 +20,14 @@ describe('Security Telemetry filters', () => { it('filters top level', () => { const event = { + _id: 'id', a: 'a', a1: 'a1', b: 'b', b1: 'b1', }; expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ + _id: 'id', a: 'a', b: 'b', }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts index e0955c9508f87f..b3316458365d53 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts @@ -105,6 +105,7 @@ const allowlistBaseEventFields: AllowlistFields = { // blindly. Object contents means that we only copy the fields that appear explicitly in // the sub-object. export const allowlistEventFields: AllowlistFields = { + _id: true, '@timestamp': true, agent: true, Endpoint: true, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index 86d41f5040cc6b..37f6debd502577 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -161,7 +161,7 @@ export const ruleExceptionListItemToTelemetryEvent = ( export const templateExceptionList = (listData: ExceptionListItem[], listType: string) => { return listData.map((item) => { const template: ListTemplate = { - '@timestamp': new Date().getTime(), + '@timestamp': moment().toISOString(), }; // cast exception list type to a TelemetryEvent for allowlist filtering diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index 6aaf6f4371475f..c65e40895de543 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -226,7 +226,7 @@ export interface ExceptionListItem { } export interface ListTemplate { - '@timestamp': number; + '@timestamp': string; detection_rule?: TelemetryEvent; endpoint_exception?: TelemetryEvent; endpoint_event_filter?: TelemetryEvent; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts index d03b445da26d08..72a4a0b3782ed9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts @@ -163,6 +163,7 @@ export const mockTemplate = { and: [], }, ], + dataViewId: '', description: '', eventType: 'all', excludedRowRendererIds: [], @@ -192,6 +193,7 @@ export const mockTimeline = { { columnHeaderType: 'not-filtered', id: 'user.name' }, ], dataProviders: [], + dataViewId: 'security-solution', description: '', eventType: 'all', excludedRowRendererIds: [], diff --git a/x-pack/plugins/security_solution/server/lib/timeline/constants.ts b/x-pack/plugins/security_solution/server/lib/timeline/constants.ts index e38096bc2e82c7..bd9a827e768dcb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/constants.ts @@ -10,9 +10,14 @@ */ export const SAVED_QUERY_ID_REF_NAME = 'savedQueryId'; +/** + * The reference name for the saved query ID field within the timeline saved object definition + */ +export const DATA_VIEW_ID_REF_NAME = 'dataViewId'; + /** * This needs to match the type of the saved query saved object. That type is defined here: - * https://github.com/elastic/kibana/blob/master/src/plugins/data/public/query/saved_query/saved_query_service.ts#L54 + * https://github.com/elastic/kibana/blob/main/src/plugins/data/public/query/saved_query/saved_query_service.ts#L54 */ export const SAVED_QUERY_TYPE = 'query'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/README.md b/x-pack/plugins/security_solution/server/lib/timeline/routes/README.md index defbf8be8b7c3c..6878e21e14452b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/README.md +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/README.md @@ -713,6 +713,7 @@ api/timelines?page_size=10&page_index=1&sort_field=updated&sort_order=desc&timel "enabled": true } ], + "dataViewId": "security-solution", "description": "", "eqlOptions": { "tiebreakerField": "", @@ -879,6 +880,7 @@ api/timelines?page_size=10&page_index=1&sort_field=updated&sort_order=desc&timel "enabled": true } ], + "dataViewId": "security-solution", "description": "", "eventType": "all", "filters": [], @@ -1023,6 +1025,7 @@ kbn-version: 8.0.0 "enabled": true } ], + "dataViewId": "security-solution", "description": "", "eventType": "all", "filters": [], @@ -1214,6 +1217,7 @@ kbn-version: 8.0.0 "enabled": true } ], + "dataViewId": "security-solution", "description": "", "eqlOptions": { "eventCategoryField": "event.category", diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts index 8bf5213d6a47ff..1d4d9b1e0f2eaa 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts @@ -9,7 +9,7 @@ import * as module from './helpers'; import { savePinnedEvents } from '../../../saved_object/pinned_events'; import { getNote } from '../../../saved_object/notes'; import { FrameworkRequest } from '../../../../framework'; -import { SavedTimeline } from '../../../../../../common/types/timeline'; +import { SavedTimeline } from '../../../../../../common'; import { mockTemplate, mockTimeline } from '../../../__mocks__/create_timelines'; import { buildFrameworkRequest } from '../../../utils/common'; import { SecurityPluginSetup } from '../../../../../../../security/server'; @@ -32,13 +32,6 @@ const notes = [ const existingNoteIds = undefined; const isImmutable = true; -// System under test uses moment.js under the hood, so we need to mock time. -// Mocking moment via jest.mock('moment') breaks imports of moment in other files. -// Instead, we simply mock Date.now() via jest API and moment starts using it. -// This affects all the tests in this file and doesn't affect tests in other files. -// https://jestjs.io/docs/timer-mocks -jest.useFakeTimers('modern').setSystemTime(new Date('2020-11-04T11:37:31.655Z')); - jest.mock('../../../saved_object/timelines', () => ({ persistTimeline: jest.fn().mockResolvedValue({ timeline: { @@ -77,6 +70,7 @@ describe('createTimelines', () => { const mockRequest = getCreateTimelinesRequest(createTimelineWithoutTimelineId); frameworkRequest = await buildFrameworkRequest(context, securitySetup, mockRequest); + Date.now = jest.fn().mockReturnValue(new Date('2020-11-04T11:37:31.655Z')); }); describe('create timelines', () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts index b49538a706e39a..5ab971adfcb839 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts @@ -51,8 +51,8 @@ const getTimelineTypeAndStatus = ( }; }; -export const convertSavedObjectToSavedTimeline = (savedObject: unknown): TimelineSavedObject => { - const timeline = pipe( +export const convertSavedObjectToSavedTimeline = (savedObject: unknown): TimelineSavedObject => + pipe( TimelineSavedObjectWithDraftRuntime.decode(savedObject), map((savedTimeline) => { const attributes = { @@ -78,6 +78,3 @@ export const convertSavedObjectToSavedTimeline = (savedObject: unknown): Timelin throw new Error(failure(errors).join('\n')); }, identity) ); - - return timeline; -}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts index bb6667f81ed8f7..87d3aa4c6f9086 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts @@ -5,13 +5,14 @@ * 2.0. */ -import { SAVED_QUERY_ID_REF_NAME, SAVED_QUERY_TYPE } from '../../constants'; +import { DATA_VIEW_ID_REF_NAME, SAVED_QUERY_ID_REF_NAME, SAVED_QUERY_TYPE } from '../../constants'; import { FieldMigrator } from '../../utils/migrator'; - +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../../../../../src/plugins/data/common'; /** * A migrator to handle moving specific fields that reference other saved objects to the references field within a saved * object. */ export const timelineFieldsMigrator = new FieldMigrator([ { path: 'savedQueryId', type: SAVED_QUERY_TYPE, name: SAVED_QUERY_ID_REF_NAME }, + { path: 'dataViewId', type: DATA_VIEW_SAVED_OBJECT_TYPE, name: DATA_VIEW_ID_REF_NAME }, ]); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts index 112796df527fa7..8af3cf7cfb1cf5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts @@ -26,6 +26,8 @@ import { mockResolvedTimeline, mockResolveTimelineResponse, } from '../../__mocks__/resolve_timeline'; +import { DATA_VIEW_ID_REF_NAME, SAVED_QUERY_ID_REF_NAME, SAVED_QUERY_TYPE } from '../../constants'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../../../../../src/plugins/data_views/common'; jest.mock('./convert_saved_object_to_savedtimeline', () => ({ convertSavedObjectToSavedTimeline: jest.fn(), @@ -309,4 +311,54 @@ describe('saved_object', () => { expect(result).toEqual(mockResolveTimelineResponse); }); }); + describe('field migrator', () => { + let mockResolveSavedObject: jest.Mock; + const convertSavedObjectToSavedTimelineMock: jest.Mock = + convertSavedObjectToSavedTimeline as jest.Mock; + let mockRequest: FrameworkRequest; + beforeEach(async () => { + jest.clearAllMocks(); + convertSavedObjectToSavedTimelineMock.mockReturnValue(mockResolvedTimeline); + mockResolveSavedObject = jest.fn().mockReturnValue({ + ...mockResolvedSavedObject, + saved_object: { + ...mockResolvedSavedObject.saved_object, + references: [ + { + id: 'boo', + name: SAVED_QUERY_ID_REF_NAME, + type: SAVED_QUERY_TYPE, + }, + { + id: 'also-boo', + name: DATA_VIEW_ID_REF_NAME, + type: DATA_VIEW_SAVED_OBJECT_TYPE, + }, + ], + }, + }); + mockRequest = { + user: { + username: 'username', + }, + context: { + core: { + savedObjects: { + client: { + resolve: mockResolveSavedObject, + }, + }, + }, + }, + } as unknown as FrameworkRequest; + + await resolveTimelineOrNull(mockRequest, '760d3d20-2142-11ec-a46f-051cb8e3154c'); + }); + + test('the fields we track in references are converted to attributes when SO is requested', () => { + const { attributes } = convertSavedObjectToSavedTimelineMock.mock.calls[0][0]; + expect(attributes.dataViewId).toEqual('also-boo'); + expect(attributes.savedQueryId).toEqual('boo'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts index 5aa7dd9c7ad462..39e05b267735da 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts @@ -38,6 +38,7 @@ describe('pickSavedTimeline', () => { { columnHeaderType: 'not-filtered', id: 'destination.ip' }, { columnHeaderType: 'not-filtered', id: 'user.name' }, ], + dataViewId: 'security-solution', indexNames: [ 'auditbeat-*', 'endgame-*', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/create_timelines_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/create_timelines_schema.ts index 4cb21a27bacc8c..bc959e512a471f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/create_timelines_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/create_timelines_schema.ts @@ -27,3 +27,5 @@ export const createTimelineSchema = rt.intersection([ version: unionWithNullType(rt.string), }), ]); + +export type CreateTimelineSchema = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/server/lib/types.ts b/x-pack/plugins/security_solution/server/lib/types.ts index 2a1452e7b2fd3a..15f40fdbc30191 100644 --- a/x-pack/plugins/security_solution/server/lib/types.ts +++ b/x-pack/plugins/security_solution/server/lib/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { ConfigType as Configuration } from '../config'; +export type { ConfigType as Configuration } from '../config'; import { TotalValue, BaseHit, Explanation } from '../../common/detection_engine/types'; export interface ShardsResponse { diff --git a/x-pack/plugins/security_solution/server/mocks.ts b/x-pack/plugins/security_solution/server/mocks.ts index cff68527af4b96..bc8183666c7f3a 100644 --- a/x-pack/plugins/security_solution/server/mocks.ts +++ b/x-pack/plugins/security_solution/server/mocks.ts @@ -11,6 +11,7 @@ type AppClientMock = jest.Mocked; const createAppClientMock = (): AppClientMock => ({ getSignalsIndex: jest.fn(), + getSourcererDataViewId: jest.fn().mockReturnValue('security-solution'), } as unknown as AppClientMock); export const siemMock = { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index b31ec3696fd425..3c281d83846285 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -7,7 +7,6 @@ import { Observable } from 'rxjs'; import LRU from 'lru-cache'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { SIGNALS_ID, QUERY_RULE_TYPE_ID, @@ -22,6 +21,8 @@ import { Logger, SavedObjectsClient } from '../../../../src/core/server'; import { UsageCounter } from '../../../../src/plugins/usage_collection/server'; import { ECS_COMPONENT_TEMPLATE_NAME } from '../../rule_registry/common/assets'; +import { FieldMap } from '../../rule_registry/common/field_map'; +import { technicalRuleFieldMap } from '../../rule_registry/common/assets/field_maps/technical_rule_field_map'; import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; import { IRuleDataClient, Dataset } from '../../rule_registry/server'; import { ListPluginSetup } from '../../lists/server'; @@ -63,8 +64,6 @@ import { licenseService } from './lib/license'; import { PolicyWatcher } from './endpoint/lib/policy/license_watch'; import { migrateArtifactsToFleet } from './endpoint/lib/artifacts/migrate_artifacts_to_fleet'; import aadFieldConversion from './lib/detection_engine/routes/index/signal_aad_mapping.json'; -import { alertsFieldMap } from './lib/detection_engine/rule_types/field_maps/alerts'; -import { rulesFieldMap } from './lib/detection_engine/rule_types/field_maps/rules'; import { registerEventLogProvider } from './lib/detection_engine/rule_execution_log/event_log_adapter/register_event_log_provider'; import { getKibanaPrivilegesFeaturePrivileges, getCasesKibanaFeature } from './features'; import { EndpointMetadataService } from './endpoint/services/metadata'; @@ -87,8 +86,9 @@ import type { SecuritySolutionPluginStart, PluginInitializerContext, } from './plugin_contract'; +import { alertsFieldMap, rulesFieldMap } from '../common/field_maps'; -export { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract'; +export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract'; export class Plugin implements ISecuritySolutionPlugin { private readonly pluginContext: PluginInitializerContext; @@ -188,13 +188,9 @@ export class Plugin implements ISecuritySolutionPlugin { }; if (isRuleRegistryEnabled) { - // NOTE: this is not used yet - // TODO: convert the aliases to FieldMaps. Requires enhancing FieldMap to support alias path. - // Split aliases by component template since we need to alias some fields in technical field mappings, - // some fields in security solution specific component template. - const aliases: Record = {}; + const aliasesFieldMap: FieldMap = {}; Object.entries(aadFieldConversion).forEach(([key, value]) => { - aliases[key] = { + aliasesFieldMap[key] = { type: 'alias', path: value, }; @@ -208,7 +204,10 @@ export class Plugin implements ISecuritySolutionPlugin { componentTemplates: [ { name: 'mappings', - mappings: mappingFromFieldMap({ ...alertsFieldMap, ...rulesFieldMap }, false), + mappings: mappingFromFieldMap( + { ...technicalRuleFieldMap, ...alertsFieldMap, ...rulesFieldMap, ...aliasesFieldMap }, + false + ), }, ], secondaryAlias: config.signalsIndex, @@ -245,7 +244,8 @@ export class Plugin implements ISecuritySolutionPlugin { ruleDataService, logger, ruleDataClient, - ruleOptions + ruleOptions, + core.getStartServices ); registerEndpointRoutes(router, endpointContext); registerLimitedConcurrencyRoutes(core); diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index c2e622bc495c98..0028d624c29551 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -36,7 +36,13 @@ export class RequestContextFactory implements IRequestContextFactory { private readonly appClientFactory: AppClientFactory; constructor(private readonly options: ConstructorOptions) { + const { config, plugins } = options; + this.appClientFactory = new AppClientFactory(); + this.appClientFactory.setup({ + getSpaceId: plugins.spaces?.spacesService?.getSpaceId, + config, + }); } public async create( @@ -44,14 +50,10 @@ export class RequestContextFactory implements IRequestContextFactory { request: KibanaRequest ): Promise { const { options, appClientFactory } = this; - const { config, plugins } = options; + const { config, core, plugins } = options; const { lists, ruleRegistry, security, spaces } = plugins; - appClientFactory.setup({ - getSpaceId: plugins.spaces?.spacesService?.getSpaceId, - config, - }); - + const [, startPlugins] = await core.getStartServices(); const frameworkRequest = await buildFrameworkRequest(context, security, request); return { @@ -69,9 +71,10 @@ export class RequestContextFactory implements IRequestContextFactory { getExecutionLogClient: () => new RuleExecutionLogClient({ + underlyingClient: config.ruleExecutionLog.underlyingClient, savedObjectsClient: context.core.savedObjects.client, eventLogService: plugins.eventLog, - underlyingClient: config.ruleExecutionLog.underlyingClient, + eventLogClient: startPlugins.eventLog.getClient(request), }), getExceptionListClient: () => { diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index f3e8cc1dee4b17..dd66a4333ad15e 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { StartServicesAccessor } from 'kibana/server'; import { Logger } from 'src/core/server'; import { IRuleDataClient, RuleDataPluginService } from '../../../rule_registry/server'; @@ -36,6 +37,7 @@ import { performBulkActionRoute } from '../lib/detection_engine/routes/rules/per import { importRulesRoute } from '../lib/detection_engine/routes/rules/import_rules_route'; import { exportRulesRoute } from '../lib/detection_engine/routes/rules/export_rules_route'; import { findRulesStatusesRoute } from '../lib/detection_engine/routes/rules/find_rules_status_route'; +import { findRuleStatusInternalRoute } from '../lib/detection_engine/routes/rules/find_rule_status_internal_route'; import { getPrepackagedRulesStatusRoute } from '../lib/detection_engine/routes/rules/get_prepackaged_rules_status_route'; import { createTimelinesRoute, @@ -55,7 +57,7 @@ import { persistNoteRoute } from '../lib/timeline/routes/notes'; import { persistPinnedEventRoute } from '../lib/timeline/routes/pinned_events'; -import { SetupPlugins } from '../plugin'; +import { SetupPlugins, StartPlugins } from '../plugin'; import { ConfigType } from '../config'; import { TelemetryEventsSender } from '../lib/telemetry/sender'; import { installPrepackedTimelinesRoute } from '../lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; @@ -63,6 +65,7 @@ import { previewRulesRoute } from '../lib/detection_engine/routes/rules/preview_ import { CreateRuleOptions } from '../lib/detection_engine/rule_types/types'; // eslint-disable-next-line no-restricted-imports import { legacyCreateLegacyNotificationRoute } from '../lib/detection_engine/routes/rules/legacy_create_legacy_notification'; +import { createSourcererDataViewRoute } from '../lib/sourcerer/routes'; import { createPreviewIndexRoute } from '../lib/detection_engine/routes/index/create_preview_index_route'; export const initRoutes = ( @@ -75,7 +78,8 @@ export const initRoutes = ( ruleDataService: RuleDataPluginService, logger: Logger, ruleDataClient: IRuleDataClient | null, - ruleOptions: CreateRuleOptions + ruleOptions: CreateRuleOptions, + getStartServices: StartServicesAccessor ) => { const isRuleRegistryEnabled = ruleDataClient != null; // Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules @@ -122,6 +126,7 @@ export const initRoutes = ( persistPinnedEventRoute(router, config, security); findRulesStatusesRoute(router); + findRuleStatusInternalRoute(router); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals // POST /api/detection_engine/signals/status @@ -147,4 +152,7 @@ export const initRoutes = ( // Privileges API to get the generic user privileges readPrivilegesRoute(router, hasEncryptionKey); + + // Sourcerer API to generate default pattern + createSourcererDataViewRoute(router, getStartServices); }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index ae68d81d6b9224..918d3aadfd6e8e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -22,9 +22,7 @@ import { HostValue, } from '../../../../../../common/search_strategy/security_solution/hosts'; import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; -import { getHostMetaData } from '../../../../../endpoint/routes/metadata/handlers'; import { EndpointAppContext } from '../../../../../endpoint/types'; -import { fleetAgentStatusToEndpointHostStatus } from '../../../../../endpoint/utils'; import { getPendingActionCounts } from '../../../../../endpoint/services'; export const HOST_FIELDS = [ @@ -184,51 +182,55 @@ export const getHostEndpoint = async ( endpointContext: EndpointAppContext; } ): Promise => { - const { esClient, endpointContext, savedObjectsClient } = deps; + if (!id) { + return null; + } + + const { esClient, endpointContext } = deps; const logger = endpointContext.logFactory.get('metadata'); + try { const agentService = endpointContext.service.getAgentService(); - if (agentService === undefined) { + + if (!agentService) { throw new Error('agentService not available'); } - const metadataRequestContext = { - esClient, - endpointAppContextService: endpointContext.service, - logger, - savedObjectsClient, - }; - const endpointData = - id != null && metadataRequestContext.endpointAppContextService.getAgentService() != null - ? await getHostMetaData(metadataRequestContext, id) - : null; - - const fleetAgentId = endpointData?.elastic.agent.id; - const [fleetAgentStatus, pendingActions] = !fleetAgentId - ? [undefined, {}] - : await Promise.all([ - // Get Agent Status - agentService.getAgentStatusById(esClient.asCurrentUser, fleetAgentId), - // Get a list of pending actions (if any) - getPendingActionCounts( - esClient.asCurrentUser, - endpointContext.service.getEndpointMetadataService(), - [fleetAgentId] - ).then((results) => { + + const endpointData = await endpointContext.service + .getEndpointMetadataService() + // Using `internalUser` ES client below due to the fact that Fleet data has been moved to + // system indices (`.fleet*`). Because this is a readonly action, this should be ok to do + // here until proper RBOC controls are implemented + .getEnrichedHostMetadata(esClient.asInternalUser, id); + + const fleetAgentId = endpointData.metadata.elastic.agent.id; + + const pendingActions = fleetAgentId + ? getPendingActionCounts( + esClient.asInternalUser, + endpointContext.service.getEndpointMetadataService(), + [fleetAgentId], + endpointContext.experimentalFeatures.pendingActionResponsesWithAck + ) + .then((results) => { return results[0].pending_actions; - }), - ]); - - return endpointData != null && endpointData - ? { - endpointPolicy: endpointData.Endpoint.policy.applied.name, - policyStatus: endpointData.Endpoint.policy.applied.status, - sensorVersion: endpointData.agent.version, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - elasticAgentStatus: fleetAgentStatusToEndpointHostStatus(fleetAgentStatus!), - isolation: endpointData.Endpoint.state?.isolation ?? false, - pendingActions, - } - : null; + }) + .catch((error) => { + // Failure in retrieving the number of pending actions should not fail the entire + // call to get endpoint details. Log the error and return an empty object + logger.warn(error); + return {}; + }) + : {}; + + return { + endpointPolicy: endpointData.metadata.Endpoint.policy.applied.name, + policyStatus: endpointData.metadata.Endpoint.policy.applied.status, + sensorVersion: endpointData.metadata.agent.version, + elasticAgentStatus: endpointData.host_status, + isolation: endpointData.metadata.Endpoint.state?.isolation ?? false, + pendingActions, + }; } catch (err) { logger.warn(err); return null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts index 1abcd4d28568b0..c75b20f44035a2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts @@ -2098,8 +2098,8 @@ export const formattedPreviewStrategyResponse = { JSON.stringify( { index: ['.siem-preview-signals-default'], - allowNoIndices: true, - ignoreUnavailable: true, + allow_no_indices: true, + ignore_unavailable: true, track_total_hits: true, body: { aggregations: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts index aa8728c97b9376..2ff4831616ab91 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/__mocks__/index.ts @@ -18,8 +18,8 @@ export const mockOptions = { export const expectedDsl = { index: ['.siem-preview-signals-default'], - allowNoIndices: true, - ignoreUnavailable: true, + allow_no_indices: true, + ignore_unavailable: true, track_total_hits: true, body: { aggregations: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts index a98117feadd73c..dde09860109b02 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/preview/query.preview_histogram.dsl.ts @@ -65,8 +65,8 @@ export const buildPreviewHistogramQuery = ({ const dslQuery = { index: defaultIndex, - allowNoIndices: true, - ignoreUnavailable: true, + allow_no_indices: true, + ignore_unavailable: true, track_total_hits: true, body: { aggregations: getHistogramAggregation(), diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts index 914c684fe8813d..856d9b00dca7b7 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts @@ -34,12 +34,6 @@ export const filterExportedCounts = (): Transform => { ); }; -export const filterExportedRulesCounts = (): Transform => { - return createFilterStream( - (obj) => obj != null && !has('exported_rules_count', obj) - ); -}; - export const filterExceptions = (): Transform => { return createFilterStream( (obj) => obj != null && !has('list_id', obj) diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts index 231b88c39aeb8d..e28409871fb4d2 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts @@ -13,7 +13,8 @@ import { setup as policyAddSetup } from './policy_add.helpers'; import { setup as policyEditSetup } from './policy_edit.helpers'; import { setup as restoreSnapshotSetup } from './restore_snapshot.helpers'; -export { nextTick, getRandomString, findTestSubject, TestBed, delay } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +export { nextTick, getRandomString, findTestSubject, delay } from '@kbn/test/jest'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/index.ts b/x-pack/plugins/snapshot_restore/public/application/components/index.ts index 642f59d12c6f2d..77f53a3533a162 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/components/index.ts @@ -17,12 +17,8 @@ export { RestoreSnapshotForm } from './restore_snapshot_form'; export { PolicyExecuteProvider } from './policy_execute_provider'; export { PolicyDeleteProvider } from './policy_delete_provider'; export { CollapsibleIndicesList, CollapsibleDataStreamsList } from './collapsible_lists'; -export { - RetentionSettingsUpdateModalProvider, - UpdateRetentionSettings, -} from './retention_update_modal_provider'; -export { - RetentionExecuteModalProvider, - ExecuteRetention, -} from './retention_execute_modal_provider'; +export type { UpdateRetentionSettings } from './retention_update_modal_provider'; +export { RetentionSettingsUpdateModalProvider } from './retention_update_modal_provider'; +export type { ExecuteRetention } from './retention_execute_modal_provider'; +export { RetentionExecuteModalProvider } from './retention_execute_modal_provider'; export { PolicyForm } from './policy_form'; diff --git a/x-pack/plugins/snapshot_restore/public/application/index.tsx b/x-pack/plugins/snapshot_restore/public/application/index.tsx index c0b438e1761c68..69b4b2dab1b34c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/index.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/index.tsx @@ -37,4 +37,4 @@ export const renderApp = (elem: Element, dependencies: AppDependencies) => { }; }; -export { AppDependencies }; +export type { AppDependencies }; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/index.ts b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts index 19a42bef4cea45..1462728e756b88 100644 --- a/x-pack/plugins/snapshot_restore/public/application/lib/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts @@ -7,10 +7,8 @@ export { useDecodedParams } from './use_decoded_params'; +export type { SortField, SortDirection, SnapshotListParams } from './snapshot_list_params'; export { - SortField, - SortDirection, - SnapshotListParams, getListParams, getQueryFromListParams, DEFAULT_SNAPSHOT_LIST_PARAMS, diff --git a/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts b/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts index 90eca1f9fd1dd9..92d2e4117d0674 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts @@ -5,12 +5,11 @@ * 2.0. */ -export { - RepositoryValidation, - RepositorySettingsValidation, - validateRepository, -} from './validate_repository'; +export type { RepositoryValidation, RepositorySettingsValidation } from './validate_repository'; +export { validateRepository } from './validate_repository'; -export { RestoreValidation, validateRestore } from './validate_restore'; +export type { RestoreValidation } from './validate_restore'; +export { validateRestore } from './validate_restore'; -export { PolicyValidation, validatePolicy, ValidatePolicyHelperData } from './validate_policy'; +export type { PolicyValidation, ValidatePolicyHelperData } from './validate_policy'; +export { validatePolicy } from './validate_policy'; diff --git a/x-pack/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/plugins/snapshot_restore/public/shared_imports.ts index a3cda90d26f2a1..cc86117d1f84ae 100644 --- a/x-pack/plugins/snapshot_restore/public/shared_imports.ts +++ b/x-pack/plugins/snapshot_restore/public/shared_imports.ts @@ -5,22 +5,24 @@ * 2.0. */ +export type { + Error, + Frequency, + SendRequestConfig, + SendRequestResponse, + UseRequestResponse, + UseRequestConfig, +} from '../../../../src/plugins/es_ui_shared/public'; export { AuthorizationProvider, CronEditor, - Error, - Frequency, NotAuthorizedSection, SectionError, PageError, PageLoading, sendRequest, - SendRequestConfig, - SendRequestResponse, - UseRequestResponse, useAuthorizationContext, useRequest, - UseRequestConfig, WithPrivileges, EuiCodeEditor, } from '../../../../src/plugins/es_ui_shared/public'; diff --git a/x-pack/plugins/snapshot_restore/server/test/helpers/index.ts b/x-pack/plugins/snapshot_restore/server/test/helpers/index.ts index 475d4d9a3e1702..682b520c12b00f 100644 --- a/x-pack/plugins/snapshot_restore/server/test/helpers/index.ts +++ b/x-pack/plugins/snapshot_restore/server/test/helpers/index.ts @@ -5,6 +5,7 @@ * 2.0. */ -export { RouterMock, RequestMock } from './router_mock'; +export type { RequestMock } from './router_mock'; +export { RouterMock } from './router_mock'; export { routeDependencies } from './route_dependencies'; diff --git a/x-pack/plugins/spaces/common/licensing/index.ts b/x-pack/plugins/spaces/common/licensing/index.ts index f7dd998a7561c8..5c23f035c84c5f 100644 --- a/x-pack/plugins/spaces/common/licensing/index.ts +++ b/x-pack/plugins/spaces/common/licensing/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { SpacesLicenseService, SpacesLicense } from './license_service'; +export type { SpacesLicense } from './license_service'; +export { SpacesLicenseService } from './license_service'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx index 988564d0140cc4..a9768bea9d1ed7 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx @@ -112,8 +112,7 @@ const setup = async (opts: SetupOpts = {}) => { return { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToCopy }; }; -// flaky https://github.com/elastic/kibana/issues/96708 -describe.skip('CopyToSpaceFlyout', () => { +describe('CopyToSpaceFlyout', () => { it('waits for spaces to load', async () => { const { wrapper } = await setup({ returnBeforeSpacesLoad: true }); diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.tsx index 7697780c352c94..3ff72e131aa667 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.tsx @@ -75,7 +75,7 @@ export const CopyToSpaceFlyoutInternal = (props: CopyToSpaceFlyoutProps) => { isLoading: false, spaces: [...spacesMap.values()].filter( ({ isActiveSpace, isAuthorizedForPurpose }) => - isActiveSpace || isAuthorizedForPurpose('copySavedObjectsIntoSpace') + !isActiveSpace && isAuthorizedForPurpose('copySavedObjectsIntoSpace') ), }); }) diff --git a/x-pack/plugins/spaces/public/index.ts b/x-pack/plugins/spaces/public/index.ts index fe04358e30483d..86f1afd234be3f 100644 --- a/x-pack/plugins/spaces/public/index.ts +++ b/x-pack/plugins/spaces/public/index.ts @@ -9,7 +9,7 @@ import { SpacesPlugin } from './plugin'; export { getSpaceColor, getSpaceImageUrl, getSpaceInitials } from './space_avatar'; -export { SpacesPluginSetup, SpacesPluginStart } from './plugin'; +export type { SpacesPluginSetup, SpacesPluginStart } from './plugin'; export type { Space, GetAllSpacesPurpose, GetSpaceResult } from '../common'; diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts index d0406d744b72a9..62f3a39a6b48b4 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts @@ -140,7 +140,9 @@ export class SpacesManager { type: string ): Promise<{ shareToAllSpaces: boolean }> { return this.http - .get('/internal/security/_share_saved_object_permissions', { query: { type } }) + .get<{ shareToAllSpaces: boolean }>('/internal/security/_share_saved_object_permissions', { + query: { type }, + }) .catch((err) => { const isNotFound = err?.body?.statusCode === 404; if (isNotFound) { @@ -190,7 +192,7 @@ export class SpacesManager { if (this.isAnonymousPath()) { return; } - const activeSpace = await this.http.get('/internal/spaces/_active_space'); + const activeSpace = await this.http.get('/internal/spaces/_active_space'); this.activeSpace$.next(activeSpace); } diff --git a/x-pack/plugins/spaces/server/index.ts b/x-pack/plugins/spaces/server/index.ts index ad27069759198b..6628d75a364945 100644 --- a/x-pack/plugins/spaces/server/index.ts +++ b/x-pack/plugins/spaces/server/index.ts @@ -19,9 +19,13 @@ export { addSpaceIdToPath } from '../common'; // end public contract exports -export { SpacesPluginSetup, SpacesPluginStart } from './plugin'; -export { SpacesServiceSetup, SpacesServiceStart } from './spaces_service'; -export { ISpacesClient, SpacesClientRepositoryFactory, SpacesClientWrapper } from './spaces_client'; +export type { SpacesPluginSetup, SpacesPluginStart } from './plugin'; +export type { SpacesServiceSetup, SpacesServiceStart } from './spaces_service'; +export type { + ISpacesClient, + SpacesClientRepositoryFactory, + SpacesClientWrapper, +} from './spaces_client'; export type { Space, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/index.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/index.ts index a4283d9837085e..a100056c57bcde 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/index.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/index.ts @@ -7,4 +7,4 @@ export { copySavedObjectsToSpacesFactory } from './copy_to_spaces'; export { resolveCopySavedObjectsToSpacesConflictsFactory } from './resolve_copy_conflicts'; -export { CopyResponse } from './types'; +export type { CopyResponse } from './types'; diff --git a/x-pack/plugins/spaces/server/spaces_client/index.ts b/x-pack/plugins/spaces/server/spaces_client/index.ts index 124d94ba07f2fe..b60d4fa2686f09 100644 --- a/x-pack/plugins/spaces/server/spaces_client/index.ts +++ b/x-pack/plugins/spaces/server/spaces_client/index.ts @@ -5,11 +5,12 @@ * 2.0. */ -export { SpacesClient, ISpacesClient } from './spaces_client'; -export { - SpacesClientService, +export type { ISpacesClient } from './spaces_client'; +export { SpacesClient } from './spaces_client'; +export type { SpacesClientServiceSetup, SpacesClientServiceStart, SpacesClientRepositoryFactory, SpacesClientWrapper, } from './spaces_client_service'; +export { SpacesClientService } from './spaces_client_service'; diff --git a/x-pack/plugins/spaces/server/spaces_service/index.ts b/x-pack/plugins/spaces/server/spaces_service/index.ts index adb311c2048e95..1fd77f742866de 100644 --- a/x-pack/plugins/spaces/server/spaces_service/index.ts +++ b/x-pack/plugins/spaces/server/spaces_service/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { SpacesService, SpacesServiceSetup, SpacesServiceStart } from './spaces_service'; +export type { SpacesServiceSetup, SpacesServiceStart } from './spaces_service'; +export { SpacesService } from './spaces_service'; diff --git a/x-pack/plugins/spaces/server/usage_stats/index.ts b/x-pack/plugins/spaces/server/usage_stats/index.ts index e8cba8fb198845..d3e736e174166d 100644 --- a/x-pack/plugins/spaces/server/usage_stats/index.ts +++ b/x-pack/plugins/spaces/server/usage_stats/index.ts @@ -6,5 +6,6 @@ */ export { SPACES_USAGE_STATS_TYPE } from './constants'; -export { UsageStatsService, UsageStatsServiceSetup } from './usage_stats_service'; -export { UsageStats } from './types'; +export type { UsageStatsServiceSetup } from './usage_stats_service'; +export { UsageStatsService } from './usage_stats_service'; +export type { UsageStats } from './types'; diff --git a/x-pack/plugins/stack_alerts/server/types.ts b/x-pack/plugins/stack_alerts/server/types.ts index b78aa4e6432d5e..a339e0b24921c0 100644 --- a/x-pack/plugins/stack_alerts/server/types.ts +++ b/x-pack/plugins/stack_alerts/server/types.ts @@ -8,7 +8,7 @@ import { PluginStartContract as TriggersActionsUiStartContract } from '../../triggers_actions_ui/server'; import { PluginSetupContract as AlertingSetup } from '../../alerting/server'; -export { +export type { PluginSetupContract as AlertingSetup, AlertType, RuleParamsAndRefs, diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index d078c7b78ad949..58fba0b6f68c7d 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -30,7 +30,7 @@ export { throwUnrecoverableError, isEphemeralTaskRejectedDueToCapacityError, } from './task_running'; -export { RunNowResult } from './task_scheduling'; +export type { RunNowResult } from './task_scheduling'; export { getOldestIdleActionTask } from './queries/oldest_idle_action_task'; export type { diff --git a/x-pack/plugins/task_manager/server/lib/fill_pool.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.ts index c9050ebb75d69f..5d7d7a2a735c1f 100644 --- a/x-pack/plugins/task_manager/server/lib/fill_pool.ts +++ b/x-pack/plugins/task_manager/server/lib/fill_pool.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { performance } from 'perf_hooks'; import { Observable } from 'rxjs'; import { concatMap, last } from 'rxjs/operators'; import { ClaimOwnershipResult } from '../queries/task_claiming'; @@ -57,7 +56,6 @@ export async function fillPool( converter: (taskInstance: ConcreteTaskInstance) => TaskManagerRunner, run: (tasks: TaskManagerRunner[]) => Promise ): Promise { - performance.mark('fillPool.start'); return new Promise((resolve, reject) => { const stopTaskTimer = startTaskTimer(); const augmentTimingTo = ( @@ -76,12 +74,6 @@ export async function fillPool( res, async ({ docs, stats }) => { if (!docs.length) { - performance.mark('fillPool.bailNoTasks'); - performance.measure( - 'fillPool.activityDurationUntilNoTasks', - 'fillPool.start', - 'fillPool.bailNoTasks' - ); return asOk({ result: TaskPoolRunResult.NoTaskWereRan, stats }); } return asOk( @@ -106,20 +98,12 @@ export async function fillPool( ({ result, stats }) => { switch (result) { case TaskPoolRunResult.RanOutOfCapacity: - performance.mark('fillPool.bailExhaustedCapacity'); - performance.measure( - 'fillPool.activityDurationUntilExhaustedCapacity', - 'fillPool.start', - 'fillPool.bailExhaustedCapacity' - ); return augmentTimingTo(FillPoolResult.RanOutOfCapacity, stats); case TaskPoolRunResult.RunningAtCapacity: - performance.mark('fillPool.cycle'); return augmentTimingTo(FillPoolResult.RunningAtCapacity, stats); case TaskPoolRunResult.NoTaskWereRan: return augmentTimingTo(FillPoolResult.NoTasksClaimed, stats); default: - performance.mark('fillPool.cycle'); return augmentTimingTo(FillPoolResult.PoolFilled, stats); } }, diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts index d541ffb5684dad..5d513c645a8622 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts @@ -46,7 +46,10 @@ export function logHealthMetrics( } const message = `Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`; - const docLink = `https://www.elastic.co/guide/en/kibana/${kibanaPackageJson.branch}/task-manager-health-monitoring.html`; + // TODO: remove when docs support "main" + const docsBranch = kibanaPackageJson.branch === 'main' ? 'master' : 'main'; + + const docLink = `https://www.elastic.co/guide/en/kibana/${docsBranch}/task-manager-health-monitoring.html`; const detectedProblemMessage = `Task Manager detected a degradation in performance. This is usually temporary, and Kibana can recover automatically. If the problem persists, check the docs for troubleshooting information: ${docLink} .`; if (enabled) { const driftInSeconds = (monitoredHealth.stats.runtime?.value.drift.p99 ?? 0) / 1000; diff --git a/x-pack/plugins/task_manager/server/monitoring/index.ts b/x-pack/plugins/task_manager/server/monitoring/index.ts index 99a4e31dbdb02e..a352ec55f2dbc6 100644 --- a/x-pack/plugins/task_manager/server/monitoring/index.ts +++ b/x-pack/plugins/task_manager/server/monitoring/index.ts @@ -18,10 +18,9 @@ import { TaskPollingLifecycle } from '../polling_lifecycle'; import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; +export type { MonitoringStats, RawMonitoringStats } from './monitoring_stats_stream'; export { - MonitoringStats, HealthStatus, - RawMonitoringStats, summarizeMonitoringStats, createAggregators, createMonitoringStatsStream, diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts index 08badf8fe1c9de..5175525b15cf19 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts @@ -37,7 +37,7 @@ import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; import { CapacityEstimationStat, withCapacityEstimate } from './capacity_estimation'; -export { AggregatedStatProvider, AggregatedStat } from './runtime_statistics_aggregator'; +export type { AggregatedStatProvider, AggregatedStat } from './runtime_statistics_aggregator'; export interface MonitoringStats { last_update: string; diff --git a/x-pack/plugins/task_manager/server/polling/task_poller.ts b/x-pack/plugins/task_manager/server/polling/task_poller.ts index 37a283e4c45317..21a1579f85767c 100644 --- a/x-pack/plugins/task_manager/server/polling/task_poller.ts +++ b/x-pack/plugins/task_manager/server/polling/task_poller.ts @@ -9,8 +9,6 @@ * This module contains the logic for polling the task manager index for new work. */ -import { performance } from 'perf_hooks'; -import { after } from 'lodash'; import { Subject, merge, of, Observable, combineLatest, timer } from 'rxjs'; import { mapTo, filter, scan, concatMap, tap, catchError, switchMap } from 'rxjs/operators'; @@ -113,7 +111,6 @@ export function createTaskPoller({ // take as many argumented calls as we have capacity for and call `work` with // those arguments. If the queue is empty this will still trigger work to be done concatMap(async (set: Set) => { - closeSleepPerf(); return mapResult>>( await promiseResult( timeoutPromiseAfter( @@ -126,7 +123,6 @@ export function createTaskPoller({ (err: Error) => asPollingError(err, PollingErrorType.WorkError) ); }), - tap(openSleepPerf), // catch errors during polling for work catchError((err: Error) => of(asPollingError(err, PollingErrorType.WorkError))) ); @@ -177,13 +173,3 @@ export class PollingError extends Error { this.data = data; } } - -const openSleepPerf = () => { - performance.mark('TaskPoller.sleep'); -}; -// we only want to close after an open has been called, as we're counting the time *between* work cycles -// so we'll ignore the first call to `closeSleepPerf` but we will run every subsequent call -const closeSleepPerf = after(2, () => { - performance.mark('TaskPoller.poll'); - performance.measure('TaskPoller.sleepDuration', 'TaskPoller.sleep', 'TaskPoller.poll'); -}); diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts index d394214e6c7780..87de0e691d4715 100644 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ b/x-pack/plugins/task_manager/server/task_pool.ts @@ -11,7 +11,6 @@ */ import { Observable, Subject } from 'rxjs'; import moment, { Duration } from 'moment'; -import { performance } from 'perf_hooks'; import { padStart } from 'lodash'; import { Logger } from '../../../../src/core/server'; import { TaskRunner } from './task_running'; @@ -111,7 +110,6 @@ export class TaskPool { public run = async (tasks: TaskRunner[]): Promise => { const [tasksToRun, leftOverTasks] = partitionListByCount(tasks, this.availableWorkers); if (tasksToRun.length) { - performance.mark('attemptToRun_start'); await Promise.all( tasksToRun .filter((taskRunner) => !this.tasksInPool.has(taskRunner.id)) @@ -130,9 +128,6 @@ export class TaskPool { .catch((err) => this.handleFailureOfMarkAsRunning(taskRunner, err)); }) ); - - performance.mark('attemptToRun_stop'); - performance.measure('taskPool.attemptToRun', 'attemptToRun_start', 'attemptToRun_stop'); } if (leftOverTasks.length) { diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index 919360952ebd41..2a5d48845ce483 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -13,7 +13,6 @@ import apm from 'elastic-apm-node'; import { withSpan } from '@kbn/apm-utils'; -import { performance } from 'perf_hooks'; import { identity, defaults, flow } from 'lodash'; import { Logger, @@ -313,7 +312,6 @@ export class TaskManagerRunner implements TaskRunner { }` ); } - performance.mark('markTaskAsRunning_start'); const apmTrans = apm.startTransaction('taskManager', 'taskManager markTaskAsRunning'); @@ -372,12 +370,10 @@ export class TaskManagerRunner implements TaskRunner { } if (apmTrans) apmTrans.end('success'); - performanceStopMarkingTaskAsRunning(); this.onTaskEvent(asTaskMarkRunningEvent(this.id, asOk(this.instance.task))); return true; } catch (error) { if (apmTrans) apmTrans.end('failure'); - performanceStopMarkingTaskAsRunning(); this.onTaskEvent(asTaskMarkRunningEvent(this.id, asErr(error))); if (!SavedObjectsErrorHelpers.isConflictError(error)) { if (!SavedObjectsErrorHelpers.isNotFoundError(error)) { @@ -617,15 +613,6 @@ function howManyMsUntilOwnershipClaimExpires(ownershipClaimedUntil: Date | null) return ownershipClaimedUntil ? ownershipClaimedUntil.getTime() - Date.now() : 0; } -function performanceStopMarkingTaskAsRunning() { - performance.mark('markTaskAsRunning_stop'); - performance.measure( - 'taskRunner.markTaskAsRunning', - 'markTaskAsRunning_start', - 'markTaskAsRunning_stop' - ); -} - // A type that extracts the Instance type out of TaskRunningStage // This helps us to better communicate to the developer what the expected "stage" // in a specific place in the code might be diff --git a/x-pack/plugins/telemetry_collection_xpack/README.md b/x-pack/plugins/telemetry_collection_xpack/README.md index 6c205bb17c663d..b055719c19585e 100644 --- a/x-pack/plugins/telemetry_collection_xpack/README.md +++ b/x-pack/plugins/telemetry_collection_xpack/README.md @@ -6,4 +6,4 @@ Gathers all usage collection, retrieving them from both: OSS and X-Pack plugins. ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions on how to set up your development environment. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions on how to set up your development environment. diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index b3ca5f17634d54..8ac619d479bef2 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -100,6 +100,49 @@ } } }, + "count_actions_executions_per_day": { + "type": "long" + }, + "count_actions_executions_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__email": { + "type": "long" + }, + "__index": { + "type": "long" + }, + "__pagerduty": { + "type": "long" + }, + "__swimlane": { + "type": "long" + }, + "__server-log": { + "type": "long" + }, + "__slack": { + "type": "long" + }, + "__webhook": { + "type": "long" + }, + "__servicenow": { + "type": "long" + }, + "__jira": { + "type": "long" + }, + "__resilient": { + "type": "long" + }, + "__teams": { + "type": "long" + } + } + }, "count_active_email_connectors_by_service_type": { "properties": { "DYNAMIC_KEY": { @@ -127,6 +170,92 @@ }, "count_actions_namespaces": { "type": "long" + }, + "count_actions_executions_failed_per_day": { + "type": "long" + }, + "count_actions_executions_failed_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__email": { + "type": "long" + }, + "__index": { + "type": "long" + }, + "__pagerduty": { + "type": "long" + }, + "__swimlane": { + "type": "long" + }, + "__server-log": { + "type": "long" + }, + "__slack": { + "type": "long" + }, + "__webhook": { + "type": "long" + }, + "__servicenow": { + "type": "long" + }, + "__jira": { + "type": "long" + }, + "__resilient": { + "type": "long" + }, + "__teams": { + "type": "long" + } + } + }, + "avg_execution_time_per_day": { + "type": "long" + }, + "avg_execution_time_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__email": { + "type": "long" + }, + "__index": { + "type": "long" + }, + "__pagerduty": { + "type": "long" + }, + "__swimlane": { + "type": "long" + }, + "__server-log": { + "type": "long" + }, + "__slack": { + "type": "long" + }, + "__webhook": { + "type": "long" + }, + "__servicenow": { + "type": "long" + }, + "__jira": { + "type": "long" + }, + "__resilient": { + "type": "long" + }, + "__teams": { + "type": "long" + } + } } } }, @@ -251,21 +380,651 @@ "xpack__uptime__alerts__tls": { "type": "long" }, - "xpack__uptime__alerts__durationAnomaly": { - "type": "long" + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "count_by_type": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "count_rules_namespaces": { + "type": "long" + }, + "count_rules_executions_per_day": { + "type": "long" + }, + "count_rules_executions_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "count_rules_executions_failured_per_day": { + "type": "long" + }, + "count_rules_executions_failured_by_reason_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "read": { + "type": "long" + }, + "decrypt": { + "type": "long" + }, + "license": { + "type": "long" + }, + "unknown": { + "type": "long" + } + } + }, + "count_rules_executions_failured_by_reason_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "read": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } }, - "__geo-containment": { - "type": "long" + "decrypt": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } }, - "xpack__ml__anomaly_detection_alert": { - "type": "long" + "license": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } }, - "xpack__ml__anomaly_detection_jobs_health": { - "type": "long" + "unknown": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } } } }, - "count_by_type": { + "avg_execution_time_per_day": { + "type": "long" + }, + "avg_execution_time_by_type_per_day": { "properties": { "DYNAMIC_KEY": { "type": "long" @@ -349,9 +1108,6 @@ "type": "long" } } - }, - "count_rules_namespaces": { - "type": "long" } } }, @@ -6930,12 +7686,6 @@ "description": "Indicates if audit logging is both enabled and supported by the current license." } }, - "auditLoggingType": { - "type": "keyword", - "_meta": { - "description": "If auditLoggingEnabled is true, indicates what type is enabled (ECS or legacy)." - } - }, "loginSelectorEnabled": { "type": "boolean", "_meta": { diff --git a/x-pack/plugins/timelines/common/constants.ts b/x-pack/plugins/timelines/common/constants.ts index 262ab841492e35..bc22c761c24e04 100644 --- a/x-pack/plugins/timelines/common/constants.ts +++ b/x-pack/plugins/timelines/common/constants.ts @@ -22,3 +22,5 @@ export const FILTER_ACKNOWLEDGED: AlertStatus = 'acknowledged'; export const RAC_ALERTS_BULK_UPDATE_URL = '/internal/rac/alerts/bulk_update'; export const DETECTION_ENGINE_SIGNALS_STATUS_URL = '/api/detection_engine/signals/status'; + +export const DELETED_SECURITY_SOLUTION_DATA_VIEW = 'DELETED_SECURITY_SOLUTION_DATA_VIEW'; diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 2242a6951c7502..004742c3ee3d4d 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -8,6 +8,8 @@ // TODO: https://github.com/elastic/kibana/issues/110904 /* eslint-disable @kbn/eslint/no_export_all */ +export { DELETED_SECURITY_SOLUTION_DATA_VIEW } from './constants'; + export * from './types'; export * from './search_strategy'; export * from './utils/accessibility'; diff --git a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts index d207cf21fcb362..3579a45d2fcbbb 100644 --- a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts @@ -5,13 +5,18 @@ * 2.0. */ -import { IFieldSubType, IndexPatternBase } from '@kbn/es-query'; -import { IEsSearchRequest, IEsSearchResponse } from '../../../../../../src/plugins/data/common'; -import { DocValueFields, Maybe } from '../common'; +import type { IFieldSubType } from '@kbn/es-query'; +import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { + IEsSearchRequest, + IEsSearchResponse, +} from '../../../../../../src/plugins/data/common'; +import type { DocValueFields, Maybe } from '../common'; +import { FieldSpec } from '../../../../../../src/plugins/data/common'; export type BeatFieldsFactoryQueryType = 'beatFields'; -interface FieldInfo { +export interface FieldInfo { category: string; description?: string; example?: string | number; @@ -20,40 +25,37 @@ interface FieldInfo { type?: string; } -export interface IndexField { +export interface IndexField extends Omit { /** Where the field belong */ category: string; /** Example of field's value */ example?: Maybe; /** whether the field's belong to an alias index */ indexes: Array>; - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; /** Description of the field */ description?: Maybe; format?: Maybe; - /** the elastic type as mapped in the index */ - esTypes?: string[]; - subType?: IFieldSubType; - readFromDocValues: boolean; } export type BeatFields = Record; -export interface IndexFieldsStrategyRequest extends IEsSearchRequest { +export interface IndexFieldsStrategyRequestByIndices extends IEsSearchRequest { indices: string[]; onlyCheckIfIndicesExist: boolean; } +export interface IndexFieldsStrategyRequestById extends IEsSearchRequest { + dataViewId: string; + onlyCheckIfIndicesExist: boolean; +} + +export type IndexFieldsStrategyRequest = T extends 'dataView' + ? IndexFieldsStrategyRequestById + : IndexFieldsStrategyRequestByIndices; export interface IndexFieldsStrategyResponse extends IEsSearchResponse { indexFields: IndexField[]; indicesExist: string[]; + runtimeMappings: MappingRuntimeFields; } export interface BrowserField { @@ -68,13 +70,11 @@ export interface BrowserField { searchable: boolean; type: string; subType?: IFieldSubType; + readFromDocValues: boolean; } export type BrowserFields = Readonly>>; export const EMPTY_BROWSER_FIELDS = {}; export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; -export const EMPTY_INDEX_PATTERN: IndexPatternBase = { - fields: [], - title: '', -}; +export const EMPTY_INDEX_FIELDS: FieldSpec[] = []; diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts index 4bb9928aa6b975..9ff370482ef08a 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts @@ -7,6 +7,7 @@ import { JsonObject } from '@kbn/utility-types'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; import type { Ecs } from '../../../../ecs'; import type { CursorType, Inspect, Maybe, PaginationInputPaginated } from '../../../common'; @@ -38,9 +39,10 @@ export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse { } export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsPaginated { - fields: string[] | Array<{ field: string; include_unmapped: boolean }>; + authFilter?: JsonObject; + excludeEcsData?: boolean; fieldRequested: string[]; + fields: string[] | Array<{ field: string; include_unmapped: boolean }>; language: 'eql' | 'kuery' | 'lucene'; - excludeEcsData?: boolean; - authFilter?: JsonObject; + runtimeMappings: MappingRuntimeFields; } diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts index 9a2d884af948fa..8673359d230b49 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/last_event_time/index.ts @@ -26,6 +26,7 @@ export interface TimelineEventsLastEventTimeStrategyResponse extends IEsSearchRe lastSeen: Maybe; inspect?: Maybe; } +export type TimelineKpiStrategyRequest = Omit; export interface TimelineKpiStrategyResponse extends IEsSearchResponse { destinationIpCount: number; @@ -37,7 +38,7 @@ export interface TimelineKpiStrategyResponse extends IEsSearchResponse { } export interface TimelineEventsLastEventTimeRequestOptions - extends Omit { + extends Omit { indexKey: LastEventIndexKey; details: LastTimeDetails; } diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts index 7cf5b6b22d1634..f576392910495d 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; import { ESQuery } from '../../typed_json'; import { @@ -43,6 +44,7 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest { docValueFields?: DocValueFields[]; factoryQueryType?: TimelineFactoryQueryTypes; entityType?: EntityType; + runtimeMappings: MappingRuntimeFields; } export interface TimelineRequestSortField extends SortField { diff --git a/x-pack/plugins/timelines/common/types/timeline/cells/index.ts b/x-pack/plugins/timelines/common/types/timeline/cells/index.ts index ce22d87c0a59a2..e922fed97a4df0 100644 --- a/x-pack/plugins/timelines/common/types/timeline/cells/index.ts +++ b/x-pack/plugins/timelines/common/types/timeline/cells/index.ts @@ -23,6 +23,7 @@ export type CellValueElementProps = EuiDataGridCellValueElementProps & { globalFilters?: Filter[]; header: ColumnHeaderOptions; isDraggable: boolean; + isTimeline?: boolean; // Default cell renderer is used for both the alert table and timeline. This allows us to cheaply separate concerns linkValues: string[] | undefined; rowRenderers?: RowRenderer[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/timelines/kibana.json b/x-pack/plugins/timelines/kibana.json index 0239dcdd8f1660..11adf42b3a6b40 100644 --- a/x-pack/plugins/timelines/kibana.json +++ b/x-pack/plugins/timelines/kibana.json @@ -11,5 +11,5 @@ "server": true, "ui": true, "requiredPlugins": ["alerting", "cases", "data", "dataEnhanced", "kibanaReact", "kibanaUtils"], - "optionalPlugins": [] + "optionalPlugins": ["security"] } diff --git a/x-pack/plugins/timelines/public/components/clipboard/with_copy_to_clipboard.tsx b/x-pack/plugins/timelines/public/components/clipboard/with_copy_to_clipboard.tsx index a62f52c27cf702..714e2c5fcb8fe2 100644 --- a/x-pack/plugins/timelines/public/components/clipboard/with_copy_to_clipboard.tsx +++ b/x-pack/plugins/timelines/public/components/clipboard/with_copy_to_clipboard.tsx @@ -26,7 +26,7 @@ export const WithCopyToClipboard = React.memo<{ showTooltip?: boolean; text: string; titleSummary?: string; -}>(({ isHoverAction, keyboardShortcut = '', showTooltip = false, text, titleSummary }) => { +}>(({ isHoverAction, keyboardShortcut = '', showTooltip = true, text, titleSummary }) => { return showTooltip ? ( { const filterOutValueFn = useCallback(() => { const makeFilter = (currentVal: string | null | undefined) => - currentVal?.length === 0 + currentVal == null || currentVal?.length === 0 ? createFilter(field, null, false) : createFilter(field, currentVal, true); const filters = Array.isArray(value) diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 9e43c16fd5e6ff..29766a5b8a1f5b 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -146,7 +146,14 @@ const EuiDataGridContainer = styled.div<{ hideLastPage: boolean }>` } `; -const FIELDS_WITHOUT_CELL_ACTIONS = ['@timestamp', 'signal.rule.risk_score', 'signal.reason']; +// TODO: accept extra list of column ids without actions from callsites +const FIELDS_WITHOUT_CELL_ACTIONS = [ + '@timestamp', + 'signal.rule.risk_score', + 'signal.reason', + 'kibana.alert.duration.us', + 'kibana.alert.reason', +]; const hasCellActions = (columnId?: string) => columnId && FIELDS_WITHOUT_CELL_ACTIONS.indexOf(columnId) < 0; const transformControlColumns = ({ diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/sort/index.ts b/x-pack/plugins/timelines/public/components/t_grid/body/sort/index.ts index e514e4f4e2618f..9e653e1b2bb8ea 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/sort/index.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/sort/index.ts @@ -9,7 +9,7 @@ import { SortDirection } from '../../../../../common/types/timeline'; import type { SortColumnTimeline } from '../../../../../common/types/timeline'; // TODO: Cleanup this type to match SortColumnTimeline -export { SortDirection }; +export type { SortDirection }; /** Specifies which column the timeline is sorted on */ export type Sort = SortColumnTimeline; diff --git a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx index e2eb1d4d04547f..617c166c373849 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx @@ -6,7 +6,7 @@ */ import type { Filter, EsQueryConfig, Query } from '@kbn/es-query'; -import { FilterStateStore } from '@kbn/es-query'; +import { DataViewBase, FilterStateStore } from '@kbn/es-query'; import { isEmpty, get } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; @@ -17,7 +17,6 @@ import { handleSkipFocus, stopPropagationAndPreventDefault, } from '../../../common'; -import { IIndexPattern } from '../../../../../../src/plugins/data/public'; import type { BrowserFields } from '../../../common/search_strategy/index_fields'; import { DataProviderType, EXISTS_OPERATOR } from '../../../common/types/timeline'; import type { DataProvider, DataProvidersAnd } from '../../../common/types/timeline'; @@ -138,7 +137,7 @@ export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: B interface CombineQueries { config: EsQueryConfig; dataProviders: DataProvider[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; browserFields: BrowserFields; filters: Filter[]; kqlQuery: Query; diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index e363297d04be5e..614857c26cbb1a 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -14,6 +14,8 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { Direction, EntityType } from '../../../../common/search_strategy'; import type { DocValueFields } from '../../../../common/search_strategy'; @@ -34,13 +36,7 @@ import type { RowRenderer, AlertStatus, } from '../../../../common/types/timeline'; -import { - esQuery, - Filter, - IIndexPattern, - Query, - DataPublicPluginStart, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery, DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; import { useDeepEqualSelector } from '../../../hooks/use_selector'; import { defaultHeaders } from '../body/column_headers/default_headers'; import { buildCombinedQuery, getCombinedFilterQuery, resolverIsShowing } from '../helpers'; @@ -119,7 +115,7 @@ export interface TGridIntegratedProps { height?: number; id: TimelineId; indexNames: string[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isLive: boolean; isLoadingIndexPattern: boolean; itemsPerPage: number; @@ -130,6 +126,7 @@ export interface TGridIntegratedProps { query: Query; renderCellValue: (props: CellValueElementProps) => React.ReactNode; rowRenderers: RowRenderer[]; + runtimeMappings: MappingRuntimeFields; setQuery: (inspect: InspectResponse, loading: boolean, refetch: Refetch) => void; sort: Sort[]; start: string; @@ -168,6 +165,7 @@ const TGridIntegratedComponent: React.FC = ({ query, renderCellValue, rowRenderers, + runtimeMappings, setQuery, sort, start, @@ -241,6 +239,7 @@ const TGridIntegratedComponent: React.FC = ({ id, indexNames, limit: itemsPerPage, + runtimeMappings, skip: !canQueryTimeline, sort: sortField, startDate: start, diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index ae092d8634bb72..4ae8e1e191d801 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -9,6 +9,7 @@ import { isEmpty } from 'lodash/fp'; import React, { useEffect, useMemo, useState, useRef } from 'react'; import styled from 'styled-components'; import { useDispatch, useSelector } from 'react-redux'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { Direction, EntityType } from '../../../../common/search_strategy'; import type { CoreStart } from '../../../../../../../src/core/public'; @@ -107,6 +108,7 @@ export interface TGridStandaloneProps { onRuleChange?: () => void; renderCellValue: (props: CellValueElementProps) => React.ReactNode; rowRenderers: RowRenderer[]; + runtimeMappings: MappingRuntimeFields; setRefetch: (ref: () => void) => void; start: string; sort: SortColumnTimeline[]; @@ -138,6 +140,7 @@ const TGridStandaloneComponent: React.FC = ({ query, renderCellValue, rowRenderers, + runtimeMappings, setRefetch, start, sort, @@ -221,6 +224,7 @@ const TGridStandaloneComponent: React.FC = ({ id: STANDALONE_ID, indexNames, limit: itemsPerPageStore, + runtimeMappings, sort: sortField, startDate: start, endDate: end, diff --git a/x-pack/plugins/timelines/public/components/utils/keury/index.ts b/x-pack/plugins/timelines/public/components/utils/keury/index.ts index 3eb03cc63543e7..776f5883a57d53 100644 --- a/x-pack/plugins/timelines/public/components/utils/keury/index.ts +++ b/x-pack/plugins/timelines/public/components/utils/keury/index.ts @@ -11,14 +11,14 @@ import { EsQueryConfig, Filter, fromKueryExpression, - IndexPatternBase, + DataViewBase, Query, toElasticsearchQuery, } from '@kbn/es-query'; export const convertKueryToElasticSearchQuery = ( kueryExpression: string, - indexPattern?: IndexPatternBase + indexPattern?: DataViewBase ) => { try { return kueryExpression @@ -29,10 +29,7 @@ export const convertKueryToElasticSearchQuery = ( } }; -export const convertKueryToDslFilter = ( - kueryExpression: string, - indexPattern: IndexPatternBase -) => { +export const convertKueryToDslFilter = (kueryExpression: string, indexPattern: DataViewBase) => { try { return kueryExpression ? toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern) @@ -74,7 +71,7 @@ export const convertToBuildEsQuery = ({ filters, }: { config: EsQueryConfig; - indexPattern: IndexPatternBase; + indexPattern: DataViewBase; queries: Query[]; filters: Filter[]; }) => { diff --git a/x-pack/plugins/timelines/public/container/index.tsx b/x-pack/plugins/timelines/public/container/index.tsx index 30245ea9f17afc..fa27b8264a38eb 100644 --- a/x-pack/plugins/timelines/public/container/index.tsx +++ b/x-pack/plugins/timelines/public/container/index.tsx @@ -11,6 +11,7 @@ import { isEmpty, isString, noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Subscription } from 'rxjs'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { tGridActions } from '..'; import { @@ -69,22 +70,23 @@ type TimelineRequest = TimelineEventsAllRequestO type TimelineResponse = TimelineEventsAllStrategyResponse; export interface UseTimelineEventsProps { + alertConsumers?: AlertConsumers[]; + data?: DataPublicPluginStart; docValueFields?: DocValueFields[]; - filterQuery?: ESQuery | string; - skip?: boolean; endDate: string; entityType: EntityType; excludeEcsData?: boolean; - id: string; fields: string[]; + filterQuery?: ESQuery | string; + id: string; indexNames: string[]; language?: KueryFilterQueryKind; limit: number; + runtimeMappings: MappingRuntimeFields; + skip?: boolean; sort?: TimelineRequestSortField[]; startDate: string; timerangeKind?: 'absolute' | 'relative'; - data?: DataPublicPluginStart; - alertConsumers?: AlertConsumers[]; } const createFilter = (filterQuery: ESQuery | string | undefined) => @@ -125,6 +127,7 @@ export const useTimelineEvents = ({ startDate, language = 'kuery', limit, + runtimeMappings, sort = initSortDefault, skip = false, timerangeKind, @@ -267,6 +270,7 @@ export const useTimelineEvents = ({ querySize: prevRequest?.pagination.querySize ?? 0, sort: prevRequest?.sort ?? initSortDefault, timerange: prevRequest?.timerange ?? {}, + runtimeMappings: prevRequest?.runtimeMappings ?? {}, }; const currentSearchParameters = { @@ -299,6 +303,7 @@ export const useTimelineEvents = ({ querySize: limit, }, language, + runtimeMappings, sort, timerange: { interval: '12h', @@ -330,6 +335,7 @@ export const useTimelineEvents = ({ startDate, sort, fields, + runtimeMappings, ]); useEffect(() => { diff --git a/x-pack/plugins/timelines/public/container/source/index.tsx b/x-pack/plugins/timelines/public/container/source/index.tsx index 1948da0974438e..f66eedb94f8815 100644 --- a/x-pack/plugins/timelines/public/container/source/index.tsx +++ b/x-pack/plugins/timelines/public/container/source/index.tsx @@ -10,6 +10,7 @@ import { isEmpty, isEqual, pick } from 'lodash/fp'; import { Subscription } from 'rxjs/internal/Subscription'; import memoizeOne from 'memoize-one'; +import { DataViewBase } from '@kbn/es-query'; import { BrowserField, BrowserFields, @@ -21,7 +22,6 @@ import { import * as i18n from './translations'; import { - IIndexPattern, DataPublicPluginStart, isCompleteResponse, isErrorResponse, @@ -37,7 +37,7 @@ interface FetchIndexReturn { docValueFields: DocValueFields[]; indexes: string[]; indexExists: boolean; - indexPatterns: IIndexPattern; + indexPatterns: DataViewBase; } /** @@ -90,7 +90,7 @@ export const getDocValueFields = memoizeOne( ); export const getIndexFields = memoizeOne( - (title: string, fields: IndexField[]): IIndexPattern => + (title: string, fields: IndexField[]): DataViewBase => fields && fields.length > 0 ? { fields: fields.map((field) => @@ -127,7 +127,7 @@ export const useFetchIndex = ( abortCtrl.current = new AbortController(); setLoading(true); searchSubscription$.current = data.search - .search( + .search, IndexFieldsStrategyResponse>( { indices: iNames, onlyCheckIfIndicesExist }, { abortSignal: abortCtrl.current.signal, diff --git a/x-pack/plugins/timelines/public/container/use_update_alerts.ts b/x-pack/plugins/timelines/public/container/use_update_alerts.ts index 37a1fe1671fbdb..b5d76f2fe324a9 100644 --- a/x-pack/plugins/timelines/public/container/use_update_alerts.ts +++ b/x-pack/plugins/timelines/public/container/use_update_alerts.ts @@ -40,14 +40,15 @@ export const useUpdateAlertsStatus = ( return { updateAlertStatus: async ({ status, index, query }) => { if (useDetectionEngine) { - return http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { + return http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', body: JSON.stringify({ status, query }), }); } else { - const { body } = await http.post(RAC_ALERTS_BULK_UPDATE_URL, { - body: JSON.stringify({ index, status, query }), - }); + const { body } = await http.post<{ body: estypes.UpdateByQueryResponse }>( + RAC_ALERTS_BULK_UPDATE_URL, + { body: JSON.stringify({ index, status, query }) } + ); return body; } }, diff --git a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts index d15b4e69807675..34622423781f91 100644 --- a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts +++ b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts @@ -126,7 +126,7 @@ export const useAddToCase = ({ } }, [event]); const isSecurityAlert = useMemo(() => { - return !isEmpty(event?.ecs.signal?.rule?.id); + return !isEmpty(event?.ecs.signal?.rule?.id ?? event?.ecs.kibana?.alert?.rule?.uuid); }, [event]); const isEventSupported = isSecurityAlert || isAlert; const userCanCrud = casePermissions?.crud ?? false; diff --git a/x-pack/plugins/timelines/public/index.ts b/x-pack/plugins/timelines/public/index.ts index 70f7185e9c486d..040137a9ea5451 100644 --- a/x-pack/plugins/timelines/public/index.ts +++ b/x-pack/plugins/timelines/public/index.ts @@ -26,14 +26,14 @@ export type { export { Direction } from '../common/search_strategy/common'; export { tGridReducer } from './store/t_grid/reducer'; export type { TGridModelForTimeline, TimelineState, TimelinesUIStart } from './types'; -export { TGridType, SortDirection } from './types'; +export type { TGridType, SortDirection } from './types'; +export type { OnColumnFocused } from '../common/utils/accessibility'; export { ARIA_COLINDEX_ATTRIBUTE, ARIA_ROWINDEX_ATTRIBUTE, DATA_COLINDEX_ATTRIBUTE, DATA_ROWINDEX_ATTRIBUTE, FIRST_ARIA_INDEX, - OnColumnFocused, arrayIndexToAriaIndex, elementOrChildrenHasFocus, isArrowDownOrArrowUp, diff --git a/x-pack/plugins/timelines/public/mock/browser_fields.ts b/x-pack/plugins/timelines/public/mock/browser_fields.ts index 6ab06e1be018a6..dd7d211058bad0 100644 --- a/x-pack/plugins/timelines/public/mock/browser_fields.ts +++ b/x-pack/plugins/timelines/public/mock/browser_fields.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { DocValueFields } from '../../common/search_strategy'; import type { BrowserFields } from '../../common/search_strategy/index_fields'; @@ -736,3 +737,12 @@ export const mockDocValueFields: DocValueFields[] = [ format: 'date_time', }, ]; + +export const mockRuntimeMappings: MappingRuntimeFields = { + '@a.runtime.field': { + script: { + source: 'emit("Radical dude: " + doc[\'host.name\'].value)', + }, + type: 'keyword', + }, +}; diff --git a/x-pack/plugins/timelines/public/mock/global_state.ts b/x-pack/plugins/timelines/public/mock/global_state.ts index f83110eeb31352..8bdd190cc85199 100644 --- a/x-pack/plugins/timelines/public/mock/global_state.ts +++ b/x-pack/plugins/timelines/public/mock/global_state.ts @@ -18,6 +18,7 @@ export const mockGlobalState: TimelineState = { end: '2020-07-08T08:20:18.966Z', }, dataProviders: [], + dataViewId: '', deletedEventIds: [], excludedRowRendererIds: [], expandedDetail: {}, diff --git a/x-pack/plugins/timelines/public/mock/index_pattern.ts b/x-pack/plugins/timelines/public/mock/index_pattern.ts index 361dbf71bd6f47..893ed1e8899251 100644 --- a/x-pack/plugins/timelines/public/mock/index_pattern.ts +++ b/x-pack/plugins/timelines/public/mock/index_pattern.ts @@ -5,105 +5,73 @@ * 2.0. */ -import { IIndexPattern } from '../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; -export const mockIndexPattern: IIndexPattern = { +export const mockIndexPattern: DataViewBase = { fields: [ { name: '@timestamp', - searchable: true, type: 'date', - aggregatable: true, }, { name: '@version', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.ephemeral_id', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.hostname', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.id', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test1', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test2', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test3', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test4', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test5', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test6', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test7', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test8', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'host.name', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'nestedField.firstAttributes', - searchable: true, type: 'string', - aggregatable: false, }, { name: 'nestedField.secondAttributes', - searchable: true, type: 'string', - aggregatable: false, }, ], title: 'filebeat-*,auditbeat-*,packetbeat-*', diff --git a/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts b/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts index c6f13f83812c54..e24d9d5c45768b 100644 --- a/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts +++ b/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts @@ -1549,6 +1549,7 @@ export const mockTgridModel: TGridModel = { }, ], dataProviders: [], + dataViewId: '', defaultColumns: [], queryFields: [], dateRange: { diff --git a/x-pack/plugins/timelines/public/mock/t_grid.tsx b/x-pack/plugins/timelines/public/mock/t_grid.tsx index 3ae1a1d53c2072..c79d0f406ec629 100644 --- a/x-pack/plugins/timelines/public/mock/t_grid.tsx +++ b/x-pack/plugins/timelines/public/mock/t_grid.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ALERT_START, ALERT_STATUS } from '@kbn/rule-data-utils'; import { TGridIntegratedProps } from '../components/t_grid/integrated'; -import { mockBrowserFields, mockDocValueFields } from './browser_fields'; +import { mockBrowserFields, mockDocValueFields, mockRuntimeMappings } from './browser_fields'; import { mockDataProviders } from './mock_data_providers'; import { mockTimelineData } from './mock_timeline_data'; import { ColumnHeaderOptions, TimelineId } from '../../common'; @@ -114,6 +114,7 @@ export const tGridIntegratedProps: TGridIntegratedProps = { }, renderCellValue: () => null, rowRenderers: [], + runtimeMappings: mockRuntimeMappings, setQuery: () => null, sort: [ { diff --git a/x-pack/plugins/timelines/public/store/t_grid/defaults.ts b/x-pack/plugins/timelines/public/store/t_grid/defaults.ts index efb8a8c9b3b729..87a129afbd4993 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/defaults.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/defaults.ts @@ -63,6 +63,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ export const tGridDefaults: SubsetTGridModel = { columns: defaultHeaders, defaultColumns: defaultHeaders, + dataViewId: '', dateRange: { start: '', end: '' }, deletedEventIds: [], excludedRowRendererIds: [], diff --git a/x-pack/plugins/timelines/public/store/t_grid/model.ts b/x-pack/plugins/timelines/public/store/t_grid/model.ts index dc6945d3fe3ad7..739bcbef3e20c5 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/model.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/model.ts @@ -48,6 +48,8 @@ export interface TGridModel extends TGridModelSettings { start: string; end: string; }; + /** Kibana data view id **/ + dataViewId: string; /** Events to not be rendered **/ deletedEventIds: string[]; /** This holds the view information for the flyout when viewing timeline in a consuming view (i.e. hosts page) or the side panel in the primary timeline view */ @@ -91,6 +93,7 @@ export type TGridModelForTimeline = Pick< | 'defaultColumns' | 'dataProviders' | 'dateRange' + | 'dataViewId' | 'deletedEventIds' | 'documentType' | 'excludedRowRendererIds' @@ -124,6 +127,7 @@ export type SubsetTGridModel = Readonly< TGridModel, | 'columns' | 'defaultColumns' + | 'dataViewId' | 'dateRange' | 'deletedEventIds' | 'excludedRowRendererIds' diff --git a/x-pack/plugins/timelines/server/index.ts b/x-pack/plugins/timelines/server/index.ts index ef18226a0e60c7..e14ac343ed8152 100644 --- a/x-pack/plugins/timelines/server/index.ts +++ b/x-pack/plugins/timelines/server/index.ts @@ -12,4 +12,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new TimelinesPlugin(initializerContext); } -export { TimelinesPluginUI, TimelinesPluginStart } from './types'; +export type { TimelinesPluginUI, TimelinesPluginStart } from './types'; diff --git a/x-pack/plugins/timelines/server/plugin.ts b/x-pack/plugins/timelines/server/plugin.ts index 79d35e53fada11..eb172b7d27bd6a 100644 --- a/x-pack/plugins/timelines/server/plugin.ts +++ b/x-pack/plugins/timelines/server/plugin.ts @@ -18,11 +18,13 @@ import { defineRoutes } from './routes'; import { timelineSearchStrategyProvider } from './search_strategy/timeline'; import { timelineEqlSearchStrategyProvider } from './search_strategy/timeline/eql'; import { indexFieldsProvider } from './search_strategy/index_fields'; +import { SecurityPluginSetup } from '../../security/server'; export class TimelinesPlugin implements Plugin { private readonly logger: Logger; + private security?: SecurityPluginSetup; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -30,19 +32,22 @@ export class TimelinesPlugin public setup(core: CoreSetup, plugins: SetupPlugins) { this.logger.debug('timelines: Setup'); + this.security = plugins.security; + const router = core.http.createRouter(); // Register server side APIs defineRoutes(router); + const IndexFields = indexFieldsProvider(core.getStartServices); // Register search strategy core.getStartServices().then(([_, depsStart]) => { const TimelineSearchStrategy = timelineSearchStrategyProvider( depsStart.data, - depsStart.alerting + depsStart.alerting, + this.security ); const TimelineEqlSearchStrategy = timelineEqlSearchStrategyProvider(depsStart.data); - const IndexFields = indexFieldsProvider(); plugins.data.search.registerSearchStrategy('indexFields', IndexFields); plugins.data.search.registerSearchStrategy('timelineSearchStrategy', TimelineSearchStrategy); diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts index e8df6200351ea2..be5bff2e30ddff 100644 --- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts @@ -795,19 +795,56 @@ describe('Fields Provider', () => { describe('search', () => { const getFieldsForWildcardMock = jest.fn(); const esClientSearchMock = jest.fn(); + const esClientFieldCapsMock = jest.fn(); + const mockPattern = { + title: 'coolbro', + fields: { + toSpec: () => ({ + coolio: { + name: 'nameio', + type: 'typeio', + searchable: true, + aggregatable: true, + }, + }), + }, + toSpec: () => ({ + runtimeFieldMap: { runtimeField: { type: 'keyword' } }, + }), + }; + const getStartServices = jest.fn().mockReturnValue([ + null, + { + data: { + indexPatterns: { + indexPatternsServiceFactory: () => ({ + get: jest.fn().mockReturnValue(mockPattern), + }), + }, + }, + }, + ]); const deps = { - esClient: { asCurrentUser: { search: esClientSearchMock } }, + esClient: { asCurrentUser: { search: esClientSearchMock, fieldCaps: esClientFieldCapsMock } }, } as unknown as SearchStrategyDependencies; beforeAll(() => { getFieldsForWildcardMock.mockResolvedValue([]); + + esClientSearchMock.mockResolvedValue({ + body: { hits: { total: { value: 123 } } }, + }); + esClientFieldCapsMock.mockResolvedValue({ + body: { indices: ['value'] }, + }); IndexPatternsFetcher.prototype.getFieldsForWildcard = getFieldsForWildcardMock; }); beforeEach(() => { getFieldsForWildcardMock.mockClear(); esClientSearchMock.mockClear(); + esClientFieldCapsMock.mockClear(); }); afterAll(() => { @@ -821,10 +858,7 @@ describe('Fields Provider', () => { onlyCheckIfIndicesExist: true, }; - const response = await requestIndexFieldSearch(request, deps, beatFields); - - expect(getFieldsForWildcardMock).toHaveBeenCalledWith({ pattern: indices[0] }); - + const response = await requestIndexFieldSearch(request, deps, beatFields, getStartServices); expect(response.indexFields).toHaveLength(0); expect(response.indicesExist).toEqual(indices); }); @@ -836,7 +870,7 @@ describe('Fields Provider', () => { onlyCheckIfIndicesExist: false, }; - const response = await requestIndexFieldSearch(request, deps, beatFields); + const response = await requestIndexFieldSearch(request, deps, beatFields, getStartServices); expect(getFieldsForWildcardMock).toHaveBeenCalledWith({ pattern: indices[0] }); @@ -844,6 +878,34 @@ describe('Fields Provider', () => { expect(response.indicesExist).toEqual(indices); }); + it('should search index fields by data view id', async () => { + const dataViewId = 'id'; + const request = { + dataViewId, + onlyCheckIfIndicesExist: false, + }; + + const response = await requestIndexFieldSearch(request, deps, beatFields, getStartServices); + + expect(getFieldsForWildcardMock).not.toHaveBeenCalled(); + + expect(response.indexFields).not.toHaveLength(0); + expect(response.indicesExist).toEqual(['coolbro']); + }); + + it('onlyCheckIfIndicesExist by data view id', async () => { + const dataViewId = 'id'; + const request = { + dataViewId, + onlyCheckIfIndicesExist: true, + }; + + const response = await requestIndexFieldSearch(request, deps, beatFields, getStartServices); + + expect(response.indexFields).toHaveLength(0); + expect(response.indicesExist).toEqual(['coolbro']); + }); + it('should search apm index fields', async () => { const indices = ['apm-*-transaction*', 'traces-apm*']; const request = { @@ -851,11 +913,9 @@ describe('Fields Provider', () => { onlyCheckIfIndicesExist: false, }; - const response = await requestIndexFieldSearch(request, deps, beatFields); + const response = await requestIndexFieldSearch(request, deps, beatFields, getStartServices); expect(getFieldsForWildcardMock).toHaveBeenCalledWith({ pattern: indices[0] }); - expect(esClientSearchMock).not.toHaveBeenCalled(); - expect(response.indexFields).not.toHaveLength(0); expect(response.indicesExist).toEqual(indices); }); @@ -870,7 +930,7 @@ describe('Fields Provider', () => { esClientSearchMock.mockResolvedValue({ body: { hits: { total: { value: 1 } } }, }); - const response = await requestIndexFieldSearch(request, deps, beatFields); + const response = await requestIndexFieldSearch(request, deps, beatFields, getStartServices); expect(esClientSearchMock).toHaveBeenCalledWith({ index: indices[0], @@ -897,7 +957,7 @@ describe('Fields Provider', () => { body: { hits: { total: { value: 0 } } }, }); - const response = await requestIndexFieldSearch(request, deps, beatFields); + const response = await requestIndexFieldSearch(request, deps, beatFields, getStartServices); expect(esClientSearchMock).toHaveBeenCalledWith({ index: indices[0], diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts index 8670b709494f04..2427ec8bb60711 100644 --- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts @@ -8,11 +8,11 @@ import { from } from 'rxjs'; import isEmpty from 'lodash/isEmpty'; import get from 'lodash/get'; +import { ElasticsearchClient, StartServicesAccessor } from 'kibana/server'; import { IndexPatternsFetcher, ISearchStrategy, SearchStrategyDependencies, - FieldDescriptor, } from '../../../../../../src/plugins/data/server'; // TODO cleanup path @@ -21,13 +21,18 @@ import { IndexField, IndexFieldsStrategyRequest, BeatFields, -} from '../../../common/search_strategy/index_fields'; + DELETED_SECURITY_SOLUTION_DATA_VIEW, +} from '../../../common'; +import { StartPlugins } from '../../types'; +import { FieldSpec } from '../../../../../../src/plugins/data_views/common'; const apmIndexPattern = 'apm-*-transaction*'; const apmDataStreamsPattern = 'traces-apm*'; -export const indexFieldsProvider = (): ISearchStrategy< - IndexFieldsStrategyRequest, +export const indexFieldsProvider = ( + getStartServices: StartServicesAccessor +): ISearchStrategy< + IndexFieldsStrategyRequest<'indices' | 'dataView'>, IndexFieldsStrategyResponse > => { // require the fields once we actually need them, rather than ahead of time, and pass @@ -36,61 +41,118 @@ export const indexFieldsProvider = (): ISearchStrategy< const beatFields: BeatFields = require('../../utils/beat_schema/fields').fieldsBeat; return { - search: (request, options, deps) => from(requestIndexFieldSearch(request, deps, beatFields)), + search: (request, options, deps) => + from(requestIndexFieldSearch(request, deps, beatFields, getStartServices)), }; }; -export const requestIndexFieldSearch = async ( - request: IndexFieldsStrategyRequest, - { esClient }: SearchStrategyDependencies, - beatFields: BeatFields -): Promise => { - const indexPatternsFetcherAsCurrentUser = new IndexPatternsFetcher(esClient.asCurrentUser); - const indexPatternsFetcherAsInternalUser = new IndexPatternsFetcher(esClient.asInternalUser); - - const dedupeIndices = dedupeIndexName(request.indices); - - const responsesIndexFields = await Promise.all( - dedupeIndices +export const findExistingIndices = async ( + indices: string[], + esClient: ElasticsearchClient +): Promise => + Promise.all( + indices .map(async (index) => { - if ( - request.onlyCheckIfIndicesExist && - (index.includes(apmIndexPattern) || index.includes(apmDataStreamsPattern)) - ) { - // for apm index pattern check also if there's data https://github.com/elastic/kibana/issues/90661 - const searchResponse = await esClient.asCurrentUser.search({ + if ([apmIndexPattern, apmDataStreamsPattern].includes(index)) { + const searchResponse = await esClient.search({ index, body: { query: { match_all: {} }, size: 0 }, }); return get(searchResponse, 'body.hits.total.value', 0) > 0; - } else { - if (index.startsWith('.alerts-observability')) { - return indexPatternsFetcherAsInternalUser.getFieldsForWildcard({ - pattern: index, - }); - } else { - return indexPatternsFetcherAsCurrentUser.getFieldsForWildcard({ - pattern: index, - }); - } } + const searchResponse = await esClient.fieldCaps({ + index, + fields: '_id', + ignore_unavailable: true, + allow_no_indices: false, + }); + return searchResponse.body.indices.length > 0; }) .map((p) => p.catch((e) => false)) ); +export const requestIndexFieldSearch = async ( + request: IndexFieldsStrategyRequest<'indices' | 'dataView'>, + { savedObjectsClient, esClient }: SearchStrategyDependencies, + beatFields: BeatFields, + getStartServices: StartServicesAccessor +): Promise => { + const indexPatternsFetcherAsCurrentUser = new IndexPatternsFetcher(esClient.asCurrentUser); + const indexPatternsFetcherAsInternalUser = new IndexPatternsFetcher(esClient.asInternalUser); + if ('dataViewId' in request && 'indices' in request) { + throw new Error('Provide index field search with either `dataViewId` or `indices`, not both'); + } + const [ + , + { + data: { indexPatterns }, + }, + ] = await getStartServices(); + const dataViewService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + esClient.asCurrentUser + ); + + let indicesExist: string[] = []; let indexFields: IndexField[] = []; + let runtimeMappings = {}; - if (!request.onlyCheckIfIndicesExist) { - indexFields = await formatIndexFields( - beatFields, - responsesIndexFields.filter((rif) => rif !== false) as FieldDescriptor[][], - dedupeIndices + // if dataViewId is provided, get fields and indices from the Kibana Data View + if ('dataViewId' in request) { + let dataView; + try { + dataView = await dataViewService.get(request.dataViewId); + } catch (r) { + if ( + r.output.payload.statusCode === 404 && + // this is the only place this id is hard coded as there are no security_solution dependencies in timeline + // needs to match value in DEFAULT_DATA_VIEW_ID security_solution/common/constants.ts + r.output.payload.message.indexOf('security-solution') > -1 + ) { + throw new Error(DELETED_SECURITY_SOLUTION_DATA_VIEW); + } else { + throw r; + } + } + + const patternList = dataView.title.split(','); + indicesExist = (await findExistingIndices(patternList, esClient.asCurrentUser)).reduce( + (acc: string[], doesIndexExist, i) => (doesIndexExist ? [...acc, patternList[i]] : acc), + [] + ); + if (!request.onlyCheckIfIndicesExist) { + const dataViewSpec = dataView.toSpec(); + const fieldDescriptor = [Object.values(dataViewSpec.fields ?? {})]; + runtimeMappings = dataViewSpec.runtimeFieldMap ?? {}; + indexFields = await formatIndexFields(beatFields, fieldDescriptor, patternList); + } + } else if ('indices' in request) { + const patternList = dedupeIndexName(request.indices); + indicesExist = (await findExistingIndices(patternList, esClient.asCurrentUser)).reduce( + (acc: string[], doesIndexExist, i) => (doesIndexExist ? [...acc, patternList[i]] : acc), + [] ); + if (!request.onlyCheckIfIndicesExist) { + const fieldDescriptor = await Promise.all( + indicesExist.map(async (index, n) => { + if (index.startsWith('.alerts-observability')) { + return indexPatternsFetcherAsInternalUser.getFieldsForWildcard({ + pattern: index, + }); + } + return indexPatternsFetcherAsCurrentUser.getFieldsForWildcard({ + pattern: index, + }); + }) + ); + indexFields = await formatIndexFields(beatFields, fieldDescriptor, patternList); + } } return { indexFields, - indicesExist: dedupeIndices.filter((index, i) => responsesIndexFields[i] !== false), + runtimeMappings, + indicesExist, rawResponse: { timed_out: false, took: -1, @@ -125,7 +187,7 @@ export const dedupeIndexName = (indices: string[]) => return acc; }, []); -const missingFields: FieldDescriptor[] = [ +const missingFields: FieldSpec[] = [ { name: '_id', type: 'string', @@ -158,7 +220,7 @@ const missingFields: FieldDescriptor[] = [ export const createFieldItem = ( beatFields: BeatFields, indexesAlias: string[], - index: FieldDescriptor, + index: FieldSpec, indexesAliasIdx: number ): IndexField => { const alias = indexesAlias[indexesAliasIdx]; @@ -174,6 +236,9 @@ export const createFieldItem = ( return { ...beatIndex, ...index, + // the format type on FieldSpec is SerializedFieldFormat + // and is a string on beatIndex + format: index.format?.id ?? beatIndex.format, indexes: [alias], }; }; @@ -188,24 +253,24 @@ export const createFieldItem = ( * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs * has already consumed a lot of the event loop processing up to this function and we want to give * I/O opportunity to occur by scheduling this on the next loop. - * @param responsesIndexFields The response index fields to loop over + * @param responsesFieldSpec The response index fields to loop over * @param indexesAlias The index aliases such as filebeat-* */ export const formatFirstFields = async ( beatFields: BeatFields, - responsesIndexFields: FieldDescriptor[][], + responsesFieldSpec: FieldSpec[][], indexesAlias: string[] ): Promise => { return new Promise((resolve) => { setTimeout(() => { resolve( - responsesIndexFields.reduce( - (accumulator: IndexField[], indexFields: FieldDescriptor[], indexesAliasIdx: number) => { + responsesFieldSpec.reduce( + (accumulator: IndexField[], fieldSpec: FieldSpec[], indexesAliasIdx: number) => { missingFields.forEach((index) => { const item = createFieldItem(beatFields, indexesAlias, index, indexesAliasIdx); accumulator.push(item); }); - indexFields.forEach((index) => { + fieldSpec.forEach((index) => { const item = createFieldItem(beatFields, indexesAlias, index, indexesAliasIdx); accumulator.push(item); }); @@ -262,15 +327,15 @@ export const formatSecondFields = async (fields: IndexField[]): Promise => { - const fields = await formatFirstFields(beatFields, responsesIndexFields, indexesAlias); + const fields = await formatFirstFields(beatFields, responsesFieldSpec, indexesAlias); const secondFields = await formatSecondFields(fields); return secondFields; }; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts index 5e56af40aebae5..1b58e63ec87522 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts @@ -5,30 +5,33 @@ * 2.0. */ -import { Direction } from '../../../../common/search_strategy'; +import { Direction, TimelineEqlRequestOptions } from '../../../../common/search_strategy'; import { buildEqlDsl, parseEqlResponse } from './helpers'; import { eventsResponse, sequenceResponse } from './__mocks__'; - +const defaultArgs = { + defaultIndex: ['logs-endpoint.events*'], + docValueFields: [], + runtimeMappings: {}, + fieldRequested: [ + '@timestamp', + 'message', + 'event.category', + 'event.action', + 'host.name', + 'source.ip', + 'destination.ip', + ], + fields: [], + filterQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]', + id: 'FkgzdTM3YXEtUmN1cVI3VS1wZ1lrdkEgVW1GSWZEX2lRZmVwQmw2c1V5RWsyZzoyMzA1MjAzMDM=', + language: 'eql' as TimelineEqlRequestOptions['language'], +}; describe('Search Strategy EQL helper', () => { describe('#buildEqlDsl', () => { it('happy path with no options', () => { expect( buildEqlDsl({ - defaultIndex: ['logs-endpoint.events*'], - docValueFields: [], - fieldRequested: [ - '@timestamp', - 'message', - 'event.category', - 'event.action', - 'host.name', - 'source.ip', - 'destination.ip', - ], - fields: [], - filterQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]', - id: 'FkgzdTM3YXEtUmN1cVI3VS1wZ1lrdkEgVW1GSWZEX2lRZmVwQmw2c1V5RWsyZzoyMzA1MjAzMDM=', - language: 'eql', + ...defaultArgs, pagination: { activePage: 0, querySize: 25 }, sort: [ { @@ -78,21 +81,7 @@ describe('Search Strategy EQL helper', () => { it('happy path with EQL options', () => { expect( buildEqlDsl({ - defaultIndex: ['logs-endpoint.events*'], - docValueFields: [], - fieldRequested: [ - '@timestamp', - 'message', - 'event.category', - 'event.action', - 'host.name', - 'source.ip', - 'destination.ip', - ], - fields: [], - filterQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]', - id: 'FkgzdTM3YXEtUmN1cVI3VS1wZ1lrdkEgVW1GSWZEX2lRZmVwQmw2c1V5RWsyZzoyMzA1MjAzMDM=', - language: 'eql', + ...defaultArgs, pagination: { activePage: 1, querySize: 2 }, sort: [ { @@ -148,21 +137,7 @@ describe('Search Strategy EQL helper', () => { it('format events', async () => { const result = await parseEqlResponse( { - defaultIndex: ['logs-endpoint.events*'], - docValueFields: [], - fieldRequested: [ - '@timestamp', - 'message', - 'event.category', - 'event.action', - 'host.name', - 'source.ip', - 'destination.ip', - ], - fields: [], - filterQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]', - id: 'FkgzdTM3YXEtUmN1cVI3VS1wZ1lrdkEgVW1GSWZEX2lRZmVwQmw2c1V5RWsyZzoyMzA1MjAzMDM=', - language: 'eql', + ...defaultArgs, pagination: { activePage: 0, querySize: 2 }, sort: [ { @@ -439,21 +414,7 @@ describe('Search Strategy EQL helper', () => { it('sequence events', async () => { const result = await parseEqlResponse( { - defaultIndex: ['logs-endpoint.events*'], - docValueFields: [], - fieldRequested: [ - '@timestamp', - 'message', - 'event.category', - 'event.action', - 'host.name', - 'source.ip', - 'destination.ip', - ], - fields: [], - filterQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]', - id: 'FkgzdTM3YXEtUmN1cVI3VS1wZ1lrdkEgVW1GSWZEX2lRZmVwQmw2c1V5RWsyZzoyMzA1MjAzMDM=', - language: 'eql', + ...defaultArgs, pagination: { activePage: 3, querySize: 2 }, sort: [ { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts index 3653cb60d36533..8c4f34e930f8dc 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts @@ -17,14 +17,15 @@ import { import { createQueryFilterClauses } from '../../../../../../server/utils/build_query'; export const buildTimelineEventsAllQuery = ({ + authFilter, defaultIndex, docValueFields, fields, filterQuery, pagination: { activePage, querySize }, + runtimeMappings, sort, timerange, - authFilter, }: Omit) => { const filterClause = [...createQueryFilterClauses(filterQuery)]; @@ -78,6 +79,7 @@ export const buildTimelineEventsAllQuery = ({ filter, }, }, + runtime_mappings: runtimeMappings, from: activePage * querySize, size: querySize, track_total_hits: true, diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts index b60add2515ec93..e2491c403945e0 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts @@ -27,17 +27,27 @@ import { export const timelineEventsDetails: TimelineFactory = { buildDsl: ({ authFilter, ...options }: TimelineEventsDetailsRequestOptions) => { - const { indexName, eventId, docValueFields = [] } = options; - return buildTimelineDetailsQuery(indexName, eventId, docValueFields, authFilter); + const { indexName, eventId, docValueFields = [], runtimeMappings = {} } = options; + return buildTimelineDetailsQuery({ + indexName, + id: eventId, + docValueFields, + runtimeMappings, + authFilter, + }); }, parse: async ( options: TimelineEventsDetailsRequestOptions, response: IEsSearchResponse ): Promise => { - const { indexName, eventId, docValueFields = [] } = options; + const { indexName, eventId, docValueFields = [], runtimeMappings = {} } = options; const { _source, fields, ...hitsData } = cloneDeep(response.rawResponse.hits.hits[0] ?? {}); const inspect = { - dsl: [inspectStringifyObject(buildTimelineDetailsQuery(indexName, eventId, docValueFields))], + dsl: [ + inspectStringifyObject( + buildTimelineDetailsQuery({ indexName, id: eventId, docValueFields, runtimeMappings }) + ), + ], }; if (response.isRunning) { return { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.test.ts index 9066861cdb8189..f34b54f3029a5f 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.test.ts @@ -18,7 +18,12 @@ describe('buildTimelineDetailsQuery', () => { { field: 'agent.name' }, ]; - const query = buildTimelineDetailsQuery(indexName, eventId, docValueFields); + const query = buildTimelineDetailsQuery({ + indexName, + id: eventId, + docValueFields, + runtimeMappings: {}, + }); expect(query).toMatchInlineSnapshot(` Object { @@ -52,6 +57,7 @@ describe('buildTimelineDetailsQuery', () => { ], }, }, + "runtime_mappings": Object {}, }, "ignore_unavailable": true, "index": ".siem-signals-default", diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts index 70b592440468e3..5baa471e5c5268 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/query.events_details.dsl.ts @@ -6,14 +6,22 @@ */ import { JsonObject } from '@kbn/utility-types'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DocValueFields } from '../../../../../../common/search_strategy'; -export const buildTimelineDetailsQuery = ( - indexName: string, - id: string, - docValueFields: DocValueFields[], - authFilter?: JsonObject -) => { +export const buildTimelineDetailsQuery = ({ + authFilter, + docValueFields, + id, + indexName, + runtimeMappings, +}: { + authFilter?: JsonObject; + docValueFields: DocValueFields[]; + id: string; + indexName: string; + runtimeMappings: MappingRuntimeFields; +}) => { const basicFilter = { terms: { _id: [id], @@ -40,6 +48,8 @@ export const buildTimelineDetailsQuery = ( docvalue_fields: docValueFields, query, fields: [{ field: '*', include_unmapped: true }], + // Remove and instead pass index_pattern.id once issue resolved: https://github.com/elastic/kibana/issues/111762 + runtime_mappings: runtimeMappings, _source: true, }, size: 1, diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts index b2e073d3ecf599..24e0edb5ddcc04 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts @@ -32,16 +32,19 @@ import { ENHANCED_ES_SEARCH_STRATEGY, ISearchOptions, } from '../../../../../../src/plugins/data/common'; +import { AuditLogger, SecurityPluginSetup } from '../../../../security/server'; +import { AlertAuditAction, alertAuditEvent } from '../../../../rule_registry/server'; export const timelineSearchStrategyProvider = ( data: PluginStart, - alerting: AlertingPluginStartContract + alerting: AlertingPluginStartContract, + security?: SecurityPluginSetup ): ISearchStrategy, TimelineStrategyResponseType> => { const esAsInternal = data.search.searchAsInternalUser; const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); - return { search: (request, options, deps) => { + const securityAuditLogger = security?.audit.asScoped(deps.request); const factoryQueryType = request.factoryQueryType; const entityType = request.entityType; @@ -59,6 +62,7 @@ export const timelineSearchStrategyProvider = ({ deps, queryFactory, alerting, + auditLogger, }: { es: ISearchStrategy; request: TimelineStrategyRequestType; @@ -111,9 +116,8 @@ const timelineAlertsSearchStrategy = ({ deps: SearchStrategyDependencies; alerting: AlertingPluginStartContract; queryFactory: TimelineFactory; + auditLogger: AuditLogger | undefined; }) => { - // Based on what solution alerts you want to see, figures out what corresponding - // index to query (ex: siem --> .alerts-security.alerts) const indices = request.defaultIndex ?? request.indexType; const requestWithAlertsIndices = { ...request, defaultIndex: indices, indexName: indices }; @@ -133,17 +137,46 @@ const timelineAlertsSearchStrategy = ({ return from(getAuthFilter()).pipe( mergeMap(({ filter }) => { - const dsl = queryFactory.buildDsl({ ...requestWithAlertsIndices, authFilter: filter }); + const dsl = queryFactory.buildDsl({ + ...requestWithAlertsIndices, + authFilter: filter, + }); return es.search({ ...requestWithAlertsIndices, params: dsl }, options, deps); }), map((response) => { + const rawResponse = shimHitsTotal(response.rawResponse, options); + // Do we have to loop over each hit? Yes. + // ecs auditLogger requires that we log each alert independently + if (auditLogger != null) { + rawResponse.hits?.hits?.forEach((hit) => { + auditLogger.log( + alertAuditEvent({ + action: AlertAuditAction.FIND, + id: hit._id, + outcome: 'success', + }) + ); + }); + } + return { ...response, - rawResponse: shimHitsTotal(response.rawResponse, options), + rawResponse, }; }), mergeMap((esSearchRes) => queryFactory.parse(requestWithAlertsIndices, esSearchRes)), catchError((err) => { + // check if auth error, if yes, write to ecs logger + if (auditLogger != null && err?.output?.statusCode === 403) { + auditLogger.log( + alertAuditEvent({ + action: AlertAuditAction.FIND, + outcome: 'failure', + error: err, + }) + ); + } + throw err; }) ); diff --git a/x-pack/plugins/timelines/server/types.ts b/x-pack/plugins/timelines/server/types.ts index 26748c37fa1e1a..f9a80908fbc719 100644 --- a/x-pack/plugins/timelines/server/types.ts +++ b/x-pack/plugins/timelines/server/types.ts @@ -8,6 +8,7 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { DataPluginSetup, DataPluginStart } from '../../../../src/plugins/data/server/plugin'; import { PluginStartContract as AlertingPluginStartContract } from '../../alerting/server'; +import { SecurityPluginSetup } from '../../security/server'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface TimelinesPluginUI {} @@ -16,6 +17,7 @@ export interface TimelinesPluginStart {} export interface SetupPlugins { data: DataPluginSetup; + security?: SecurityPluginSetup; } export interface StartPlugins { diff --git a/x-pack/plugins/transform/common/api_schemas/transforms.ts b/x-pack/plugins/transform/common/api_schemas/transforms.ts index 8867ecb5cc760e..55ea326069f0dd 100644 --- a/x-pack/plugins/transform/common/api_schemas/transforms.ts +++ b/x-pack/plugins/transform/common/api_schemas/transforms.ts @@ -94,6 +94,13 @@ function transformConfigPayloadValidator< } } +export const _metaSchema = schema.object( + {}, + { + unknowns: 'allow', + } +); + // PUT transforms/{transformId} export const putTransformsRequestSchema = schema.object( { @@ -112,6 +119,11 @@ export const putTransformsRequestSchema = schema.object( settings: schema.maybe(settingsSchema), source: sourceSchema, sync: schema.maybe(syncSchema), + /** + * This _meta field stores an arbitrary key-value map + * where keys are strings and values are arbitrary objects (possibly also maps). + */ + _meta: schema.maybe(_metaSchema), }, { validate: transformConfigPayloadValidator, diff --git a/x-pack/plugins/transform/common/shared_imports.ts b/x-pack/plugins/transform/common/shared_imports.ts index 42e77938d9ceca..22201cf7c0757d 100644 --- a/x-pack/plugins/transform/common/shared_imports.ts +++ b/x-pack/plugins/transform/common/shared_imports.ts @@ -5,12 +5,12 @@ * 2.0. */ +export type { ChartData } from '../../ml/common'; export { composeValidators, isPopulatedObject, isRuntimeMappings, patternValidator, - ChartData, } from '../../ml/common'; export { RUNTIME_FIELD_TYPES } from '../../../../src/plugins/data/common'; diff --git a/x-pack/plugins/transform/common/types/transform.ts b/x-pack/plugins/transform/common/types/transform.ts index a478946ff917c5..92ffc0b99bc3dd 100644 --- a/x-pack/plugins/transform/common/types/transform.ts +++ b/x-pack/plugins/transform/common/types/transform.ts @@ -24,6 +24,7 @@ export type TransformBaseConfig = PutTransformsRequestSchema & { create_time?: number; version?: string; alerting_rules?: TransformHealthAlertRule[]; + _meta?: Record; }; export interface PivotConfigDefinition { diff --git a/x-pack/plugins/transform/public/app/common/index.ts b/x-pack/plugins/transform/public/app/common/index.ts index ccd90f87593588..7081b6db2fe405 100644 --- a/x-pack/plugins/transform/public/app/common/index.ts +++ b/x-pack/plugins/transform/public/app/common/index.ts @@ -11,35 +11,46 @@ export { getPivotPreviewDevConsoleStatement, INIT_MAX_COLUMNS, } from './data_grid'; +export type { EsDoc, EsDocSource } from './fields'; export { getDefaultSelectableFields, getFlattenedFields, getSelectableFields, toggleSelectedField, - EsDoc, - EsDocSource, } from './fields'; -export { DropDownLabel, DropDownOption, Label } from './dropdown'; +export type { DropDownLabel, DropDownOption, Label } from './dropdown'; export { isTransformIdValid, refreshTransformList$, useRefreshTransformList, REFRESH_TRANSFORM_LIST_STATE, } from './transform'; -export { TRANSFORM_LIST_COLUMN, TransformListAction, TransformListRow } from './transform_list'; +export type { TransformListAction, TransformListRow } from './transform_list'; +export { TRANSFORM_LIST_COLUMN } from './transform_list'; export { getTransformProgress, isCompletedBatchTransform } from './transform_stats'; -export { - getEsAggFromAggConfig, - isPivotAggsConfigWithUiSupport, - isPivotAggsConfigPercentiles, - PERCENTILES_AGG_DEFAULT_PERCENTS, +export type { PivotAggsConfig, PivotAggsConfigDict, PivotAggsConfigBase, PivotAggsConfigWithUiSupport, PivotAggsConfigWithUiSupportDict, +} from './pivot_aggs'; +export { + getEsAggFromAggConfig, + isPivotAggsConfigWithUiSupport, + isPivotAggsConfigPercentiles, + PERCENTILES_AGG_DEFAULT_PERCENTS, pivotAggsFieldSupport, } from './pivot_aggs'; +export type { + GroupByConfigWithInterval, + GroupByConfigWithUiSupport, + PivotGroupByConfig, + PivotGroupByConfigDict, + PivotGroupByConfigWithUiSupportDict, + PivotSupportedGroupByAggs, + PivotSupportedGroupByAggsWithInterval, +} from './pivot_group_by'; export { dateHistogramIntervalFormatRegex, getEsAggFromGroupByConfig, @@ -49,15 +60,9 @@ export { isGroupByHistogram, isGroupByTerms, pivotGroupByFieldSupport, - GroupByConfigWithInterval, - GroupByConfigWithUiSupport, - PivotGroupByConfig, - PivotGroupByConfigDict, - PivotGroupByConfigWithUiSupportDict, - PivotSupportedGroupByAggs, - PivotSupportedGroupByAggsWithInterval, PIVOT_SUPPORTED_GROUP_BY_AGGS, } from './pivot_group_by'; +export type { PivotQuery, SimpleQuery } from './request'; export { defaultQuery, getPreviewTransformRequestBody, @@ -68,6 +73,4 @@ export { isMatchAllQuery, isSimpleQuery, matchAllQuery, - PivotQuery, - SimpleQuery, } from './request'; diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index 8f8341260bd7eb..184e3d31e89d21 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -242,6 +242,7 @@ export const getCreateTransformRequestBody = ( }, } : {}), + ...(transformDetailsState._meta ? { _meta: transformDetailsState._meta } : {}), // conditionally add additional settings ...getCreateTransformSettingsRequestBody(transformDetailsState), }); diff --git a/x-pack/plugins/transform/public/app/hooks/use_search_items/index.ts b/x-pack/plugins/transform/public/app/hooks/use_search_items/index.ts index eacfa20f3f0735..14f2dde8a3fa7c 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_search_items/index.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_search_items/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { SavedSearchQuery, SearchItems } from './common'; +export type { SavedSearchQuery, SearchItems } from './common'; export { useSearchItems } from './use_search_items'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/index.ts index ac1f427d6bf97c..df038845bc260a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/index.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/index.ts @@ -5,5 +5,7 @@ * 2.0. */ -export { AggListForm, AggListProps } from './list_form'; -export { AggListSummary, AggListSummaryProps } from './list_summary'; +export type { AggListProps } from './list_form'; +export { AggListForm } from './list_form'; +export type { AggListSummaryProps } from './list_summary'; +export { AggListSummary } from './list_summary'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/index.ts index b7461f63f2adb0..c2bd23974c8923 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/index.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/index.ts @@ -6,4 +6,4 @@ */ export { filterAggsFieldSupport, FILTERS } from './constants'; -export { FilterAggType } from './types'; +export type { FilterAggType } from './types'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts index 5014b8a4c80fc3..775401decef352 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts @@ -5,16 +5,12 @@ * 2.0. */ -export { - defaultSearch, - QUERY_LANGUAGE, - QUERY_LANGUAGE_KUERY, - QUERY_LANGUAGE_LUCENE, -} from './constants'; +export type { QUERY_LANGUAGE } from './constants'; +export { defaultSearch, QUERY_LANGUAGE_KUERY, QUERY_LANGUAGE_LUCENE } from './constants'; export { applyTransformConfigToDefineState } from './apply_transform_config_to_define_state'; export { getAggNameConflictToastMessages } from './get_agg_name_conflict_toast_messages'; export { getDefaultAggregationConfig } from './get_default_aggregation_config'; export { getDefaultGroupByConfig } from './get_default_group_by_config'; export { getDefaultStepDefineState } from './get_default_step_define_state'; export { getPivotDropdownOptions } from './get_pivot_dropdown_options'; -export { ErrorMessage, Field, StepDefineExposedState } from './types'; +export type { ErrorMessage, Field, StepDefineExposedState } from './types'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts index 3c0ce70bd09956..5646e420a8ec74 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts @@ -5,13 +5,13 @@ * 2.0. */ +export type { StepDefineExposedState } from './common'; export { defaultSearch, applyTransformConfigToDefineState, getDefaultStepDefineState, - StepDefineExposedState, QUERY_LANGUAGE_KUERY, } from './common'; -export { StepDefineFormHook } from './hooks/use_step_define_form'; +export type { StepDefineFormHook } from './hooks/use_step_define_form'; export { StepDefineForm } from './step_define_form'; export { StepDefineSummary } from './step_define_summary'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts index 39b1a2de26f8ec..21e6bce204ec85 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts @@ -27,6 +27,7 @@ export interface StepDetailsExposedState { transformSettingsDocsPerSecond?: number; valid: boolean; indexPatternTimeField?: string | undefined; + _meta?: Record; } const defaultContinuousModeDelay = '60s'; @@ -94,6 +95,10 @@ export function applyTransformConfigToDetailsState( state.transformSettingsDocsPerSecond = transformConfig.settings.docs_per_second; } } + + if (transformConfig._meta) { + state._meta = transformConfig._meta; + } } return state; } diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts index bbc4b42e1b236e..6045005dd4a0a2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts @@ -5,10 +5,7 @@ * 2.0. */ -export { - applyTransformConfigToDetailsState, - getDefaultStepDetailsState, - StepDetailsExposedState, -} from './common'; +export type { StepDetailsExposedState } from './common'; +export { applyTransformConfigToDetailsState, getDefaultStepDetailsState } from './common'; export { StepDetailsForm } from './step_details_form'; export { StepDetailsSummary } from './step_details_summary'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 416bad15d800a6..eda95013f60bd1 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -289,6 +289,7 @@ export const StepDetailsForm: FC = React.memo( touched: true, valid, indexPatternTimeField, + _meta: defaults._meta, }); // custom comparison /* eslint-disable react-hooks/exhaustive-deps */ diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/index.ts index 8d28d175943ca6..a656966fa8330e 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/index.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { StatsBar, TransformStatsBarStats } from './stats_bar'; +export type { TransformStatsBarStats } from './stats_bar'; +export { StatsBar } from './stats_bar'; diff --git a/x-pack/plugins/transform/public/app/services/es_index_service.ts b/x-pack/plugins/transform/public/app/services/es_index_service.ts index d9014602cc3938..c8d3f625a9281c 100644 --- a/x-pack/plugins/transform/public/app/services/es_index_service.ts +++ b/x-pack/plugins/transform/public/app/services/es_index_service.ts @@ -11,7 +11,7 @@ import { IIndexPattern } from '../../../../../../src/plugins/data/common'; export class IndexService { async canDeleteIndex(http: HttpSetup) { - const privilege = await http.get(`${API_BASE_PATH}privileges`); + const privilege = await http.get<{ hasAllPrivileges: boolean }>(`${API_BASE_PATH}privileges`); if (!privilege) { return false; } diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index b8f5d882058585..c8375af88a2131 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -6,20 +6,20 @@ */ export { XJsonMode } from '@kbn/ace'; -export { UseRequestConfig, useRequest } from '../../../../src/plugins/es_ui_shared/public'; +export type { UseRequestConfig } from '../../../../src/plugins/es_ui_shared/public'; +export { useRequest } from '../../../../src/plugins/es_ui_shared/public'; export { getSavedSearch, getSavedSearchUrlConflictMessage, } from '../../../../src/plugins/discover/public'; -export { - getMlSharedImports, +export type { GetMlSharedImportsReturnType, UseIndexDataReturnType, EsSorting, RenderCellValue, - ES_CLIENT_TOTAL_HITS_RELATION, } from '../../ml/public'; +export { getMlSharedImports, ES_CLIENT_TOTAL_HITS_RELATION } from '../../ml/public'; import { XJson } from '../../../../src/plugins/es_ui_shared/public'; const { expandLiteralStrings, collapseLiteralStrings } = XJson; diff --git a/x-pack/plugins/translations/README.asciidoc b/x-pack/plugins/translations/README.asciidoc index 3690297a097cb1..52ab4b4fda8b85 100644 --- a/x-pack/plugins/translations/README.asciidoc +++ b/x-pack/plugins/translations/README.asciidoc @@ -2,4 +2,4 @@ == Translations plugin Contains Elastic-supported translations. Owned by the Localizations team. -For adding localizations and instrument a ui to support translated content, see https://github.com/elastic/kibana/tree/master/packages/kbn-i18n[kbn-i18n] +For adding localizations and instrument a ui to support translated content, see https://github.com/elastic/kibana/tree/main/packages/kbn-i18n[kbn-i18n] diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ba4626c7261144..661f5420949734 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1311,8 +1311,6 @@ "core.ui_settings.params.defaultRoute.defaultRouteTitle": "デフォルトのルート", "core.ui_settings.params.disableAnimationsText": "Kibana UIの不要なアニメーションをオフにします。変更を適用するにはページを更新してください。", "core.ui_settings.params.disableAnimationsTitle": "アニメーションを無効にする", - "core.ui_settings.params.maxCellHeightText": "表のセルが使用する高さの上限です。この切り捨てを無効にするには0に設定します", - "core.ui_settings.params.maxCellHeightTitle": "表のセルの高さの上限", "core.ui_settings.params.notifications.banner.markdownLinkText": "マークダウン対応", "core.ui_settings.params.notifications.bannerLifetimeText": "バナー通知が画面に表示される時間(ミリ秒単位)です。", "core.ui_settings.params.notifications.bannerLifetimeTitle": "バナー通知時間", @@ -1359,11 +1357,7 @@ "core.ui.primaryNav.pinnedLinksAriaLabel": "ピン留めされたリンク", "core.ui.primaryNav.screenReaderLabel": "プライマリ", "core.ui.primaryNav.toggleNavAriaLabel": "プライマリナビゲーションを切り替える", - "core.ui.primaryNavSection.dockAriaLabel": "プライマリナビゲーションリンクを固定する", - "core.ui.primaryNavSection.dockLabel": "ナビゲーションを固定する", "core.ui.primaryNavSection.screenReaderLabel": "プライマリナビゲーションリンク、{category}", - "core.ui.primaryNavSection.undockAriaLabel": "プライマリナビゲーションリンクの固定を解除する", - "core.ui.primaryNavSection.undockLabel": "ナビゲーションの固定を解除する", "core.ui.publicBaseUrlWarning.configMissingDescription": "{configKey}が見つかりません。本番環境を実行するときに構成してください。一部の機能が正常に動作しない場合があります。", "core.ui.publicBaseUrlWarning.configMissingTitle": "構成がありません", "core.ui.publicBaseUrlWarning.muteWarningButtonLabel": "ミュート警告", @@ -2319,6 +2313,8 @@ "discover.advancedSettings.sortDefaultOrderTitle": "デフォルトの並べ替え方向", "discover.advancedSettings.sortOrderAsc": "昇順", "discover.advancedSettings.sortOrderDesc": "降順", + "discover.advancedSettings.params.maxCellHeightTitle": "表のセルの高さの上限", + "discover.advancedSettings.params.maxCellHeightText": "表のセルが使用する高さの上限です。この切り捨てを無効にするには0に設定します", "discover.backToTopLinkText": "最上部へ戻る。", "discover.badge.readOnly.text": "読み取り専用", "discover.badge.readOnly.tooltip": "検索を保存できません", @@ -4395,7 +4391,6 @@ "share.contextMenu.permalinksLabel": "パーマリンク", "share.contextMenuTitle": "この {objectType} を共有", "share.urlGenerators.error.createUrlFnProvided": "このジェネレーターは非推奨とマークされています。createUrl fn を付けないでください。", - "share.urlGenerators.error.migrateCalledNotDeprecated": "非推奨以外のジェネレーターで migrate を呼び出すことはできません。", "share.urlGenerators.error.migrationFnGivenNotDeprecated": "移行機能を提供する場合、このジェネレーターに非推奨マークを付ける必要があります", "share.urlGenerators.error.noCreateUrlFnProvided": "このジェネレーターには非推奨のマークがありません。createUrl fn を付けてください。", "share.urlGenerators.error.noMigrationFnProvided": "アクセスリンクジェネレーターに非推奨マークが付いている場合、移行機能を提供する必要があります。", @@ -6238,7 +6233,6 @@ "xpack.apm.fleet_integration.settings.apm.defaultServiceEnvironmentTitle": "サービス構成", "xpack.apm.fleet_integration.settings.apm.expvarEnabledDescription": "/debug/varsの下に公開されます", "xpack.apm.fleet_integration.settings.apm.expvarEnabledTitle": "APM Server Golang expvarサポートを有効にする", - "xpack.apm.fleet_integration.settings.apm.hostDescription": "この統合の使用方法を識別できるように、名前と説明を選択してください。", "xpack.apm.fleet_integration.settings.apm.hostLabel": "ホスト", "xpack.apm.fleet_integration.settings.apm.hostTitle": "サーバー構成", "xpack.apm.fleet_integration.settings.apm.idleTimeoutLabel": "基本接続が終了するまでのアイドル時間", @@ -6248,7 +6242,6 @@ "xpack.apm.fleet_integration.settings.apm.maxHeaderBytesLabel": "リクエストヘッダーの最大サイズ(バイト)", "xpack.apm.fleet_integration.settings.apm.maxHeaderBytesTitle": "上限", "xpack.apm.fleet_integration.settings.apm.readTimeoutLabel": "リクエスト全体を読み取る最大期間", - "xpack.apm.fleet_integration.settings.apm.responseHeadersDescription": "リクエストヘッダーサイズおよびタイミング構成の上限を設定します。", "xpack.apm.fleet_integration.settings.apm.responseHeadersHelpText": "セキュリティポリシー遵守目的で使用できます。", "xpack.apm.fleet_integration.settings.apm.responseHeadersLabel": "HTTP応答に追加されたカスタムHTTPヘッダー", "xpack.apm.fleet_integration.settings.apm.responseHeadersTitle": "カスタムヘッダー", @@ -19243,10 +19236,6 @@ "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全ページレイアウト", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "複数のページを使用します。ページごとに最大2のビジュアライゼーションが表示されます", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "印刷用に最適化", - "xpack.reporting.serverConfig.autoSet.sandboxDisabled": "Chromiumサンドボックスは保護が強化されていますが、{osName} OSではサポートされていません。自動的に'{configKey}: true'を設定しています。", - "xpack.reporting.serverConfig.autoSet.sandboxEnabled": "Chromiumサンドボックスは保護が強化され、{osName} OSでサポートされています。自動的にChromiumサンドボックスを有効にしています。", - "xpack.reporting.serverConfig.osDetected": "OSは'{osName}'で実行しています", - "xpack.reporting.serverConfig.randomEncryptionKey": "xpack.reporting.encryptionKey のランダムキーを生成しています。再起動時にセッションが無効にならないようにするには、kibana.yml でxpack.reporting.encryptionKey を設定するか、bin/kibana-encryption-keys コマンドを使用してください。", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG レポート", @@ -20552,8 +20541,6 @@ "xpack.securitySolution.detectionEngine.components.allRules.refreshPromptConfirm": "続行", "xpack.securitySolution.detectionEngine.components.allRules.refreshPromptTitle": "応答してください。", "xpack.securitySolution.detectionEngine.components.importRuleModal.cancelTitle": "キャンセル", - "xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedDetailedTitle": "ルールID:{ruleId}\n ステータスコード:{statusCode}\n メッセージ:{message}", - "xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedTitle": "ルールをインポートできませんでした", "xpack.securitySolution.detectionEngine.components.importRuleModal.importRuleTitle": "ルールのインポート", "xpack.securitySolution.detectionEngine.components.importRuleModal.initialPromptTextDescription": "有効なrules_export.ndjsonファイルを選択するか、ドラッグしてドロップします", "xpack.securitySolution.detectionEngine.components.importRuleModal.overwriteDescription": "競合するルールIDで既存の検出を上書き", @@ -20629,8 +20616,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdLabel": "しきい値", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.importTimelineModalTitle": "保存されたタイムラインからクエリをインポート", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.importTimelineQueryButton": "保存されたタイムラインからクエリをインポート", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesCustomDescription": "インデックスのカスタムリストを入力します", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesFromConfigDescription": "セキュリティソリューション詳細設定からElasticsearchインデックスを使用します", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesHelperDescription": "このルールを実行するElasticsearchインデックスのパターンを入力します。デフォルトでは、セキュリティソリューション詳細設定で定義されたインデックスパターンが含まれます。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.machineLearningJobIdHelpText": "手始めに使えるように、一般的なジョブがいくつか提供されています。独自のカスタムジョブを追加するには、{machineLearning} アプリケーションでジョブに「security」のグループを割り当て、ここに表示されるようにします。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.machineLearningJobIdRequired": "機械学習ジョブが必要です。", @@ -22111,7 +22096,6 @@ "xpack.securitySolution.hostsTable.osTitle": "オペレーティングシステム", "xpack.securitySolution.hostsTable.versionTitle": "バージョン", "xpack.securitySolution.hoverActions.showTopTooltip": "上位の{fieldName}を表示", - "xpack.securitySolution.indexPatterns.allDefault": "すべてのデフォルト", "xpack.securitySolution.indexPatterns.dataSourcesLabel": "データソース", "xpack.securitySolution.indexPatterns.disabled": "このページでは無効なインデックスパターンが推奨されますが、最初にKibanaインデックスパターン設定で構成する必要があります。", "xpack.securitySolution.indexPatterns.help": "データソース選択", @@ -22420,9 +22404,6 @@ "xpack.securitySolution.overview.endpointNotice.tryButton": "Endpoint Securityを試す", "xpack.securitySolution.overview.errorFetchingEvents": "イベントの取得エラー", "xpack.securitySolution.overview.eventsTitle": "イベント数", - "xpack.securitySolution.overview.feedbackText": "Elastic SIEM に関するご意見やご提案は、お気軽に {feedback}", - "xpack.securitySolution.overview.feedbackText.feedbackLinkText": "フィードバックをオンラインで送信", - "xpack.securitySolution.overview.feedbackTitle": "フィードバック", "xpack.securitySolution.overview.filebeatCiscoTitle": "Cisco", "xpack.securitySolution.overview.filebeatNetflowTitle": "Netflow", "xpack.securitySolution.overview.filebeatPanwTitle": "Palo Alto Networks", @@ -22449,11 +22430,6 @@ "xpack.securitySolution.overview.recentTimelinesSidebarTitle": "最近のタイムライン", "xpack.securitySolution.overview.showTopTooltip": "上位の{fieldName}を表示", "xpack.securitySolution.overview.signalCountTitle": "検出アラート傾向", - "xpack.securitySolution.overview.startedText": "セキュリティ情報およびイベント管理(SIEM)へようこそ。はじめに{docs}や{data}をご参照ください。今後の機能に関する情報やチュートリアルは、{siemSolution}ページをご覧ください。", - "xpack.securitySolution.overview.startedText.dataLinkText": "投入データ", - "xpack.securitySolution.overview.startedText.docsLinkText": "ドキュメンテーション", - "xpack.securitySolution.overview.startedText.siemSolutionLinkText": "SIEM ソリューション", - "xpack.securitySolution.overview.startedTitle": "はじめて使う", "xpack.securitySolution.overview.topNLabel": "トップ{fieldName}", "xpack.securitySolution.overview.viewAlertsButtonLabel": "アラートを表示", "xpack.securitySolution.overview.viewEventsButtonLabel": "イベントを表示", @@ -22816,8 +22792,6 @@ "xpack.securitySolution.timelines.allTimelines.errorFetchingTimelinesTitle": "すべてのタイムラインデータをクエリできませんでした", "xpack.securitySolution.timelines.allTimelines.importTimelineTitle": "インポート", "xpack.securitySolution.timelines.allTimelines.panelTitle": "すべてのタイムライン", - "xpack.securitySolution.timelines.components.importTimelineModal.importFailedDetailedTitle": "タイムライン ID:{id}\n ステータスコード:{statusCode}\n メッセージ:{message}", - "xpack.securitySolution.timelines.components.importTimelineModal.importFailedTitle": "インポートできませんでした", "xpack.securitySolution.timelines.components.importTimelineModal.importTimelineTitle": "インポート", "xpack.securitySolution.timelines.components.importTimelineModal.importTitle": "インポート…", "xpack.securitySolution.timelines.components.importTimelineModal.initialPromptTextDescription": "有効な timelines_export.ndjson ファイルを選択するか、またはドラッグアンドドロップします", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 614e5ee55ee7e1..bb446cbd13015b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1327,8 +1327,6 @@ "core.ui_settings.params.defaultRoute.defaultRouteTitle": "默认路由", "core.ui_settings.params.disableAnimationsText": "在 Kibana UI 中关闭所有不必要的动画。刷新页面可应用所做的更改。", "core.ui_settings.params.disableAnimationsTitle": "禁用动画", - "core.ui_settings.params.maxCellHeightText": "表单元格应占用的最大高度。设置为 0 可禁用截断", - "core.ui_settings.params.maxCellHeightTitle": "最大表单元格高度", "core.ui_settings.params.notifications.banner.markdownLinkText": "Markdown 受支持", "core.ui_settings.params.notifications.bannerLifetimeText": "在屏幕上显示横幅通知的时间(毫秒)。", "core.ui_settings.params.notifications.bannerLifetimeTitle": "横幅通知生存时间", @@ -1375,11 +1373,7 @@ "core.ui.primaryNav.pinnedLinksAriaLabel": "置顶链接", "core.ui.primaryNav.screenReaderLabel": "主分片", "core.ui.primaryNav.toggleNavAriaLabel": "切换主导航", - "core.ui.primaryNavSection.dockAriaLabel": "停靠主导航", - "core.ui.primaryNavSection.dockLabel": "停靠导航", "core.ui.primaryNavSection.screenReaderLabel": "主导航链接, {category}", - "core.ui.primaryNavSection.undockAriaLabel": "取消停靠主导航", - "core.ui.primaryNavSection.undockLabel": "取消停靠导航", "core.ui.publicBaseUrlWarning.configMissingDescription": "{configKey} 缺失,在生产环境中运行时应配置。某些功能可能运行不正常。", "core.ui.publicBaseUrlWarning.configMissingTitle": "配置缺失", "core.ui.publicBaseUrlWarning.muteWarningButtonLabel": "静音警告", @@ -2341,6 +2335,8 @@ "discover.advancedSettings.sortDefaultOrderTitle": "默认排序方向", "discover.advancedSettings.sortOrderAsc": "升序", "discover.advancedSettings.sortOrderDesc": "降序", + "discover.advancedSettings.params.maxCellHeightTitle": "最大表单元格高度", + "discover.advancedSettings.params.maxCellHeightText": "表单元格应占用的最大高度。设置为 0 可禁用截断", "discover.backToTopLinkText": "返回顶部。", "discover.badge.readOnly.text": "只读", "discover.badge.readOnly.tooltip": "无法保存搜索", @@ -4438,7 +4434,6 @@ "share.contextMenu.permalinksLabel": "固定链接", "share.contextMenuTitle": "共享此 {objectType}", "share.urlGenerators.error.createUrlFnProvided": "此生成器标记为已过时。切勿提供 createUrl 函数。", - "share.urlGenerators.error.migrateCalledNotDeprecated": "无法在非过时的生成器上调用迁移。", "share.urlGenerators.error.migrationFnGivenNotDeprecated": "如果提供了迁移函数,则必须将此生成器标记为已过时", "share.urlGenerators.error.noCreateUrlFnProvided": "此生成器未标记为已过时。请提供 createUrl 函数。", "share.urlGenerators.error.noMigrationFnProvided": "如果访问链接生成器标记为已过时,则必须提供迁移函数。", @@ -6287,7 +6282,6 @@ "xpack.apm.fleet_integration.settings.apm.defaultServiceEnvironmentTitle": "服务配置", "xpack.apm.fleet_integration.settings.apm.expvarEnabledDescription": "在 /debug/vars 下公开", "xpack.apm.fleet_integration.settings.apm.expvarEnabledTitle": "启用 APM Server Golang expvar 支持", - "xpack.apm.fleet_integration.settings.apm.hostDescription": "选择有助于确定如何使用此集成的名称和描述。", "xpack.apm.fleet_integration.settings.apm.hostLabel": "主机", "xpack.apm.fleet_integration.settings.apm.hostTitle": "服务器配置", "xpack.apm.fleet_integration.settings.apm.idleTimeoutLabel": "基础连接关闭前的空闲时间", @@ -6297,7 +6291,6 @@ "xpack.apm.fleet_integration.settings.apm.maxHeaderBytesLabel": "请求标头的最大大小(字节)", "xpack.apm.fleet_integration.settings.apm.maxHeaderBytesTitle": "限制", "xpack.apm.fleet_integration.settings.apm.readTimeoutLabel": "读取整个请求的最大持续时间", - "xpack.apm.fleet_integration.settings.apm.responseHeadersDescription": "设置请求标头大小限制和计时配置。", "xpack.apm.fleet_integration.settings.apm.responseHeadersHelpText": "可能会用于安全策略合规。", "xpack.apm.fleet_integration.settings.apm.responseHeadersLabel": "添加到 HTTP 响应的定制 HTTP 标头", "xpack.apm.fleet_integration.settings.apm.responseHeadersTitle": "定制标头", @@ -19520,10 +19513,6 @@ "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全页面布局", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "使用多页,每页最多显示 2 个可视化", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "打印优化", - "xpack.reporting.serverConfig.autoSet.sandboxDisabled": "Chromium 沙盒提供附加保护层,但不受 {osName} OS 支持。自动设置“{configKey}: true”。", - "xpack.reporting.serverConfig.autoSet.sandboxEnabled": "Chromium 沙盒提供附加保护层,受 {osName} OS 支持。自动启用 Chromium 沙盒。", - "xpack.reporting.serverConfig.osDetected": "正在以下 OS 上运行:“{osName}”", - "xpack.reporting.serverConfig.randomEncryptionKey": "为 xpack.reporting.encryptionKey 生成随机密钥。为防止会话在重启时失效,请在 kibana.yml 中设置 xpack.reporting.encryptionKey 或使用 bin/kibana-encryption-keys 命令。", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG 报告", @@ -20867,8 +20856,6 @@ "xpack.securitySolution.detectionEngine.components.allRules.refreshPromptConfirm": "继续", "xpack.securitySolution.detectionEngine.components.allRules.refreshPromptTitle": "您还在吗?", "xpack.securitySolution.detectionEngine.components.importRuleModal.cancelTitle": "取消", - "xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedDetailedTitle": "规则 ID:{ruleId}\n 状态代码:{statusCode}\n 消息:{message}", - "xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedTitle": "无法导入规则", "xpack.securitySolution.detectionEngine.components.importRuleModal.importRuleTitle": "导入规则", "xpack.securitySolution.detectionEngine.components.importRuleModal.initialPromptTextDescription": "选择或拖放有效 rules_export.ndjson 文件", "xpack.securitySolution.detectionEngine.components.importRuleModal.overwriteDescription": "覆盖具有冲突规则 ID 的现有检测规则", @@ -20945,8 +20932,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdLabel": "阈值", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.importTimelineModalTitle": "从已保存时间线导入查询", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.importTimelineQueryButton": "从已保存时间线导入查询", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesCustomDescription": "提供定制的索引列表", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesFromConfigDescription": "使用 Security Solution 高级设置中的 Elasticsearch 索引", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesHelperDescription": "输入要运行此规则的 Elasticsearch 索引的模式。默认情况下,将包括 Security Solution 高级设置中定义的索引模式。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.machineLearningJobIdHelpText": "我们提供了一些常见作业来帮助您入门。要添加自己的定制作业,请在 {machineLearning} 应用程序中将一个“security”组分配给这些作业,以使它们显示在此处。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.machineLearningJobIdRequired": "Machine Learning 作业必填。", @@ -22461,7 +22446,6 @@ "xpack.securitySolution.hostsTable.unit": "{totalCount, plural, other {个主机}}", "xpack.securitySolution.hostsTable.versionTitle": "版本", "xpack.securitySolution.hoverActions.showTopTooltip": "显示排名靠前的{fieldName}", - "xpack.securitySolution.indexPatterns.allDefault": "所有默认值", "xpack.securitySolution.indexPatterns.dataSourcesLabel": "数据源", "xpack.securitySolution.indexPatterns.disabled": "在此页面上建议使用已禁用的索引模式,但是首先需要在 Kibana 索引模式设置中配置这些模式", "xpack.securitySolution.indexPatterns.help": "数据源的选择", @@ -22788,9 +22772,6 @@ "xpack.securitySolution.overview.endpointNotice.tryButton": "试用 Endpoint Security", "xpack.securitySolution.overview.errorFetchingEvents": "提取事件时出错", "xpack.securitySolution.overview.eventsTitle": "事件计数", - "xpack.securitySolution.overview.feedbackText": "如果您对 Elastic SIEM 体验有任何建议,请随时{feedback}。", - "xpack.securitySolution.overview.feedbackText.feedbackLinkText": "在线提交反馈", - "xpack.securitySolution.overview.feedbackTitle": "反馈", "xpack.securitySolution.overview.filebeatCiscoTitle": "Cisco", "xpack.securitySolution.overview.filebeatNetflowTitle": "NetFlow", "xpack.securitySolution.overview.filebeatPanwTitle": "Palo Alto Networks", @@ -22819,11 +22800,6 @@ "xpack.securitySolution.overview.recentTimelinesSidebarTitle": "最近的时间线", "xpack.securitySolution.overview.showTopTooltip": "显示排名靠前的{fieldName}", "xpack.securitySolution.overview.signalCountTitle": "检测告警趋势", - "xpack.securitySolution.overview.startedText": "欢迎使用安全信息和事件管理 (SIEM)。首先阅读我们的{docs}或{data}。要了解即将推出的功能和教程,请访问我们的 {siemSolution}页面。", - "xpack.securitySolution.overview.startedText.dataLinkText": "正在采集数据", - "xpack.securitySolution.overview.startedText.docsLinkText": "文档", - "xpack.securitySolution.overview.startedText.siemSolutionLinkText": "SIEM 解决方案", - "xpack.securitySolution.overview.startedTitle": "入门", "xpack.securitySolution.overview.topNLabel": "排名靠前的{fieldName}", "xpack.securitySolution.overview.viewAlertsButtonLabel": "查看告警", "xpack.securitySolution.overview.viewEventsButtonLabel": "查看事件", @@ -23189,8 +23165,6 @@ "xpack.securitySolution.timelines.allTimelines.errorFetchingTimelinesTitle": "无法查询所有时间线数据", "xpack.securitySolution.timelines.allTimelines.importTimelineTitle": "导入", "xpack.securitySolution.timelines.allTimelines.panelTitle": "所有时间线", - "xpack.securitySolution.timelines.components.importTimelineModal.importFailedDetailedTitle": "时间线 ID:{id}\n 状态代码:{statusCode}\n 消息:{message}", - "xpack.securitySolution.timelines.components.importTimelineModal.importFailedTitle": "无法导入", "xpack.securitySolution.timelines.components.importTimelineModal.importTimelineTitle": "导入", "xpack.securitySolution.timelines.components.importTimelineModal.importTitle": "导入……", "xpack.securitySolution.timelines.components.importTimelineModal.initialPromptTextDescription": "搜索或拖放有效的 timelines_export.ndjson 文件", diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index 999d62db304f81..d4967552080fe4 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -256,7 +256,7 @@ Each alert type should be defined as `AlertTypeModel` object with the these prop |requiresAppContext|Define if alert type is enabled for create and edit in the alerting management UI.| IMPORTANT: The current UI supports a single action group only. -Action groups are mapped from the server API result for [GET /api/alerts/list_alert_types: List alert types](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting#get-apialerttypes-list-alert-types). +Action groups are mapped from the server API result for [GET /api/alerts/list_alert_types: List alert types](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#get-apialerttypes-list-alert-types). Server side alert type model: ``` export interface AlertType { @@ -294,7 +294,7 @@ triggersActionsUi.ruleTypeRegistry.register(getSomeNewAlertType()); ## Create and register new alert type UI example -Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting#example +Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#example Alert type UI is expected to be defined as `AlertTypeModel` object. @@ -1149,7 +1149,7 @@ triggersActionsUi.actionTypeRegistry.register(getSomeNewActionType()); ## Create and register new action type UI -Before starting the UI implementation, the [server side registration](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions#action-types) should be done first. +Before starting the UI implementation, the [server side registration](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions#action-types) should be done first. Action type UI is expected to be defined as `ActionTypeModel` object. diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts index d9bad9677c6b8a..a58dd84e9a32b5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts @@ -22,8 +22,6 @@ import { import { getJiraActionType } from './jira'; import { getResilientActionType } from './resilient'; import { getTeamsActionType } from './teams'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ENABLE_ITOM } from '../../../../../actions/server/constants/connectors'; export function registerBuiltInActionTypes({ actionTypeRegistry, @@ -38,13 +36,9 @@ export function registerBuiltInActionTypes({ actionTypeRegistry.register(getSwimlaneActionType()); actionTypeRegistry.register(getWebhookActionType()); actionTypeRegistry.register(getServiceNowITSMActionType()); + actionTypeRegistry.register(getServiceNowITOMActionType()); actionTypeRegistry.register(getServiceNowSIRActionType()); actionTypeRegistry.register(getJiraActionType()); actionTypeRegistry.register(getResilientActionType()); actionTypeRegistry.register(getTeamsActionType()); - - // TODO: Remove when ITOM is ready - if (ENABLE_ITOM) { - actionTypeRegistry.register(getServiceNowITOMActionType()); - } } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts index 38d65b923b3749..91b4f1e343bc73 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts @@ -8,87 +8,96 @@ import { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; import { getIssueTypes, getFieldsByIssueType, getIssues, getIssue } from './api'; -const issueTypesResponse = { - status: 'ok', - data: { - projects: [ - { - issuetypes: [ - { - id: '10006', - name: 'Task', - }, - { - id: '10007', - name: 'Bug', - }, - ], - }, - ], - }, - actionId: 'test', +const issueTypesData = { + projects: [ + { + issuetypes: [ + { + id: '10006', + name: 'Task', + }, + { + id: '10007', + name: 'Bug', + }, + ], + }, + ], }; -const fieldsResponse = { - status: 'ok', - data: { - projects: [ - { - issuetypes: [ - { - id: '10006', - name: 'Task', - fields: { - summary: { fieldId: 'summary' }, - priority: { - fieldId: 'priority', - allowedValues: [ - { - name: 'Highest', - id: '1', - }, - { - name: 'High', - id: '2', - }, - { - name: 'Medium', - id: '3', - }, - { - name: 'Low', - id: '4', - }, - { - name: 'Lowest', - id: '5', - }, - ], - defaultValue: { +const fieldData = { + projects: [ + { + issuetypes: [ + { + id: '10006', + name: 'Task', + fields: { + summary: { fieldId: 'summary' }, + priority: { + fieldId: 'priority', + allowedValues: [ + { + name: 'Highest', + id: '1', + }, + { + name: 'High', + id: '2', + }, + { name: 'Medium', id: '3', }, + { + name: 'Low', + id: '4', + }, + { + name: 'Lowest', + id: '5', + }, + ], + defaultValue: { + name: 'Medium', + id: '3', }, }, }, - ], - }, - ], - actionId: 'test', - }, + }, + ], + }, + ], +}; + +const issueTypesResponse = { + status: 'ok' as const, + connector_id: 'test', + data: issueTypesData, +}; + +const fieldsResponse = { + status: 'ok' as const, + data: fieldData, + connector_id: 'test', +}; + +const singleIssue = { + id: '10267', + key: 'RJ-107', + title: 'some title', }; const issueResponse = { - status: 'ok', - data: { - id: '10267', - key: 'RJ-107', - fields: { summary: 'Test title' }, - }, - actionId: 'test', + status: 'ok' as const, + data: singleIssue, + connector_id: 'test', }; -const issuesResponse = [issueResponse]; +const issuesResponse = { + ...issueResponse, + data: [singleIssue], +}; describe('Jira API', () => { const http = httpServiceMock.createStartContract(); @@ -100,8 +109,11 @@ describe('Jira API', () => { const abortCtrl = new AbortController(); http.post.mockResolvedValueOnce(issueTypesResponse); const res = await getIssueTypes({ http, signal: abortCtrl.signal, connectorId: 'te/st' }); - - expect(res).toEqual(issueTypesResponse); + expect(res).toEqual({ + status: 'ok' as const, + actionId: 'test', + data: issueTypesData, + }); expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"issueTypes","subActionParams":{}}}', signal: abortCtrl.signal, @@ -120,7 +132,7 @@ describe('Jira API', () => { id: '10006', }); - expect(res).toEqual(fieldsResponse); + expect(res).toEqual({ status: 'ok', data: fieldData, actionId: 'test' }); expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"fieldsByIssueType","subActionParams":{"id":"10006"}}}', signal: abortCtrl.signal, @@ -139,7 +151,11 @@ describe('Jira API', () => { title: 'test issue', }); - expect(res).toEqual(issuesResponse); + expect(res).toEqual({ + status: 'ok', + data: [singleIssue], + actionId: 'test', + }); expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"issues","subActionParams":{"title":"test issue"}}}', signal: abortCtrl.signal, @@ -150,7 +166,7 @@ describe('Jira API', () => { describe('getIssue', () => { test('should call get fields API', async () => { const abortCtrl = new AbortController(); - http.post.mockResolvedValueOnce(issuesResponse); + http.post.mockResolvedValueOnce(issueResponse); const res = await getIssue({ http, signal: abortCtrl.signal, @@ -158,7 +174,11 @@ describe('Jira API', () => { id: 'RJ-107', }); - expect(res).toEqual(issuesResponse); + expect(res).toEqual({ + status: 'ok', + data: singleIssue, + actionId: 'test', + }); expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"issue","subActionParams":{"id":"RJ-107"}}}', signal: abortCtrl.signal, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts index 83e126ea9d2f6d..e9cc583ee44acd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts @@ -6,7 +6,10 @@ */ import { HttpSetup } from 'kibana/public'; +import { ActionTypeExecutorResult } from '../../../../../../actions/common'; import { BASE_ACTION_API_PATH } from '../../../constants'; +import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body'; +import { Fields, Issue, IssueTypes } from './types'; export async function getIssueTypes({ http, @@ -16,8 +19,8 @@ export async function getIssueTypes({ http: HttpSetup; signal: AbortSignal; connectorId: string; -}): Promise> { - return await http.post( +}): Promise> { + const res = await http.post>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, { body: JSON.stringify({ @@ -26,6 +29,7 @@ export async function getIssueTypes({ signal, } ); + return rewriteResponseToCamelCase(res); } export async function getFieldsByIssueType({ @@ -38,8 +42,8 @@ export async function getFieldsByIssueType({ signal: AbortSignal; connectorId: string; id: string; -}): Promise> { - return await http.post( +}): Promise> { + const res = await http.post>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, { body: JSON.stringify({ @@ -48,6 +52,7 @@ export async function getFieldsByIssueType({ signal, } ); + return rewriteResponseToCamelCase(res); } export async function getIssues({ @@ -60,8 +65,8 @@ export async function getIssues({ signal: AbortSignal; connectorId: string; title: string; -}): Promise> { - return await http.post( +}): Promise> { + const res = await http.post>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, { body: JSON.stringify({ @@ -70,6 +75,7 @@ export async function getIssues({ signal, } ); + return rewriteResponseToCamelCase(res); } export async function getIssue({ @@ -82,8 +88,8 @@ export async function getIssue({ signal: AbortSignal; connectorId: string; id: string; -}): Promise> { - return await http.post( +}): Promise> { + const res = await http.post>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, { body: JSON.stringify({ @@ -92,4 +98,5 @@ export async function getIssue({ signal, } ); + return rewriteResponseToCamelCase(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx index 1090414104c248..38aed2cfc6f88b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx @@ -8,7 +8,7 @@ import React, { useMemo, useEffect, useCallback, useState, memo } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../types'; import { useGetIssues } from './use_get_issues'; import { useGetSingleIssue } from './use_get_single_issue'; @@ -17,10 +17,7 @@ import * as i18n from './translations'; interface Props { selectedValue?: string | null; http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; actionConnector?: ActionConnector; onChange: (parentIssueKey: string) => void; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts index 01165147a7c0eb..6073971912acc1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts @@ -24,3 +24,18 @@ export interface JiraSecrets { email: string; apiToken: string; } + +export type IssueTypes = Array<{ id: string; name: string }>; + +export interface Issue { + id: string; + key: string; + title: string; +} + +export interface Fields { + [key: string]: { + allowedValues: Array<{ name: string; id: string }> | []; + defaultValue: { name: string; id: string } | {}; + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_fields_by_issue_type.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_fields_by_issue_type.tsx index 61db73c129db6c..71dafef4dca2e3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_fields_by_issue_type.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_fields_by_issue_type.tsx @@ -6,24 +6,15 @@ */ import { useState, useEffect, useRef } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../types'; +import { Fields } from './types'; import { getFieldsByIssueType } from './api'; import * as i18n from './translations'; -interface Fields { - [key: string]: { - allowedValues: Array<{ name: string; id: string }> | []; - defaultValue: { name: string; id: string } | {}; - }; -} - interface Props { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; issueType: string | undefined; actionConnector?: ActionConnector; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issue_types.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issue_types.tsx index 11430c4c372dcc..09ed90b296d06b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issue_types.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issue_types.tsx @@ -6,19 +6,16 @@ */ import { useState, useEffect, useRef } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; + import { ActionConnector } from '../../../../types'; +import { IssueTypes } from './types'; import { getIssueTypes } from './api'; import * as i18n from './translations'; -type IssueTypes = Array<{ id: string; name: string }>; - interface Props { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; actionConnector?: ActionConnector; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issues.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issues.tsx index e0423304325a37..3ad67709f82fbf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issues.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_issues.tsx @@ -7,25 +7,21 @@ import { isEmpty, debounce } from 'lodash/fp'; import { useState, useEffect, useRef } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../types'; +import { Issue } from './types'; import { getIssues } from './api'; import * as i18n from './translations'; -type Issues = Array<{ id: string; key: string; title: string }>; - interface Props { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; actionConnector?: ActionConnector; query: string | null; } export interface UseGetIssues { - issues: Issues; + issues: Issue[]; isLoading: boolean; } @@ -36,7 +32,7 @@ export const useGetIssues = ({ query, }: Props): UseGetIssues => { const [isLoading, setIsLoading] = useState(false); - const [issues, setIssues] = useState([]); + const [issues, setIssues] = useState([]); const abortCtrl = useRef(new AbortController()); useEffect(() => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_single_issue.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_single_issue.tsx index e0099e24f2c5dc..62ddb8b6e362bb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_single_issue.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/use_get_single_issue.tsx @@ -6,23 +6,15 @@ */ import { useState, useEffect, useRef } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../types'; +import { Issue } from './types'; import { getIssue } from './api'; import * as i18n from './translations'; -interface Issue { - id: string; - key: string; - title: string; -} - interface Props { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; id?: string | null; actionConnector?: ActionConnector; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts index 0d4bf9148a92ff..a345a9c81beb07 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts @@ -32,7 +32,6 @@ const incidentTypesResponse = { { id: 16, name: 'TBD / Unknown' }, { id: 15, name: 'Vendor / 3rd party error' }, ], - actionId: 'te/st', }; const severityResponse = { @@ -42,7 +41,6 @@ const severityResponse = { { id: 5, name: 'Medium' }, { id: 6, name: 'High' }, ], - actionId: 'te/st', }; describe('Resilient API', () => { @@ -53,14 +51,14 @@ describe('Resilient API', () => { describe('getIncidentTypes', () => { test('should call get choices API', async () => { const abortCtrl = new AbortController(); - http.post.mockResolvedValueOnce(incidentTypesResponse); + http.post.mockResolvedValueOnce({ ...incidentTypesResponse, connector_id: 'te/st' }); const res = await getIncidentTypes({ http, signal: abortCtrl.signal, connectorId: 'te/st', }); - expect(res).toEqual(incidentTypesResponse); + expect(res).toEqual({ ...incidentTypesResponse, actionId: 'te/st' }); expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"incidentTypes","subActionParams":{}}}', signal: abortCtrl.signal, @@ -71,14 +69,15 @@ describe('Resilient API', () => { describe('getSeverity', () => { test('should call get choices API', async () => { const abortCtrl = new AbortController(); - http.post.mockResolvedValueOnce(severityResponse); + http.post.mockResolvedValueOnce({ ...severityResponse, connector_id: 'te/st' }); const res = await getSeverity({ http, signal: abortCtrl.signal, connectorId: 'te/st', }); - expect(res).toEqual(severityResponse); + expect(res).toEqual({ ...severityResponse, actionId: 'te/st' }); + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"severity","subActionParams":{}}}', signal: abortCtrl.signal, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts index 6bd9c43105cf0e..daca46247ae3e4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts @@ -7,6 +7,7 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ACTION_API_PATH } from '../../../constants'; +import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body'; export async function getIncidentTypes({ http, @@ -17,7 +18,7 @@ export async function getIncidentTypes({ signal: AbortSignal; connectorId: string; }): Promise> { - return await http.post( + const res = await http.post>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, { body: JSON.stringify({ @@ -26,6 +27,7 @@ export async function getIncidentTypes({ signal, } ); + return rewriteResponseToCamelCase(res); } export async function getSeverity({ @@ -37,7 +39,7 @@ export async function getSeverity({ signal: AbortSignal; connectorId: string; }): Promise> { - return await http.post( + const res = await http.post>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, { body: JSON.stringify({ @@ -46,4 +48,5 @@ export async function getSeverity({ signal, } ); + return rewriteResponseToCamelCase(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/rewrite_response_body.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/rewrite_response_body.ts new file mode 100644 index 00000000000000..d9bf48c4ab8542 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/rewrite_response_body.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ActionTypeExecutorResult, RewriteResponseCase } from '../../../../../actions/common'; + +export type ConnectorExecutorResult = ReturnType< + RewriteResponseCase> +>; + +export const rewriteResponseToCamelCase = ({ + connector_id: actionId, + service_message: serviceMessage, + ...data +}: ConnectorExecutorResult): ActionTypeExecutorResult => ({ + ...data, + actionId, + ...(serviceMessage && { serviceMessage }), +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts index 32a2d0296d4c94..13cd3e5313165e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts @@ -6,11 +6,15 @@ */ import { HttpSetup } from 'kibana/public'; + // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { snExternalServiceConfig } from '../../../../../../actions/server/builtin_action_types/servicenow/config'; import { BASE_ACTION_API_PATH } from '../../../constants'; import { API_INFO_ERROR } from './translations'; import { AppInfo, RESTApiError } from './types'; +import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body'; +import { ActionTypeExecutorResult } from '../../../../../../actions/common'; +import { Choice } from './types'; export async function getChoices({ http, @@ -22,8 +26,8 @@ export async function getChoices({ signal: AbortSignal; connectorId: string; fields: string[]; -}): Promise> { - return await http.post( +}): Promise> { + const res = await http.post>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, { body: JSON.stringify({ @@ -32,6 +36,7 @@ export async function getChoices({ signal, } ); + return rewriteResponseToCamelCase(res); } /** diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts index e40db85bcb12d1..9a8094f53d501f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isRESTApiError, isFieldInvalid } from './helpers'; +import { isRESTApiError, isFieldInvalid, isDeprecatedConnector } from './helpers'; describe('helpers', () => { describe('isRESTApiError', () => { @@ -48,4 +48,51 @@ describe('helpers', () => { expect(isFieldInvalid('description', [])).toBeFalsy(); }); }); + + describe('isDeprecatedConnector', () => { + const connector = { + id: 'test', + actionTypeId: '.webhook', + name: 'Test', + config: { apiUrl: 'http://example.com', usesTableApi: false }, + secrets: { username: 'test', password: 'test' }, + isPreconfigured: false as const, + }; + + it('returns false if the connector is not defined', () => { + expect(isDeprecatedConnector()).toBe(false); + }); + + it('returns false if the connector is not ITSM or SecOps', () => { + expect(isDeprecatedConnector(connector)).toBe(false); + }); + + it('returns false if the connector is .servicenow and the usesTableApi=false', () => { + expect(isDeprecatedConnector({ ...connector, actionTypeId: '.servicenow' })).toBe(false); + }); + + it('returns false if the connector is .servicenow-sir and the usesTableApi=false', () => { + expect(isDeprecatedConnector({ ...connector, actionTypeId: '.servicenow-sir' })).toBe(false); + }); + + it('returns true if the connector is .servicenow and the usesTableApi=true', () => { + expect( + isDeprecatedConnector({ + ...connector, + actionTypeId: '.servicenow', + config: { ...connector.config, usesTableApi: true }, + }) + ).toBe(true); + }); + + it('returns true if the connector is .servicenow-sir and the usesTableApi=true', () => { + expect( + isDeprecatedConnector({ + ...connector, + actionTypeId: '.servicenow-sir', + config: { ...connector.config, usesTableApi: true }, + }) + ).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts index 755923acc25cb4..de0b30b9acb2f4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts @@ -6,11 +6,6 @@ */ import { EuiSelectOption } from '@elastic/eui'; -import { - ENABLE_NEW_SN_ITSM_CONNECTOR, - ENABLE_NEW_SN_SIR_CONNECTOR, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../actions/server/constants/connectors'; import { IErrorObject } from '../../../../../public/types'; import { AppInfo, Choice, RESTApiError, ServiceNowActionConnector } from './types'; @@ -28,27 +23,21 @@ export const isFieldInvalid = ( ): boolean => error !== undefined && error.length > 0 && field != null; // TODO: Remove when the applications are certified -export const isDeprecatedConnector = (connector: ServiceNowActionConnector): boolean => { +export const isDeprecatedConnector = (connector?: ServiceNowActionConnector): boolean => { if (connector == null) { - return true; + return false; } - if (!ENABLE_NEW_SN_ITSM_CONNECTOR && connector.actionTypeId === '.servicenow') { - return true; + if (connector.actionTypeId === '.servicenow' || connector.actionTypeId === '.servicenow-sir') { + /** + * Connector's prior to the Elastic ServiceNow application + * use the Table API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_TableAPI) + * Connectors after the Elastic ServiceNow application use the + * Import Set API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_ImportSetAPI) + * A ServiceNow connector is considered deprecated if it uses the Table API. + */ + return !!connector.config.usesTableApi; } - if (!ENABLE_NEW_SN_SIR_CONNECTOR && connector.actionTypeId === '.servicenow-sir') { - return true; - } - - /** - * Connectors after the Elastic ServiceNow application use the - * Import Set API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_ImportSetAPI) - * A ServiceNow connector is considered deprecated if it uses the Table API. - * - * All other connectors do not have the usesTableApi config property - * so the function will always return false for them. - */ - - return !!connector.config.usesTableApi; + return false; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx index 48c0fb2109f556..175a80c63d4b77 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx @@ -122,7 +122,7 @@ describe('UseChoices', () => { it('it displays an error when service fails', async () => { getChoicesMock.mockResolvedValue({ status: 'error', - service_message: 'An error occurred', + serviceMessage: 'An error occurred', }); const { waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.tsx index 5493fdaee8bfa6..bfad678f9b24bf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.tsx @@ -6,7 +6,7 @@ */ import { useCallback, useMemo, useState } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../types'; import { Choice, Fields } from './types'; @@ -14,10 +14,7 @@ import { useGetChoices } from './use_get_choices'; export interface UseChoicesProps { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; actionConnector?: ActionConnector; fields: string[]; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx index 532789385e8bd4..ecbc9512a4d3a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx @@ -121,7 +121,7 @@ describe('useGetChoices', () => { it('it displays an error when service fails', async () => { getChoicesMock.mockResolvedValue({ status: 'error', - service_message: 'An error occurred', + serviceMessage: 'An error occurred', }); const { waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx index f4c881c633cdc7..b115c84562ae64 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx @@ -6,7 +6,7 @@ */ import { useState, useEffect, useRef, useCallback } from 'react'; -import { HttpSetup, ToastsApi } from 'kibana/public'; +import { HttpSetup, IToasts } from 'kibana/public'; import { ActionConnector } from '../../../../types'; import { getChoices } from './api'; import { Choice } from './types'; @@ -14,10 +14,7 @@ import * as i18n from './translations'; export interface UseGetChoicesProps { http: HttpSetup; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: IToasts; actionConnector?: ActionConnector; fields: string[]; onSuccess?: (choices: Choice[]) => void; @@ -66,7 +63,7 @@ export const useGetChoices = ({ if (res.status && res.status === 'error') { toastNotifications.addDanger({ title: i18n.CHOICES_API_ERROR, - text: `${res.service_message ?? res.message}`, + text: `${res.serviceMessage ?? res.message}`, }); } else if (onSuccess) { onSuccess(data); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connector_types.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connector_types.ts index 6f7e8b03658e0f..e1b63280cc9153 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connector_types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connector_types.ts @@ -27,6 +27,8 @@ const rewriteBodyReq: RewriteRequestCase = ({ }); export async function loadActionTypes({ http }: { http: HttpSetup }): Promise { - const res = await http.get(`${BASE_ACTION_API_PATH}/connector_types`); + const res = await http.get[0]>( + `${BASE_ACTION_API_PATH}/connector_types` + ); return rewriteResponseRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts index 7011ec016c089f..70997ca52dab1c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts @@ -34,6 +34,8 @@ const transformConnector: RewriteRequestCase< }); export async function loadAllActions({ http }: { http: HttpSetup }): Promise { - const res = await http.get(`${BASE_ACTION_API_PATH}/connectors`); + const res = await http.get[0]>( + `${BASE_ACTION_API_PATH}/connectors` + ); return rewriteResponseRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts index 7a63f6a19f583a..624aff1cd77733 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts @@ -42,8 +42,9 @@ export async function createActionConnector({ http: HttpSetup; connector: Omit; }): Promise { - const res = await http.post(`${BASE_ACTION_API_PATH}/connector`, { - body: JSON.stringify(rewriteBodyRequest(connector)), - }); + const res = await http.post[0]>( + `${BASE_ACTION_API_PATH}/connector`, + { body: JSON.stringify(rewriteBodyRequest(connector)) } + ); return rewriteBodyRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts index 868e5390045ccd..8c9495158cc579 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts @@ -17,7 +17,9 @@ export async function deleteActions({ const successes: string[] = []; const errors: string[] = []; await Promise.all( - ids.map((id) => http.delete(`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`)) + ids.map((id) => + http.delete(`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`) + ) ).then( function (fulfilled) { successes.push(...fulfilled); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts index d97ad7d5962b74..0a305413f61f7d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts @@ -31,7 +31,7 @@ export async function executeAction({ http: HttpSetup; params: Record; }): Promise> { - const res = await http.post( + const res = await http.post[0]>( `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}/_execute`, { body: JSON.stringify({ params }), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts index f2319ace29d685..a45dc8cfca2f9e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts @@ -36,13 +36,16 @@ export async function updateActionConnector({ connector: Pick; id: string; }): Promise { - const res = await http.put(`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`, { - body: JSON.stringify({ - name: connector.name, - config: connector.config, - secrets: connector.secrets, - }), - }); + const res = await http.put[0]>( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`, + { + body: JSON.stringify({ + name: connector.name, + config: connector.config, + secrets: connector.secrets, + }), + } + ); return rewriteBodyRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/aggregate.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/aggregate.ts index 589677ec2322d2..917a491586b36b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/aggregate.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/aggregate.ts @@ -8,7 +8,7 @@ import { HttpSetup } from 'kibana/public'; import { AlertAggregations } from '../../../types'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; import { mapFiltersToKql } from './map_filters_to_kql'; -import { RewriteRequestCase } from '../../../../../actions/common'; +import { AsApiContract, RewriteRequestCase } from '../../../../../actions/common'; const rewriteBodyRes: RewriteRequestCase = ({ rule_execution_status: alertExecutionStatus, @@ -32,13 +32,16 @@ export async function loadAlertAggregations({ alertStatusesFilter?: string[]; }): Promise { const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter }); - const res = await http.get(`${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, { - query: { - search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined, - search: searchText, - filter: filters.length ? filters.join(' and ') : undefined, - default_search_operator: 'AND', - }, - }); + const res = await http.get>( + `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, + { + query: { + search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined, + search: searchText, + filter: filters.length ? filters.join(' and ') : undefined, + default_search_operator: 'AND', + }, + } + ); return rewriteBodyRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts index 701c8f3a7beec8..35a757f4b6afeb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts @@ -7,7 +7,7 @@ import { HttpSetup } from 'kibana/public'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; import { AlertInstanceSummary } from '../../../types'; -import { RewriteRequestCase } from '../../../../../actions/common'; +import { RewriteRequestCase, AsApiContract } from '../../../../../actions/common'; const rewriteBodyRes: RewriteRequestCase = ({ alerts, @@ -38,7 +38,7 @@ export async function loadAlertInstanceSummary({ http: HttpSetup; alertId: string; }): Promise { - const res = await http.get( + const res = await http.get>( `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(alertId)}/_alert_summary` ); return rewriteBodyRes(res); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/create.ts index bd92769b4bbf39..36d2a17bcd4d53 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/create.ts @@ -5,7 +5,7 @@ * 2.0. */ import { HttpSetup } from 'kibana/public'; -import { RewriteResponseCase } from '../../../../../actions/common'; +import { AsApiContract, RewriteResponseCase } from '../../../../../actions/common'; import { Alert, AlertUpdates } from '../../../types'; import { BASE_ALERTING_API_PATH } from '../../constants'; import { transformAlert } from './common_transformations'; @@ -37,7 +37,7 @@ export async function createAlert({ http: HttpSetup; alert: AlertCreateBody; }): Promise { - const res = await http.post(`${BASE_ALERTING_API_PATH}/rule`, { + const res = await http.post>(`${BASE_ALERTING_API_PATH}/rule`, { body: JSON.stringify(rewriteBodyRequest(alert)), }); return transformAlert(res); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts index b853e722e6fc36..d223dd08ca29a5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts @@ -17,7 +17,7 @@ export async function deleteAlerts({ const successes: string[] = []; const errors: string[] = []; await Promise.all( - ids.map((id) => http.delete(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`)) + ids.map((id) => http.delete(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`)) ).then( function (fulfilled) { successes.push(...fulfilled); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts index 9fa882c02fa228..fd4de0c3dae689 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts @@ -5,6 +5,7 @@ * 2.0. */ import { HttpSetup } from 'kibana/public'; +import { AsApiContract } from '../../../../../actions/common'; import { Alert } from '../../../types'; import { BASE_ALERTING_API_PATH } from '../../constants'; import { transformAlert } from './common_transformations'; @@ -16,6 +17,8 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - const res = await http.get(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(alertId)}`); + const res = await http.get>( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(alertId)}` + ); return transformAlert(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/health.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/health.ts index 9468f4b3c03e09..b9df3938fafaa2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/health.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/health.ts @@ -38,7 +38,14 @@ export async function alertingFrameworkHealth({ }: { http: HttpSetup; }): Promise { - const res = await http.get(`${BASE_ALERTING_API_PATH}/_health`); - const alertingFrameworkHeath = rewriteAlertingFrameworkHeath(res.alerting_framework_heath); - return { ...rewriteBodyRes(res), alertingFrameworkHeath }; + const res = await http.get>( + `${BASE_ALERTING_API_PATH}/_health` + ); + const alertingFrameworkHeath = rewriteAlertingFrameworkHeath( + res.alerting_framework_heath as unknown as AsApiContract + ); + return { + ...rewriteBodyRes(res), + alertingFrameworkHeath, + }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts index bc2a19d298f8a5..fa2867ffd85e7a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts @@ -5,6 +5,7 @@ * 2.0. */ import { HttpSetup } from 'kibana/public'; +import { AsApiContract } from '../../../../../actions/common'; import { ResolvedRule } from '../../../types'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; import { transformResolvedRule } from './common_transformations'; @@ -16,7 +17,7 @@ export async function resolveRule({ http: HttpSetup; ruleId: string; }): Promise { - const res = await http.get( + const res = await http.get>( `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(ruleId)}/_resolve` ); return transformResolvedRule(res); 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 67d317643ec068..530c158838c2bb 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 @@ -36,6 +36,8 @@ const rewriteBodyReq: RewriteRequestCase = ({ }); export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { - const res = await http.get(`${BASE_ALERTING_API_PATH}/rule_types`); + const res = await http.get>>>( + `${BASE_ALERTING_API_PATH}/rule_types` + ); return rewriteResponseRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rules.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rules.ts index f0bbb57180bb46..3475cbb04408e7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rules.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/rules.ts @@ -38,7 +38,14 @@ export async function loadAlerts({ data: Alert[]; }> { const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter }); - const res = await http.get(`${BASE_ALERTING_API_PATH}/rules/_find`, { + const res = await http.get< + AsApiContract<{ + page: number; + perPage: number; + total: number; + data: Array>; + }> + >(`${BASE_ALERTING_API_PATH}/rules/_find`, { query: { page: page.index + 1, per_page: page.size, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/state.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/state.ts index 428bc5b99a70b4..f5529c5d7d5b52 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/state.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/state.ts @@ -34,10 +34,10 @@ export async function loadAlertState({ alertId: string; }): Promise { return await http - .get(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${alertId}/state`) - .then((state: AsApiContract | EmptyHttpResponse) => - state ? rewriteBodyRes(state) : {} + .get | EmptyHttpResponse>( + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${alertId}/state` ) + .then((state) => (state ? rewriteBodyRes(state) : {})) .then((state: AlertTaskState) => { return pipe( alertStateSchema.decode(state), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts index 930c0c2fb21a08..8b9365b0a46678 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts @@ -8,7 +8,7 @@ import { HttpSetup } from 'kibana/public'; import { pick } from 'lodash'; import { BASE_ALERTING_API_PATH } from '../../constants'; import { Alert, AlertUpdates } from '../../../types'; -import { RewriteResponseCase } from '../../../../../actions/common'; +import { RewriteResponseCase, AsApiContract } from '../../../../../actions/common'; import { transformAlert } from './common_transformations'; type AlertUpdatesBody = Pick< @@ -41,12 +41,15 @@ export async function updateAlert({ >; id: string; }): Promise { - const res = await http.put(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, { - body: JSON.stringify( - rewriteBodyRequest( - pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions', 'notifyWhen']) - ) - ), - }); + const res = await http.put>( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, + { + body: JSON.stringify( + rewriteBodyRequest( + pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions', 'notifyWhen']) + ) + ), + } + ); return transformAlert(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 90eadaf5f9b8b9..d477fcd0ddf742 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -6,6 +6,8 @@ */ import * as React from 'react'; +// eslint-disable-next-line @kbn/eslint/module_migration +import { ThemeProvider } from 'styled-components'; import { mountWithIntl, nextTick } from '@kbn/test/jest'; import ActionsConnectorsList from './actions_connectors_list'; @@ -458,3 +460,80 @@ describe('actions_connectors_list component with disabled items', () => { ); }); }); + +describe('actions_connectors_list component with deprecated connectors', () => { + let wrapper: ReactWrapper; + + async function setup() { + loadAllActions.mockResolvedValueOnce([ + { + id: '1', + actionTypeId: '.servicenow', + description: 'My test', + referencedByCount: 1, + config: { usesTableApi: true }, + }, + { + id: '2', + actionTypeId: '.servicenow-sir', + description: 'My test 2', + referencedByCount: 1, + config: { usesTableApi: true }, + }, + ]); + loadActionTypes.mockResolvedValueOnce([ + { + id: 'test', + name: '.servicenow', + enabled: false, + enabledInConfig: false, + enabledInLicense: true, + }, + { + id: 'test2', + name: '.servicenow-sir', + enabled: false, + enabledInConfig: true, + enabledInLicense: false, + }, + ]); + + const [ + { + application: { capabilities }, + }, + ] = await mocks.getStartServices(); + + // eslint-disable-next-line react-hooks/rules-of-hooks + useKibanaMock().services.application.capabilities = { + ...capabilities, + actions: { + show: true, + save: true, + delete: true, + }, + }; + // eslint-disable-next-line react-hooks/rules-of-hooks + useKibanaMock().services.actionTypeRegistry = actionTypeRegistry; + wrapper = mountWithIntl( + ({ eui: { euiSizeS: '15px' }, darkMode: true })}> + + + ); + + // Wait for active space to resolve before requesting the component to update + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(loadAllActions).toHaveBeenCalled(); + } + + it('shows the warning icon', async () => { + await setup(); + expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1); + expect(wrapper.find('EuiTableRow')).toHaveLength(2); + expect(wrapper.find('.euiToolTipAnchor [aria-label="Warning"]').exists()).toBe(true); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 5de21470fc19ab..6b52479f2ac870 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -48,11 +48,6 @@ import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../../'; import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; import ConnectorEditFlyout from '../../action_connector_form/connector_edit_flyout'; import ConnectorAddFlyout from '../../action_connector_form/connector_add_flyout'; -import { - ENABLE_NEW_SN_ITSM_CONNECTOR, - ENABLE_NEW_SN_SIR_CONNECTOR, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../actions/server/constants/connectors'; const ConnectorIconTipWithSpacing = withTheme(({ theme }: { theme: EuiTheme }) => { return ( @@ -202,14 +197,19 @@ const ActionsConnectorsList: React.FunctionComponent = () => { const checkEnabledResult = checkActionTypeEnabled( actionTypesIndex && actionTypesIndex[item.actionTypeId] ); + const itemConfig = ( item as UserConfiguredActionConnector, Record> ).config; - const showDeprecatedTooltip = - itemConfig?.usesTableApi && - // TODO: Remove when applications are certified - ((ENABLE_NEW_SN_ITSM_CONNECTOR && item.actionTypeId === '.servicenow') || - (ENABLE_NEW_SN_SIR_CONNECTOR && item.actionTypeId === '.servicenow-sir')); + + /** + * TODO: Remove when connectors can provide their own UX message. + * Issue: https://github.com/elastic/kibana/issues/114507 + */ + const hasSNApplication = + item?.actionTypeId === '.servicenow' || item?.actionTypeId === '.servicenow-sir'; + + const showDeprecatedTooltip = hasSNApplication && itemConfig?.usesTableApi; const link = ( <> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx index 672cd75f0cd136..1f758de1912c4e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx @@ -48,7 +48,18 @@ describe('padOrTruncateDurations', () => { }); it('pads execution duration values when there are fewer than display desires', () => { - expect(padOrTruncateDurations([1, 2, 3], 10)).toEqual([1, 2, 3, 0, 0, 0, 0, 0, 0, 0]); + expect(padOrTruncateDurations([1, 2, 3], 10)).toEqual([ + 1, + 2, + 3, + null, + null, + null, + null, + null, + null, + null, + ]); }); it('truncates execution duration values when there are more than display desires', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.tsx index ca20af9cea0fd8..ea8c16d03cc041 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.tsx @@ -82,19 +82,35 @@ export const ExecutionDurationChart: React.FunctionComponent = ({ /> [ndx, val])} + minBarHeight={2} /> [ndx, executionDuration.average])} + data={paddedExecutionDurations.map((val, ndx) => [ + ndx, + val ? executionDuration.average : null, + ])} curve={CurveType.CURVE_NATURAL} /> formatMillisForDisplay(d)} /> @@ -125,7 +141,7 @@ export function padOrTruncateDurations(values: number[], desiredSize: number) { if (values.length === desiredSize) { return values; } else if (values.length < desiredSize) { - return assign(fill(new Array(desiredSize), 0), values); + return assign(fill(new Array(desiredSize), null), values); } else { // oldest durations are at the start of the array, so take the last {desiredSize} values return values.slice(-desiredSize); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts index 7b97c8bdfaa691..d8a1ecabcd500b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts @@ -22,9 +22,10 @@ export async function getMatchingIndices({ if (!pattern.endsWith('*')) { pattern = `${pattern}*`; } - const { indices } = await http.post(`${DATA_API_ROOT}/_indices`, { - body: JSON.stringify({ pattern }), - }); + const { indices } = await http.post>( + `${DATA_API_ROOT}/_indices`, + { body: JSON.stringify({ pattern }) } + ); return indices; } @@ -43,9 +44,10 @@ export async function getESIndexFields({ aggregatable: boolean; }> > { - const { fields } = await http.post(`${DATA_API_ROOT}/_fields`, { - body: JSON.stringify({ indexPatterns: indexes }), - }); + const { fields } = await http.post<{ fields: ReturnType }>( + `${DATA_API_ROOT}/_fields`, + { body: JSON.stringify({ indexPatterns: indexes }) } + ); return fields; } diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 8085f9245f4e9c..0b35317cad9b3e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -43,7 +43,7 @@ import { type Alert = SanitizedAlert; type ResolvedRule = ResolvedSanitizedRule; -export { +export type { Alert, AlertAction, AlertAggregations, @@ -56,13 +56,12 @@ export { AlertTypeParams, ResolvedRule, }; +export type { ActionType, AsApiContract }; export { - ActionType, AlertHistoryEsIndexConnectorId, AlertHistoryDocumentTemplate, AlertHistoryDefaultIndexName, ALERT_HISTORY_PREFIX, - AsApiContract, }; export type ActionTypeIndex = Record; diff --git a/x-pack/plugins/triggers_actions_ui/server/data/index.ts b/x-pack/plugins/triggers_actions_ui/server/data/index.ts index 7bc14d0619d79e..1f6f39a57cbc8c 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/index.ts @@ -9,9 +9,8 @@ import { Logger, IRouter } from '../../../../../src/core/server'; import { timeSeriesQuery } from './lib/time_series_query'; import { registerRoutes } from './routes'; +export type { TimeSeriesQuery, CoreQueryParams } from './lib'; export { - TimeSeriesQuery, - CoreQueryParams, CoreQueryParamsSchemaProperties, validateCoreQueryBody, validateTimeWindowUnits, diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts index aeaee619354490..c76eb1cafa8671 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -export { TimeSeriesQuery } from './time_series_query'; +export type { TimeSeriesQuery } from './time_series_query'; +export type { CoreQueryParams } from './core_query_types'; export { - CoreQueryParams, CoreQueryParamsSchemaProperties, validateCoreQueryBody, validateTimeWindowUnits, diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index 0be981661f5655..ca4c7c8acc5dc0 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -12,7 +12,7 @@ import { DEFAULT_GROUPS } from '../index'; import { getDateRangeInfo } from './date_range_info'; import { TimeSeriesQuery, TimeSeriesResult, TimeSeriesResultRow } from './time_series_types'; -export { TimeSeriesQuery, TimeSeriesResult } from './time_series_types'; +export type { TimeSeriesQuery, TimeSeriesResult } from './time_series_types'; export interface TimeSeriesQueryParameters { logger: Logger; diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts index f6a1950c0fd97c..125bdaa487fd2d 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts @@ -19,7 +19,7 @@ import { getDateStartAfterDateEndErrorMessage, } from './date_range_info'; -export { TimeSeriesResult, TimeSeriesResultRow, MetricResult } from '../../../common/data'; +export type { TimeSeriesResult, TimeSeriesResultRow, MetricResult } from '../../../common/data'; // The parameters here are very similar to the alert parameters. // Missing are `comparator` and `threshold`, which aren't needed to generate diff --git a/x-pack/plugins/triggers_actions_ui/server/data/routes/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/time_series_query.ts index da6638db2e4579..8b618b3ae3d49e 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/routes/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/time_series_query.ts @@ -16,7 +16,7 @@ import { Logger } from '../../../../../../src/core/server'; import { TimeSeriesQueryParameters } from '../lib/time_series_query'; import { TimeSeriesQuery, TimeSeriesQuerySchema, TimeSeriesResult } from '../lib/time_series_types'; -export { TimeSeriesQuery, TimeSeriesResult } from '../lib/time_series_types'; +export type { TimeSeriesQuery, TimeSeriesResult } from '../lib/time_series_types'; export function createTimeSeriesQueryRoute( logger: Logger, diff --git a/x-pack/plugins/triggers_actions_ui/server/index.ts b/x-pack/plugins/triggers_actions_ui/server/index.ts index 89c17ea0d41890..2f33f3bd77cc0d 100644 --- a/x-pack/plugins/triggers_actions_ui/server/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/index.ts @@ -8,10 +8,9 @@ import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server' import { configSchema, ConfigSchema } from '../config'; import { TriggersActionsPlugin } from './plugin'; -export { PluginStartContract } from './plugin'; +export type { PluginStartContract } from './plugin'; +export type { TimeSeriesQuery, CoreQueryParams } from './data'; export { - TimeSeriesQuery, - CoreQueryParams, CoreQueryParamsSchemaProperties, validateCoreQueryBody, validateTimeWindowUnits, diff --git a/x-pack/plugins/ui_actions_enhanced/kibana.json b/x-pack/plugins/ui_actions_enhanced/kibana.json index da34dfd46e5778..fd7ffb820727db 100644 --- a/x-pack/plugins/ui_actions_enhanced/kibana.json +++ b/x-pack/plugins/ui_actions_enhanced/kibana.json @@ -1,7 +1,7 @@ { "id": "uiActionsEnhanced", "owner": { - "name": "Kibana App Services", + "name": "App Services", "githubTeam": "kibana-app-services" }, "description": "Extends UI Actions plugin with more functionality", diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/presentable_picker/presentable_picker.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/presentable_picker/presentable_picker.tsx index 72f9e5aa0bd4ab..9667a78df9a4e6 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/presentable_picker/presentable_picker.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/presentable_picker/presentable_picker.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { PresentablePickerItem, Item } from './presentable_picker_item'; -export { Item } from './presentable_picker_item'; +export type { Item } from './presentable_picker_item'; export interface PresentablePickerProps { items: Item[]; diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/trigger_picker/index.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/trigger_picker/index.ts index cdb6fbe54698df..4a30a0494e8f78 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/trigger_picker/index.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/trigger_picker/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { TriggerPickerItemDescription } from './trigger_picker_item'; +export type { TriggerPickerItemDescription } from './trigger_picker_item'; export * from './trigger_picker'; diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/types.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/types.ts index 4d6e5354604a1f..052211d523896b 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/types.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/components/types.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { ActionFactoryPlaceContext } from '../types'; +export type { ActionFactoryPlaceContext } from '../types'; diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/index.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/index.ts index 85ee5863674066..cb1e6099c9b53c 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/index.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { createPublicDrilldownManager, PublicDrilldownManagerComponent } from './drilldown_manager'; +export type { PublicDrilldownManagerComponent } from './drilldown_manager'; +export { createPublicDrilldownManager } from './drilldown_manager'; diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts index 5a34a002bf4c3c..e363d154dc141b 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts @@ -468,7 +468,6 @@ export class DrilldownManagerState { // Below are convenience React hooks for consuming observables in connected // React components. - /* eslint-disable react-hooks/rules-of-hooks */ public readonly useTitle = () => useObservable(this.title$, this.title$.getValue()); public readonly useFooter = () => useObservable(this.footer$, this.footer$.getValue()); public readonly useRoute = () => useObservable(this.route$, this.route$.getValue()); @@ -477,5 +476,4 @@ export class DrilldownManagerState { public readonly useActionFactory = () => useObservable(this.actionFactory$, this.actionFactory$.getValue()); public readonly useEvents = () => useObservable(this.events$, this.events$.getValue()); - /* eslint-enable react-hooks/rules-of-hooks */ } diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_state.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_state.ts index f80ad30d34bb87..d16a9a93930dd6 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_state.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_state.ts @@ -233,10 +233,8 @@ export class DrilldownState { // Below are convenience React hooks for consuming observables in connected // React components. - /* eslint-disable react-hooks/rules-of-hooks */ public readonly useName = () => useObservable(this.name$, this.name$.getValue()); public readonly useTriggers = () => useObservable(this.triggers$, this.triggers$.getValue()); public readonly useConfig = () => useObservable(this.config$, this.config$.getValue()); public readonly useError = () => useSyncObservable(this.error$); - /* eslint-enable react-hooks/rules-of-hooks */ } diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/index.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/index.ts index 7922158c62e7fc..5aa1dddd64f8c8 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/index.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { UrlDrilldownCollectConfig, UrlDrilldownCollectConfigProps } from './lazy'; +export type { UrlDrilldownCollectConfigProps } from './lazy'; +export { UrlDrilldownCollectConfig } from './lazy'; diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/index.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/index.ts index cc0ce30eb4f432..db76a54cc8d5b8 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/index.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { UrlDrilldownConfig, UrlDrilldownGlobalScope, UrlDrilldownScope } from './types'; +export type { UrlDrilldownConfig, UrlDrilldownGlobalScope, UrlDrilldownScope } from './types'; export { UrlDrilldownCollectConfig } from './components'; export { validateUrlTemplate as urlDrilldownValidateUrlTemplate, diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/types.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/types.ts index b400f9bd972314..2512753c07f4aa 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/types.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/types.ts @@ -7,7 +7,7 @@ import { SerializedAction, SerializedEvent, BaseActionConfig } from '../../common/types'; -export { SerializedAction, SerializedEvent, BaseActionConfig }; +export type { SerializedAction, SerializedEvent, BaseActionConfig }; /** * Action factory context passed into ActionFactories' CollectConfig, getDisplayName, getIconType diff --git a/x-pack/plugins/ui_actions_enhanced/public/index.ts b/x-pack/plugins/ui_actions_enhanced/public/index.ts index 3135cf44a7aa97..453a633e851af5 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/index.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/index.ts @@ -13,38 +13,42 @@ export function plugin(initializerContext: PluginInitializerContext) { } export { AdvancedUiActionsPublicPlugin as Plugin }; -export { +export type { SetupContract as AdvancedUiActionsSetup, StartContract as AdvancedUiActionsStart, } from './plugin'; -export { +export type { ActionFactoryDefinition as UiActionsEnhancedActionFactoryDefinition, - ActionFactory as UiActionsEnhancedActionFactory, SerializedAction as UiActionsEnhancedSerializedAction, SerializedEvent as UiActionsEnhancedSerializedEvent, - AbstractActionStorage as UiActionsEnhancedAbstractActionStorage, - DynamicActionManager as UiActionsEnhancedDynamicActionManager, DynamicActionManagerParams as UiActionsEnhancedDynamicActionManagerParams, DynamicActionManagerState as UiActionsEnhancedDynamicActionManagerState, - MemoryActionStorage as UiActionsEnhancedMemoryActionStorage, BaseActionFactoryContext as UiActionsEnhancedBaseActionFactoryContext, BaseActionConfig as UiActionsEnhancedBaseActionConfig, } from './dynamic_actions'; +export { + ActionFactory as UiActionsEnhancedActionFactory, + AbstractActionStorage as UiActionsEnhancedAbstractActionStorage, + DynamicActionManager as UiActionsEnhancedDynamicActionManager, + MemoryActionStorage as UiActionsEnhancedMemoryActionStorage, +} from './dynamic_actions'; -export { DynamicActionsState } from './services/ui_actions_service_enhancements'; +export type { DynamicActionsState } from './services/ui_actions_service_enhancements'; -export { +export type { DrilldownDefinition as UiActionsEnhancedDrilldownDefinition, DrilldownTemplate as UiActionsEnhancedDrilldownTemplate, } from './drilldowns'; +export type { + UrlDrilldownConfig, + UrlDrilldownGlobalScope, + UrlDrilldownScope, +} from './drilldowns/url_drilldown'; export { urlDrilldownCompileUrl, UrlDrilldownCollectConfig, - UrlDrilldownConfig, - UrlDrilldownGlobalScope, urlDrilldownGlobalScopeProvider, - UrlDrilldownScope, urlDrilldownValidateUrl, urlDrilldownValidateUrlTemplate, } from './drilldowns/url_drilldown'; diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 8da9e62766cc3e..40155617b22e09 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -22,7 +22,7 @@ import { PersistableStateDefinition } from '../../../../../src/plugins/kibana_ut import { DynamicActionsState } from '../../common/types'; -export { DynamicActionsState }; +export type { DynamicActionsState }; export interface UiActionsServiceEnhancementsParams { readonly actionFactories?: ActionFactoryRegistry; diff --git a/x-pack/plugins/ui_actions_enhanced/server/index.ts b/x-pack/plugins/ui_actions_enhanced/server/index.ts index ef6cc4015c8e0b..a1d70d3ea4a835 100644 --- a/x-pack/plugins/ui_actions_enhanced/server/index.ts +++ b/x-pack/plugins/ui_actions_enhanced/server/index.ts @@ -12,17 +12,17 @@ export function plugin() { } export { AdvancedUiActionsServerPlugin as Plugin }; -export { +export type { SetupContract as AdvancedUiActionsSetup, StartContract as AdvancedUiActionsStart, } from './plugin'; -export { +export type { ActionFactoryDefinition as UiActionsEnhancedActionFactoryDefinition, ActionFactory as UiActionsEnhancedActionFactory, } from './types'; -export { +export type { DynamicActionsState, BaseActionConfig as UiActionsEnhancedBaseActionConfig, SerializedAction as UiActionsEnhancedSerializedAction, diff --git a/x-pack/plugins/ui_actions_enhanced/server/types.ts b/x-pack/plugins/ui_actions_enhanced/server/types.ts index 24d30d74661bdd..e0734e31486b5b 100644 --- a/x-pack/plugins/ui_actions_enhanced/server/types.ts +++ b/x-pack/plugins/ui_actions_enhanced/server/types.ts @@ -24,4 +24,4 @@ export interface ActionFactory

id: string; } -export { SerializedEvent, SerializedAction, DynamicActionsState }; +export type { SerializedEvent, SerializedAction, DynamicActionsState }; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts index b2a1c4e80ec7d8..2d3fff9d43e2ca 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts @@ -5,8 +5,11 @@ * 2.0. */ -export { setup as setupOverviewPage, OverviewTestBed } from './overview.helpers'; -export { setup as setupElasticsearchPage, ElasticsearchTestBed } from './elasticsearch.helpers'; -export { setup as setupKibanaPage, KibanaTestBed } from './kibana.helpers'; +export type { OverviewTestBed } from './overview.helpers'; +export { setup as setupOverviewPage } from './overview.helpers'; +export type { ElasticsearchTestBed } from './elasticsearch.helpers'; +export { setup as setupElasticsearchPage } from './elasticsearch.helpers'; +export type { KibanaTestBed } from './kibana.helpers'; +export { setup as setupKibanaPage } from './kibana.helpers'; export { setupEnvironment, kibanaVersion } from './setup_environment'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/index.tsx index 6b9eee80acb574..dad239ddb81b30 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/index.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/index.tsx @@ -5,4 +5,5 @@ * 2.0. */ -export { ReindexFlyout, ReindexFlyoutProps } from './container'; +export type { ReindexFlyoutProps } from './container'; +export { ReindexFlyout } from './container'; diff --git a/x-pack/plugins/upgrade_assistant/public/shared_imports.ts b/x-pack/plugins/upgrade_assistant/public/shared_imports.ts index 64b52065f63e6f..06816daac428b0 100644 --- a/x-pack/plugins/upgrade_assistant/public/shared_imports.ts +++ b/x-pack/plugins/upgrade_assistant/public/shared_imports.ts @@ -8,18 +8,20 @@ import { useKibana as _useKibana } from '../../../../src/plugins/kibana_react/public'; import { AppServicesContext } from './types'; -export { - sendRequest, +export type { SendRequestConfig, SendRequestResponse, - useRequest, UseRequestConfig, +} from '../../../../src/plugins/es_ui_shared/public/'; +export { + sendRequest, + useRequest, SectionLoading, GlobalFlyout, } from '../../../../src/plugins/es_ui_shared/public/'; export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; -export { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +export type { DataPublicPluginStart } from '../../../../src/plugins/data/public'; export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts index 7b181ac2cf50cf..59e15f2d9e9c3f 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts @@ -27,6 +27,11 @@ export const JourneyStepType = t.intersection([ lt: t.string, }), }), + observer: t.partial({ + geo: t.type({ + name: t.string, + }), + }), synthetics: t.partial({ error: t.partial({ message: t.string, diff --git a/x-pack/plugins/uptime/common/runtime_types/snapshot/index.ts b/x-pack/plugins/uptime/common/runtime_types/snapshot/index.ts index a09e5455015a19..15f190a0dacd23 100644 --- a/x-pack/plugins/uptime/common/runtime_types/snapshot/index.ts +++ b/x-pack/plugins/uptime/common/runtime_types/snapshot/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { Snapshot, SnapshotType } from './snapshot_count'; +export type { Snapshot } from './snapshot_count'; +export { SnapshotType } from './snapshot_count'; diff --git a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap index c9299bf7bf3ea5..4ee227c6452531 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap @@ -80,6 +80,24 @@ exports[`DonutChart component passes correct props without errors for valid prop "strokeWidth": 1, "visible": false, }, + "lumaSteps": Array [ + 224, + 184, + 128, + 96, + 64, + 32, + 16, + 8, + 4, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + ], "vertical": Object { "dash": Array [ 0, diff --git a/x-pack/plugins/uptime/public/components/common/higher_order/index.ts b/x-pack/plugins/uptime/public/components/common/higher_order/index.ts index d1d95b7cb099f1..403f942a3d7c5a 100644 --- a/x-pack/plugins/uptime/public/components/common/higher_order/index.ts +++ b/x-pack/plugins/uptime/public/components/common/higher_order/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { ResponsiveWrapperProps, withResponsiveWrapper } from './responsive_wrapper'; +export type { ResponsiveWrapperProps } from './responsive_wrapper'; +export { withResponsiveWrapper } from './responsive_wrapper'; diff --git a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx index cd122eb5d5fc52..1510fe28f17213 100644 --- a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx @@ -12,8 +12,13 @@ import { createMemoryHistory } from 'history'; import { render } from '../../lib/helper/rtl_helpers'; import { fireEvent } from '@testing-library/dom'; -// FLAKY: https://github.com/elastic/kibana/issues/114396 -describe.skip('UptimeDatePicker component', () => { +describe('UptimeDatePicker component', () => { + jest.setTimeout(10_000); + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('renders properly with mock data', async () => { const { findByText } = render(); expect(await findByText('Last 15 minutes')).toBeInTheDocument(); @@ -86,7 +91,7 @@ describe.skip('UptimeDatePicker component', () => { // it should update shared state - expect(startPlugins.data.query.timefilter.timefilter.setTime).toHaveBeenCalledTimes(3); + expect(startPlugins.data.query.timefilter.timefilter.setTime).toHaveBeenCalledTimes(2); expect(startPlugins.data.query.timefilter.timefilter.setTime).toHaveBeenCalledWith({ from: 'now-10m', diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx index 0de1b50ecce8f7..b83cb630aaa791 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx @@ -5,11 +5,12 @@ * 2.0. */ -export { WaterfallChart, RenderItem, WaterfallChartProps } from './components/waterfall_chart'; +export type { RenderItem, WaterfallChartProps } from './components/waterfall_chart'; +export { WaterfallChart } from './components/waterfall_chart'; export { WaterfallProvider, useWaterfallContext } from './context/waterfall_chart'; export { MiddleTruncatedText } from './components/middle_truncated_text'; export { useFlyout } from './components/use_flyout'; -export { +export type { WaterfallData, WaterfallDataEntry, WaterfallMetadata, diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts index 05223cc367c700..db1ca00c36ba45 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -export { - ToggleAlertFlyoutButton, - ToggleAlertFlyoutButtonProps, -} from './toggle_alert_flyout_button'; +export type { ToggleAlertFlyoutButtonProps } from './toggle_alert_flyout_button'; +export { ToggleAlertFlyoutButton } from './toggle_alert_flyout_button'; export { UptimeAlertsFlyoutWrapper } from './uptime_alerts_flyout_wrapper'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx index b339410ef2409b..aa5e2a7db64618 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx @@ -15,6 +15,8 @@ import { import { render } from '../../../../lib/helper/rtl_helpers'; describe('alert monitor status component', () => { + jest.setTimeout(10_000); + describe('hasFilters', () => { const EMPTY_FILTERS = { tags: [], diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx index 3bf6c4f4bcb14b..9848ea77d4673a 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx @@ -8,15 +8,15 @@ import { EuiEmptyPrompt, EuiPanel, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; -import { IHttpFetchError } from 'src/core/public'; +import { IHttpFetchError, ResponseErrorBody } from 'src/core/public'; interface EmptyStateErrorProps { - errors: IHttpFetchError[]; + errors: Array>; } export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { const unauthorized = errors.find( - (error: IHttpFetchError) => error.message && error.message.includes('unauthorized') + (error) => error.message && error.message.includes('unauthorized') ); return ( @@ -47,9 +47,9 @@ export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { body={ {!unauthorized && - errors.map((error: IHttpFetchError) => ( -

- {error.body.message || error.message} + errors.map((error) => ( +

+ {error.body?.message || error.message}

))} diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/index.ts b/x-pack/plugins/uptime/public/components/overview/monitor_list/index.ts index 4d54668b6e24be..86e4c6907b7d1a 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/index.ts @@ -6,7 +6,7 @@ */ export { MonitorListComponent } from './monitor_list'; -export { Criteria, Pagination } from './types'; +export type { Criteria, Pagination } from './types'; export { LocationLink } from './monitor_list_drawer'; export { MonitorListDrawer } from './monitor_list_drawer/list_drawer_container'; export { ActionsPopover } from './monitor_list_drawer/actions_popover/actions_popover_container'; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx index 80b1463ac2f435..de077931167c18 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx @@ -18,7 +18,7 @@ import { import { MonitorListComponent, noItemsMessage } from './monitor_list'; import * as redux from 'react-redux'; import moment from 'moment'; -import { IHttpFetchError } from '../../../../../../../src/core/public'; +import { IHttpFetchError, ResponseErrorBody } from '../../../../../../../src/core/public'; import { mockMoment } from '../../../lib/helper/test_helpers'; import { render } from '../../../lib/helper/rtl_helpers'; import { NO_DATA_MESSAGE } from './translations'; @@ -185,7 +185,7 @@ describe('MonitorList component', () => { , loading: false, }} pageSize={10} diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx index e4c57dab0ffcfd..217d8cdfc4c9ea 100644 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx @@ -17,7 +17,8 @@ import { UptimeUrlParams } from '../../../lib/helper/url_params'; const SAMPLE_ES_FILTERS = `{"bool":{"should":[{"match_phrase":{"monitor.id":"NodeServer"}}],"minimum_should_match":1}}`; -describe('useQueryBar', () => { +// FLAKY: https://github.com/elastic/kibana/issues/112677 +describe.skip('useQueryBar', () => { let DEFAULT_URL_PARAMS: UptimeUrlParams; let wrapper: any; let useUrlParamsSpy: jest.SpyInstance<[URL.GetUrlParams, URL.UpdateUrlParams]>; diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx index 54f73fb39a52a4..f8776f74b780e4 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx @@ -36,6 +36,7 @@ export const StepScreenshots = ({ step }: Props) => { timestamp: step['@timestamp'], monitorId: step.monitor.id, stepIndex: step.synthetics?.step?.index!, + location: step.observer?.geo?.name, }); } }, [step._id, step['@timestamp']]); diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx index 7aa763c15ca1f8..e1f43cfebdbb2c 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx @@ -228,6 +228,9 @@ const browserConsoleStep = { _id: 'IvT1oXwB5ds00bB_FVXP', observer: { hostname: '16Elastic', + geo: { + name: 'au-heartbeat', + }, }, agent: { name: '16Elastic', diff --git a/x-pack/plugins/uptime/public/contexts/index.ts b/x-pack/plugins/uptime/public/contexts/index.ts index 0a5d3441bfe2cd..8467691064bff3 100644 --- a/x-pack/plugins/uptime/public/contexts/index.ts +++ b/x-pack/plugins/uptime/public/contexts/index.ts @@ -6,11 +6,8 @@ */ export { UptimeRefreshContext, UptimeRefreshContextProvider } from './uptime_refresh_context'; -export { - UptimeSettingsContextValues, - UptimeSettingsContext, - UptimeSettingsContextProvider, -} from './uptime_settings_context'; +export type { UptimeSettingsContextValues } from './uptime_settings_context'; +export { UptimeSettingsContext, UptimeSettingsContextProvider } from './uptime_settings_context'; export { UptimeThemeContextProvider, UptimeThemeContext } from './uptime_theme_context'; export { UptimeStartupPluginsContext, diff --git a/x-pack/plugins/uptime/public/lib/helper/index.ts b/x-pack/plugins/uptime/public/lib/helper/index.ts index 2fce3cc0e54dc6..c06e1efc381377 100644 --- a/x-pack/plugins/uptime/public/lib/helper/index.ts +++ b/x-pack/plugins/uptime/public/lib/helper/index.ts @@ -9,5 +9,6 @@ export { convertMicrosecondsToMilliseconds } from './convert_measurements'; export * from './observability_integration'; export { getChartDateLabel } from './charts'; export { seriesHasDownValues } from './series_has_down_values'; -export { UptimeUrlParams, getSupportedUrlParams } from './url_params'; +export type { UptimeUrlParams } from './url_params'; +export { getSupportedUrlParams } from './url_params'; export { MountWithReduxProvider } from './helper_with_redux'; diff --git a/x-pack/plugins/uptime/public/lib/helper/url_params/index.ts b/x-pack/plugins/uptime/public/lib/helper/url_params/index.ts index 958a7b904da92f..8b1ff815c1c1f2 100644 --- a/x-pack/plugins/uptime/public/lib/helper/url_params/index.ts +++ b/x-pack/plugins/uptime/public/lib/helper/url_params/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { UptimeUrlParams, getSupportedUrlParams } from './get_supported_url_params'; +export type { UptimeUrlParams } from './get_supported_url_params'; +export { getSupportedUrlParams } from './get_supported_url_params'; diff --git a/x-pack/plugins/uptime/public/state/api/alerts.ts b/x-pack/plugins/uptime/public/state/api/alerts.ts index d74a73befb5d04..e175f7ac61bd3a 100644 --- a/x-pack/plugins/uptime/public/state/api/alerts.ts +++ b/x-pack/plugins/uptime/public/state/api/alerts.ts @@ -127,8 +127,11 @@ export const fetchAlertRecords = async ({ sort_field: 'name.keyword', sort_order: 'asc', }; - const alerts = await apiService.get(API_URLS.RULES_FIND, data); - return alerts.data.find((alert: Alert) => alert.params.monitorId === monitorId); + const alerts = await apiService.get<{ data: Array> }>( + API_URLS.RULES_FIND, + data + ); + return alerts.data.find((alert) => alert.params.monitorId === monitorId) as Alert; }; export const disableAlertById = async ({ alertId }: { alertId: string }) => { diff --git a/x-pack/plugins/uptime/public/state/api/journey.ts b/x-pack/plugins/uptime/public/state/api/journey.ts index 8ed3fadf5c346f..05d4a9e356919d 100644 --- a/x-pack/plugins/uptime/public/state/api/journey.ts +++ b/x-pack/plugins/uptime/public/state/api/journey.ts @@ -20,7 +20,9 @@ import { } from '../../../common/runtime_types/ping/synthetics'; export async function fetchScreenshotBlockSet(params: string[]): Promise { - return apiService.post('/api/uptime/journey/screenshot/block', { hashes: params }); + return apiService.post('/api/uptime/journey/screenshot/block', { + hashes: params, + }); } export async function fetchJourneySteps( @@ -49,10 +51,12 @@ export async function fetchLastSuccessfulStep({ monitorId, timestamp, stepIndex, + location, }: { monitorId: string; timestamp: string; stepIndex: number; + location?: string; }): Promise { return await apiService.get( `/api/uptime/synthetics/step/success/`, @@ -60,6 +64,7 @@ export async function fetchLastSuccessfulStep({ monitorId, timestamp, stepIndex, + location, }, JourneyStepType ); diff --git a/x-pack/plugins/uptime/public/state/api/monitor.ts b/x-pack/plugins/uptime/public/state/api/monitor.ts index 4991727a3dcdd1..ef04b38f37469f 100644 --- a/x-pack/plugins/uptime/public/state/api/monitor.ts +++ b/x-pack/plugins/uptime/public/state/api/monitor.ts @@ -27,7 +27,7 @@ export const fetchMonitorDetails = async ({ dateStart, dateEnd, }; - return await apiService.get(API_URLS.MONITOR_DETAILS, params, MonitorDetailsType); + return await apiService.get(API_URLS.MONITOR_DETAILS, params, MonitorDetailsType); }; type ApiParams = QueryParams & ApiRequest; @@ -38,5 +38,5 @@ export const fetchMonitorLocations = async ({ monitorId, dateStart, dateEnd }: A dateEnd, monitorId, }; - return await apiService.get(API_URLS.MONITOR_LOCATIONS, params, MonitorLocationsType); + return await apiService.get(API_URLS.MONITOR_LOCATIONS, params, MonitorLocationsType); }; diff --git a/x-pack/plugins/uptime/public/state/api/monitor_duration.ts b/x-pack/plugins/uptime/public/state/api/monitor_duration.ts index 56838d06bf94fb..c8010e18d08680 100644 --- a/x-pack/plugins/uptime/public/state/api/monitor_duration.ts +++ b/x-pack/plugins/uptime/public/state/api/monitor_duration.ts @@ -16,5 +16,5 @@ export const fetchMonitorDuration = async ({ monitorId, dateStart, dateEnd }: Ba dateEnd, }; - return await apiService.get(API_URLS.MONITOR_DURATION, queryParams); + return await apiService.get(API_URLS.MONITOR_DURATION, queryParams); }; diff --git a/x-pack/plugins/uptime/public/state/api/utils.ts b/x-pack/plugins/uptime/public/state/api/utils.ts index d10064f1ff7a13..0399129a804660 100644 --- a/x-pack/plugins/uptime/public/state/api/utils.ts +++ b/x-pack/plugins/uptime/public/state/api/utils.ts @@ -71,8 +71,13 @@ class ApiService { return ApiService.instance; } - public async get(apiUrl: string, params?: HttpFetchQuery, decodeType?: any, asResponse = false) { - const response = await this._http!.fetch({ + public async get( + apiUrl: string, + params?: HttpFetchQuery, + decodeType?: any, + asResponse = false + ) { + const response = await this._http!.fetch({ path: apiUrl, query: params, asResponse, @@ -83,7 +88,7 @@ class ApiService { if (decodeType) { const decoded = decodeType.decode(response); if (isRight(decoded)) { - return decoded.right; + return decoded.right as T; } else { // eslint-disable-next-line no-console console.error( @@ -98,8 +103,8 @@ class ApiService { return response; } - public async post(apiUrl: string, data?: any, decodeType?: any) { - const response = await this._http!.post(apiUrl, { + public async post(apiUrl: string, data?: any, decodeType?: any) { + const response = await this._http!.post(apiUrl, { method: 'POST', body: JSON.stringify(data), }); @@ -107,7 +112,7 @@ class ApiService { if (decodeType) { const decoded = decodeType.decode(response); if (isRight(decoded)) { - return decoded.right; + return decoded.right as T; } else { // eslint-disable-next-line no-console console.warn( @@ -118,8 +123,8 @@ class ApiService { return response; } - public async delete(apiUrl: string) { - const response = await this._http!.delete(apiUrl); + public async delete(apiUrl: string) { + const response = await this._http!.delete(apiUrl); if (response instanceof Error) { throw response; } diff --git a/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts b/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts index 8a2c7df05018ea..2e833bd033c46e 100644 --- a/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts +++ b/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts @@ -6,12 +6,12 @@ */ import { handleActions, Action } from 'redux-actions'; -import { IHttpFetchError } from 'src/core/public'; +import { IHttpFetchError, ResponseErrorBody } from 'src/core/public'; import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions'; import { MonitorSummariesResult } from '../../../common/runtime_types'; export interface MonitorList { - error?: IHttpFetchError; + error?: IHttpFetchError; loading: boolean; list: MonitorSummariesResult; } @@ -25,7 +25,7 @@ export const initialState: MonitorList = { loading: false, }; -type Payload = MonitorSummariesResult & IHttpFetchError; +type Payload = MonitorSummariesResult & IHttpFetchError; export const monitorListReducer = handleActions( { @@ -42,7 +42,10 @@ export const monitorListReducer = handleActions( error: undefined, list: { ...action.payload }, }), - [String(getMonitorListFailure)]: (state: MonitorList, action: Action) => ({ + [String(getMonitorListFailure)]: ( + state: MonitorList, + action: Action> + ) => ({ ...state, error: action.payload, loading: false, diff --git a/x-pack/plugins/uptime/public/state/reducers/types.ts b/x-pack/plugins/uptime/public/state/reducers/types.ts index c9dc30ed3dde3c..71cf9a59f6478f 100644 --- a/x-pack/plugins/uptime/public/state/reducers/types.ts +++ b/x-pack/plugins/uptime/public/state/reducers/types.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { IHttpFetchError } from 'src/core/public'; +import { IHttpFetchError, ResponseErrorBody } from 'src/core/public'; export interface AsyncInitState { data: ReduceStateType | null; loading: boolean; - error?: IHttpFetchError | null; + error?: IHttpFetchError | null; } diff --git a/x-pack/plugins/uptime/server/lib/domains/index.ts b/x-pack/plugins/uptime/server/lib/domains/index.ts index e0252e7d4a3ebc..ed459d39e246e0 100644 --- a/x-pack/plugins/uptime/server/lib/domains/index.ts +++ b/x-pack/plugins/uptime/server/lib/domains/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { licenseCheck, UMLicenseCheck } from './license'; +export type { UMLicenseCheck } from './license'; +export { licenseCheck } from './license'; diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts index 894bf743499f98..fbd0494a3ca82c 100644 --- a/x-pack/plugins/uptime/server/lib/lib.ts +++ b/x-pack/plugins/uptime/server/lib/lib.ts @@ -59,8 +59,6 @@ export function createUptimeESClient({ request?: KibanaRequest; savedObjectsClient: SavedObjectsClientContract | ISavedObjectsRepository; }) { - const { _inspect = false } = (request?.query as { _inspect: boolean }) ?? {}; - return { baseESClient: esClient, async search( @@ -101,10 +99,9 @@ export function createUptimeESClient({ startTime: startTimeNow, }) ); - } - - if (_inspect && request) { - debugESCall({ startTime, request, esError, operationName: 'search', params: esParams }); + if (request) { + debugESCall({ startTime, request, esError, operationName: 'search', params: esParams }); + } } if (esError) { @@ -129,8 +126,9 @@ export function createUptimeESClient({ } catch (e) { esError = e; } + const inspectableEsQueries = inspectableEsQueriesMap.get(request!); - if (_inspect && request) { + if (inspectableEsQueries && request) { debugESCall({ startTime, request, esError, operationName: 'count', params: esParams }); } diff --git a/x-pack/plugins/uptime/server/lib/requests/__snapshots__/get_monitor_details.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__snapshots__/get_monitor_details.test.ts.snap new file mode 100644 index 00000000000000..56b7ed25a74ad5 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/__snapshots__/get_monitor_details.test.ts.snap @@ -0,0 +1,109 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getMonitorDetails getMonitorAlerts should use expected filters for the query 1`] = ` +Array [ + Object { + "body": Object { + "aggs": Object { + "monitors": Object { + "terms": Object { + "field": "monitor.id", + "size": 1000, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "monitor.id": "fooID", + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "monitor.type": "http", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "url.domain": "www.cnn.com", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + "size": 0, + }, + "index": "heartbeat-8*,synthetics-*", + }, +] +`; + +exports[`getMonitorDetails getMonitorDetails will provide expected calls 1`] = ` +Array [ + Object { + "body": Object { + "_source": Array [ + "error", + "@timestamp", + ], + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-15m", + "lte": "now", + }, + }, + }, + Object { + "term": Object { + "monitor.id": "fooID", + }, + }, + ], + "must": Array [ + Object { + "exists": Object { + "field": "error", + }, + }, + ], + }, + }, + "size": 1, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + }, + "index": "heartbeat-8*,synthetics-*", + }, +] +`; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.test.ts new file mode 100644 index 00000000000000..63274bf64536cd --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getLastSuccessfulStepParams } from './get_last_successful_step'; + +describe('getLastSuccessfulStep', () => { + describe('getLastSuccessfulStepParams', () => { + it('formats ES params with location', () => { + const monitorId = 'my-monitor'; + const stepIndex = 1; + const location = 'au-heartbeat'; + const timestamp = '2021-10-31T19:47:52.392Z'; + const params = getLastSuccessfulStepParams({ + monitorId, + stepIndex, + location, + timestamp, + }); + + expect(params).toEqual({ + query: { + bool: { + filter: [ + { + range: { + '@timestamp': { + lte: '2021-10-31T19:47:52.392Z', + }, + }, + }, + { + term: { + 'monitor.id': monitorId, + }, + }, + { + term: { + 'synthetics.type': 'step/end', + }, + }, + { + term: { + 'synthetics.step.status': 'succeeded', + }, + }, + { + term: { + 'synthetics.step.index': stepIndex, + }, + }, + { + term: { + 'observer.geo.name': location, + }, + }, + ], + }, + }, + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }); + }); + + it('formats ES params without location', () => { + const params = getLastSuccessfulStepParams({ + monitorId: 'my-monitor', + stepIndex: 1, + location: undefined, + timestamp: '2021-10-31T19:47:52.392Z', + }); + + expect(params).toEqual({ + query: { + bool: { + filter: [ + { + range: { + '@timestamp': { + lte: '2021-10-31T19:47:52.392Z', + }, + }, + }, + { + term: { + 'monitor.id': 'my-monitor', + }, + }, + { + term: { + 'synthetics.type': 'step/end', + }, + }, + { + term: { + 'synthetics.step.status': 'succeeded', + }, + }, + { + term: { + 'synthetics.step.index': 1, + }, + }, + ], + must_not: { + exists: { + field: 'observer.geo.name', + }, + }, + }, + }, + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts index e096cdaa65b86b..d6862b93c8cd4c 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts @@ -13,13 +13,16 @@ export interface GetStepScreenshotParams { monitorId: string; timestamp: string; stepIndex: number; + location?: string; } -export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< - GetStepScreenshotParams, - JourneyStep | null -> = async ({ uptimeEsClient, monitorId, stepIndex, timestamp }) => { - const lastSuccessCheckParams: estypes.SearchRequest['body'] = { +export const getLastSuccessfulStepParams = ({ + monitorId, + stepIndex, + timestamp, + location, +}: GetStepScreenshotParams): estypes.SearchRequest['body'] => { + return { size: 1, sort: [ { @@ -58,10 +61,40 @@ export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< 'synthetics.step.index': stepIndex, }, }, + ...(location + ? [ + { + term: { + 'observer.geo.name': location, + }, + }, + ] + : []), ], + ...(!location + ? { + must_not: { + exists: { + field: 'observer.geo.name', + }, + }, + } + : {}), }, }, }; +}; + +export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< + GetStepScreenshotParams, + JourneyStep | null +> = async ({ uptimeEsClient, monitorId, stepIndex, timestamp, location }) => { + const lastSuccessCheckParams = getLastSuccessfulStepParams({ + monitorId, + stepIndex, + timestamp, + location, + }); const { body: result } = await uptimeEsClient.search({ body: lastSuccessCheckParams }); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.test.ts new file mode 100644 index 00000000000000..68560faf4f7fed --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.test.ts @@ -0,0 +1,219 @@ +/* + * 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 { mockSearchResult } from './helper'; +import { getMonitorAlerts, getMonitorDetails } from './get_monitor_details'; +import * as statusCheck from '../alerts/status_check'; + +describe('getMonitorDetails', () => { + it('getMonitorDetails will provide expected calls', async () => { + expect.assertions(2); + + const uptimeEsClient = mockSearchResult([{ _source: { id: 1 } }]); + + await getMonitorDetails({ + uptimeEsClient, + monitorId: 'fooID', + dateStart: 'now-15m', + dateEnd: 'now', + rulesClient: { find: jest.fn().mockReturnValue({ data: [] }) }, + }); + expect(uptimeEsClient.baseESClient.search).toHaveBeenCalledTimes(1); + + expect((uptimeEsClient.baseESClient.search as jest.Mock).mock.calls[0]).toMatchSnapshot(); + }); + + describe('getMonitorAlerts', () => { + it('should use expected filters for the query', async function () { + const uptimeEsClient = mockSearchResult([{ _source: { id: 1 } }]); + + jest.spyOn(statusCheck, 'formatFilterString').mockImplementation(async () => ({ + bool: { + filter: [ + { + bool: { should: [{ match: { 'monitor.type': 'http' } }], minimum_should_match: 1 }, + }, + { + bool: { + should: [{ match_phrase: { 'url.domain': 'www.cnn.com' } }], + minimum_should_match: 1, + }, + }, + ], + }, + })); + + await getMonitorAlerts({ + uptimeEsClient, + monitorId: 'fooID', + rulesClient: { + find: jest.fn().mockReturnValue({ data: dummyAlertRules.data }), + }, + }); + expect(uptimeEsClient.baseESClient.search).toHaveBeenCalledTimes(3); + + const esParams = (uptimeEsClient.baseESClient.search as jest.Mock).mock.calls[0]; + + expect(esParams[0].body.query).toEqual({ + bool: { + filter: [ + { + term: { + 'monitor.id': 'fooID', + }, + }, + { + bool: { + filter: [ + { + bool: { + minimum_should_match: 1, + should: [ + { + match: { + 'monitor.type': 'http', + }, + }, + ], + }, + }, + { + bool: { + minimum_should_match: 1, + should: [ + { + match_phrase: { + 'url.domain': 'www.cnn.com', + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }); + + expect(esParams).toMatchSnapshot(); + }); + }); +}); + +const dummyAlertRules = { + page: 1, + total: 3, + per_page: 10, + data: [ + { + id: '9e0cad00-31e7-11ec-b2d2-abfef52bb74d', + consumer: 'uptime', + tags: [], + name: 'browser alerrt', + enabled: true, + throttle: null, + schedule: { interval: '1m' }, + params: { + search: '', + numTimes: 5, + timerangeUnit: 'm', + timerangeCount: 15, + shouldCheckStatus: true, + shouldCheckAvailability: true, + availability: { range: 30, rangeUnit: 'd', threshold: '99' }, + filters: { tags: [], 'url.port': [], 'observer.geo.name': [], 'monitor.type': ['browser'] }, + }, + rule_type_id: 'xpack.uptime.alerts.monitorStatus', + created_by: null, + updated_by: null, + created_at: '2021-10-20T20:52:20.050Z', + updated_at: '2021-10-20T20:52:20.050Z', + api_key_owner: null, + notify_when: 'onActionGroupChange', + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: '9e91bb80-31e7-11ec-b2d2-abfef52bb74d', + execution_status: { + status: 'active', + last_execution_date: '2021-10-21T09:33:22.044Z', + last_duration: 414, + }, + actions: [], + }, + { + id: 'deb541f0-31e7-11ec-b2d2-abfef52bb74d', + consumer: 'alerts', + tags: [], + name: 'http alert', + enabled: true, + throttle: null, + schedule: { interval: '1m' }, + params: { + search: '', + numTimes: 5, + timerangeUnit: 'm', + timerangeCount: 15, + shouldCheckStatus: true, + shouldCheckAvailability: true, + availability: { range: 30, rangeUnit: 'd', threshold: '99' }, + filters: { tags: [], 'url.port': [], 'observer.geo.name': [], 'monitor.type': ['http'] }, + }, + rule_type_id: 'xpack.uptime.alerts.monitorStatus', + created_by: null, + updated_by: null, + created_at: '2021-10-20T20:54:08.529Z', + updated_at: '2021-10-20T20:54:08.529Z', + api_key_owner: null, + notify_when: 'onActionGroupChange', + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: 'df3e2100-31e7-11ec-b2d2-abfef52bb74d', + execution_status: { + status: 'ok', + last_execution_date: '2021-10-21T09:33:22.044Z', + last_duration: 92, + }, + actions: [], + }, + { + id: '5bd4f720-31e8-11ec-b2d2-abfef52bb74d', + consumer: 'uptime', + tags: [], + name: 'http rule', + enabled: true, + throttle: null, + schedule: { interval: '1m' }, + params: { + search: 'url.domain : "www.cnn.com" ', + numTimes: 5, + timerangeUnit: 'm', + timerangeCount: 15, + shouldCheckStatus: true, + shouldCheckAvailability: true, + availability: { range: 30, rangeUnit: 'd', threshold: '99' }, + filters: { tags: [], 'url.port': [], 'observer.geo.name': [], 'monitor.type': ['http'] }, + }, + rule_type_id: 'xpack.uptime.alerts.monitorStatus', + created_by: null, + updated_by: null, + created_at: '2021-10-20T20:57:38.451Z', + updated_at: '2021-10-20T20:57:38.451Z', + api_key_owner: null, + notify_when: 'onActionGroupChange', + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: '5bf417e0-31e8-11ec-b2d2-abfef52bb74d', + execution_status: { + status: 'ok', + last_execution_date: '2021-10-21T09:33:22.043Z', + last_duration: 87, + }, + actions: [], + }, + ], +}; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.ts index b5994d7e5b1c4e..9f77b0833d8b65 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.ts @@ -5,10 +5,12 @@ * 2.0. */ +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { UMElasticsearchQueryFn } from '../adapters'; import { MonitorDetails, Ping } from '../../../common/runtime_types'; import { formatFilterString } from '../alerts/status_check'; import { UptimeESClient } from '../lib'; +import { createEsQuery } from '../../../common/utils/es_search'; export interface GetMonitorDetailsParams { monitorId: string; @@ -17,7 +19,7 @@ export interface GetMonitorDetailsParams { rulesClient: any; } -const getMonitorAlerts = async ({ +export const getMonitorAlerts = async ({ uptimeEsClient, rulesClient, monitorId, @@ -43,39 +45,48 @@ const getMonitorAlerts = async ({ monitorAlerts.push(currAlert); continue; } - const esParams = { - query: { - bool: { - filter: [ - { - term: { - 'monitor.id': monitorId, + + const parsedFilters: QueryDslQueryContainer | undefined = await formatFilterString( + uptimeEsClient, + currAlert.params.filters, + currAlert.params.search + ); + + const esParams = createEsQuery({ + body: { + query: { + bool: { + filter: [ + { + term: { + 'monitor.id': monitorId, + }, }, - }, - ], + ] as QueryDslQueryContainer[], + }, }, - }, - size: 0, - aggs: { - monitors: { - terms: { - field: 'monitor.id', - size: 1000, + size: 0, + aggs: { + monitors: { + terms: { + field: 'monitor.id', + size: 1000, + }, }, }, }, - }; + }); - const parsedFilters = await formatFilterString( - uptimeEsClient, - currAlert.params.filters, - currAlert.params.search - ); - esParams.query.bool = Object.assign({}, esParams.query.bool, parsedFilters?.bool); + if (parsedFilters) { + esParams.body.query.bool.filter.push(parsedFilters); + } - const { body: result } = await uptimeEsClient.search({ body: esParams }); + const { body: result } = await uptimeEsClient.search( + esParams, + `getMonitorsForAlert-${currAlert.name}` + ); - if (result.hits.total.value > 0) { + if (result?.hits.total.value > 0) { monitorAlerts.push(currAlert); } } @@ -124,7 +135,7 @@ export const getMonitorDetails: UMElasticsearchQueryFn { - const { timestamp, monitorId, stepIndex } = request.query; + const { timestamp, monitorId, stepIndex, location } = request.query; const step: JourneyStep | null = await libs.requests.getStepLastSuccessfulStep({ uptimeEsClient, monitorId, stepIndex, timestamp, + location, }); if (step === null) { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts index 09a841ff147a49..37fe71d143988f 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts @@ -11,7 +11,8 @@ import { setup as watchCreateJsonSetup } from './watch_create_json.helpers'; import { setup as watchCreateThresholdSetup } from './watch_create_threshold.helpers'; import { setup as watchEditSetup } from './watch_edit.helpers'; -export { getRandomString, findTestSubject, TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test/jest'; +export { getRandomString, findTestSubject } from '@kbn/test/jest'; export { wrapBodyResponse, unwrapBodyResponse } from './body_response'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/watcher/public/application/components/index.ts b/x-pack/plugins/watcher/public/application/components/index.ts index f70c15bda18b26..f10dfe9b1e2812 100644 --- a/x-pack/plugins/watcher/public/application/components/index.ts +++ b/x-pack/plugins/watcher/public/application/components/index.ts @@ -11,4 +11,5 @@ export { DeleteWatchesModal } from './delete_watches_modal'; export { ErrableFormRow } from './form_errors'; export { WatchStatus } from './watch_status'; export { SectionLoading } from './section_loading'; -export { SectionError, Error } from './section_error'; +export type { Error } from './section_error'; +export { SectionError } from './section_error'; diff --git a/x-pack/plugins/watcher/public/application/lib/api.ts b/x-pack/plugins/watcher/public/application/lib/api.ts index 1957ee8a52e287..61ea124695cb98 100644 --- a/x-pack/plugins/watcher/public/application/lib/api.ts +++ b/x-pack/plugins/watcher/public/application/lib/api.ts @@ -91,7 +91,7 @@ export const deleteWatches = async (watchIds: string[]) => { const body = JSON.stringify({ watchIds, }); - const { results } = await getHttpClient().post(`${basePath}/watches/delete`, { body }); + const { results } = await getHttpClient().post(`${basePath}/watches/delete`, { body }); return results; }; @@ -110,7 +110,7 @@ export const activateWatch = async (id: string) => { }; export const loadWatch = async (id: string) => { - const { watch } = await getHttpClient().get(`${basePath}/watch/${id}`); + const { watch } = await getHttpClient().get(`${basePath}/watch/${id}`); return Watch.fromUpstreamJson(watch); }; @@ -122,12 +122,12 @@ export const getMatchingIndices = async (pattern: string) => { pattern = `${pattern}*`; } const body = JSON.stringify({ pattern }); - const { indices } = await getHttpClient().post(`${basePath}/indices`, { body }); + const { indices } = await getHttpClient().post(`${basePath}/indices`, { body }); return indices; }; export const fetchFields = async (indexes: string[]) => { - const { fields } = await getHttpClient().post(`${basePath}/fields`, { + const { fields } = await getHttpClient().post(`${basePath}/fields`, { body: JSON.stringify({ indexes }), }); return fields; @@ -190,7 +190,7 @@ export const useLoadSettings = () => { }; export const ackWatchAction = async (watchId: string, actionId: string) => { - const { watchStatus } = await getHttpClient().put( + const { watchStatus } = await getHttpClient().put( `${basePath}/watch/${watchId}/action/${actionId}/acknowledge` ); return WatchStatus.fromUpstreamJson(watchStatus); diff --git a/x-pack/plugins/watcher/public/application/shared_imports.ts b/x-pack/plugins/watcher/public/application/shared_imports.ts index 977204c627e5c8..a076fed8be58f4 100644 --- a/x-pack/plugins/watcher/public/application/shared_imports.ts +++ b/x-pack/plugins/watcher/public/application/shared_imports.ts @@ -5,10 +5,12 @@ * 2.0. */ -export { +export type { SendRequestConfig, SendRequestResponse, UseRequestConfig, +} from '../../../../../src/plugins/es_ui_shared/public'; +export { sendRequest, useRequest, XJson, diff --git a/x-pack/plugins/watcher/public/legacy/calc_es_interval.js b/x-pack/plugins/watcher/public/legacy/calc_es_interval.ts similarity index 83% rename from x-pack/plugins/watcher/public/legacy/calc_es_interval.js rename to x-pack/plugins/watcher/public/legacy/calc_es_interval.ts index 29f0f0f56d38d5..cae88b797ea4f3 100644 --- a/x-pack/plugins/watcher/public/legacy/calc_es_interval.js +++ b/x-pack/plugins/watcher/public/legacy/calc_es_interval.ts @@ -7,7 +7,7 @@ import dateMath from '@elastic/datemath'; -import { parseEsInterval } from './index'; +import { parseEsInterval } from './parse_es_interval'; const unitsDesc = dateMath.unitsDesc; const largeMax = unitsDesc.indexOf('M'); @@ -17,10 +17,9 @@ const largeMax = unitsDesc.indexOf('M'); * compatible expression, and provide * associated metadata * - * @param {moment.duration} duration - * @return {object} + * @param duration */ -export function convertDurationToNormalizedEsInterval(duration) { +export function convertDurationToNormalizedEsInterval(duration: moment.Duration) { for (let i = 0; i < unitsDesc.length; i++) { const unit = unitsDesc[i]; const val = duration.as(unit); @@ -35,7 +34,7 @@ export function convertDurationToNormalizedEsInterval(duration) { return { value: val, - unit: unit, + unit, expression: val + unit, }; } @@ -49,7 +48,7 @@ export function convertDurationToNormalizedEsInterval(duration) { }; } -export function convertIntervalToEsInterval(interval) { +export function convertIntervalToEsInterval(interval: string) { const { value, unit } = parseEsInterval(interval); return { value, diff --git a/x-pack/plugins/watcher/public/legacy/parse_es_interval/index.ts b/x-pack/plugins/watcher/public/legacy/parse_es_interval/index.ts index 0af13b3abbf5ec..b172a0b217c55c 100644 --- a/x-pack/plugins/watcher/public/legacy/parse_es_interval/index.ts +++ b/x-pack/plugins/watcher/public/legacy/parse_es_interval/index.ts @@ -5,7 +5,8 @@ * 2.0. */ -export { parseEsInterval, ParsedInterval } from './parse_es_interval'; +export type { ParsedInterval } from './parse_es_interval'; +export { parseEsInterval } from './parse_es_interval'; export { InvalidEsCalendarIntervalError } from './invalid_es_calendar_interval_error'; export { InvalidEsIntervalFormatError } from './invalid_es_interval_format_error'; export { isValidEsInterval } from './is_valid_es_interval'; diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index e31b12cd0115d7..77212274b6ade5 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -71,6 +71,10 @@ const onlyNotInCoverageTests = [ require.resolve('../test/saved_object_api_integration/security_and_spaces/config_trial.ts'), require.resolve('../test/saved_object_api_integration/security_and_spaces/config_basic.ts'), require.resolve('../test/saved_object_api_integration/spaces_only/config.ts'), + // TODO: Enable once RBAC timeline search strategy + // tests updated + // require.resolve('../test/timeline/security_and_spaces/config_basic.ts'), + require.resolve('../test/timeline/security_and_spaces/config_trial.ts'), require.resolve('../test/ui_capabilities/security_and_spaces/config.ts'), require.resolve('../test/ui_capabilities/spaces_only/config.ts'), require.resolve('../test/upgrade_assistant_integration/config.js'), diff --git a/x-pack/scripts/functional_tests_server.js b/x-pack/scripts/functional_tests_server.js old mode 100644 new mode 100755 diff --git a/x-pack/test/accessibility/apps/index_lifecycle_management.ts b/x-pack/test/accessibility/apps/index_lifecycle_management.ts index 35f4a8e1adea57..6cec8d1cb891ab 100644 --- a/x-pack/test/accessibility/apps/index_lifecycle_management.ts +++ b/x-pack/test/accessibility/apps/index_lifecycle_management.ts @@ -7,6 +7,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; +const REPO_NAME = 'test'; const POLICY_NAME = 'ilm-a11y-test'; const POLICY_ALL_PHASES = { policy: { @@ -23,7 +24,7 @@ const POLICY_ALL_PHASES = { frozen: { actions: { searchable_snapshot: { - snapshot_repository: 'test', + snapshot_repository: REPO_NAME, }, }, }, @@ -46,7 +47,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esClient = getService('es'); const a11y = getService('a11y'); + const filterByPolicyName = async (policyName: string) => { + await testSubjects.setValue('ilmSearchBar', policyName); + }; + const findPolicyLinkInListView = async (policyName: string) => { + await filterByPolicyName(policyName); const links = await testSubjects.findAll('policyTablePolicyNameLink'); for (const link of links) { const name = await link.getVisibleText(); @@ -57,11 +63,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { throw new Error(`Could not find ${policyName} in policy table`); }; - // FLAKY - // https://github.com/elastic/kibana/issues/114541 - // https://github.com/elastic/kibana/issues/114542 - describe.skip('Index Lifecycle Management', async () => { + describe('Index Lifecycle Management', async () => { before(async () => { + await esClient.snapshot.createRepository({ + name: REPO_NAME, + body: { + type: 'fs', + settings: { + // use one of the values defined in path.repo in test/functional/config.js + location: '/tmp/', + }, + }, + verify: false, + }); await esClient.ilm.putLifecycle({ name: POLICY_NAME, body: POLICY_ALL_PHASES }); await esClient.indices.putIndexTemplate({ name: indexTemplateName, @@ -79,6 +93,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { + await esClient.snapshot.deleteRepository({ + name: REPO_NAME, + }); await esClient.ilm.deleteLifecycle({ name: POLICY_NAME }); await esClient.indices.deleteIndexTemplate({ name: indexTemplateName }); }); @@ -144,6 +161,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Add policy to index template modal', async () => { + await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); const addPolicyButton = await policyRow.findByTestSubject('addPolicyToTemplate'); @@ -157,6 +175,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Delete policy modal', async () => { + await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); const deleteButton = await policyRow.findByTestSubject('deletePolicy'); @@ -170,6 +189,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Index templates flyout', async () => { + await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); const actionsButton = await policyRow.findByTestSubject('viewIndexTemplates'); diff --git a/x-pack/test/accessibility/apps/lens.ts b/x-pack/test/accessibility/apps/lens.ts index b8ddd774741b67..e97dc869af48bb 100644 --- a/x-pack/test/accessibility/apps/lens.ts +++ b/x-pack/test/accessibility/apps/lens.ts @@ -13,13 +13,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); const listingTable = getService('listingTable'); + const kibanaServer = getService('kibanaServer'); // Failing: See https://github.com/elastic/kibana/issues/115614 describe.skip('Lens', () => { const lensChartName = 'MyLensChart'; before(async () => { - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); after(async () => { @@ -29,7 +32,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await listingTable.clickDeleteSelected(); await PageObjects.common.clickConfirmOnModal(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); it('lens', async () => { diff --git a/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts b/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts index 48224ebcf73532..c9088c650c0339 100644 --- a/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts +++ b/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('can open job selection flyout', async () => { await PageObjects.dashboard.clickCreateDashboardPrompt(); await ml.dashboardEmbeddables.assertDashboardIsEmpty(); - await ml.dashboardEmbeddables.openJobSelectionFlyout(); + await ml.dashboardEmbeddables.openAnomalyJobSelectionFlyout('ml_anomaly_charts'); await a11y.testAppSnapshot(); }); diff --git a/x-pack/test/accessibility/apps/reporting.ts b/x-pack/test/accessibility/apps/reporting.ts index bccb650fa08cad..91356ef85972bd 100644 --- a/x-pack/test/accessibility/apps/reporting.ts +++ b/x-pack/test/accessibility/apps/reporting.ts @@ -16,7 +16,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const reporting = getService('reporting'); - const esArchiver = getService('esArchiver'); const security = getService('security'); describe('Reporting', () => { @@ -33,17 +32,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); - + await reporting.initLogs(); await createReportingUser(); await reporting.loginReportingUser(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - + await reporting.teardownLogs(); await deleteReportingUser(); }); diff --git a/x-pack/test/alerting_api_integration/common/lib/index.ts b/x-pack/test/alerting_api_integration/common/lib/index.ts index eeb9c882696676..305c42b5c1d64d 100644 --- a/x-pack/test/alerting_api_integration/common/lib/index.ts +++ b/x-pack/test/alerting_api_integration/common/lib/index.ts @@ -14,7 +14,8 @@ export { getConsumerUnauthorizedErrorMessage, getProducerUnauthorizedErrorMessage, } from './alert_utils'; -export { TaskManagerUtils, TaskManagerDoc } from './task_manager_utils'; +export type { TaskManagerDoc } from './task_manager_utils'; +export { TaskManagerUtils } from './task_manager_utils'; export * from './test_assertions'; export { checkAAD } from './check_aad'; export { getEventLog } from './get_event_log'; diff --git a/x-pack/test/api_integration/apis/lens/telemetry.ts b/x-pack/test/api_integration/apis/lens/telemetry.ts index 1c0c67a5203d6a..f29df1c358d371 100644 --- a/x-pack/test/api_integration/apis/lens/telemetry.ts +++ b/x-pack/test/api_integration/apis/lens/telemetry.ts @@ -21,6 +21,7 @@ const COMMON_HEADERS = { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const es = getService('es'); + const kibanaServer = getService('kibanaServer'); async function assertExpectedSavedObjects(num: number) { // Make sure that new/deleted docs are available to search @@ -172,9 +173,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should collect telemetry on saved visualization types with a painless script', async () => { - const esArchiver = getService('esArchiver'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); const kibanaClient = convertToKibanaClient(es); const results = await getVisualizationCounts(() => Promise.resolve(kibanaClient), '.kibana'); @@ -194,7 +196,9 @@ export default ({ getService }: FtrProviderContext) => { }); expect(results.saved_overall_total).to.eql(3); - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); }); }; diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts index a9721c58565985..469ee43e4bef1d 100644 --- a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts +++ b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { registerEsHelpers, SlmPolicy } from './elasticsearch'; +export type { SlmPolicy } from './elasticsearch'; +export { registerEsHelpers } from './elasticsearch'; diff --git a/x-pack/test/api_integration/apis/metrics_ui/http_source.ts b/x-pack/test/api_integration/apis/metrics_ui/http_source.ts index 65a350c523ee32..f0eba78f90a02e 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/http_source.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/http_source.ts @@ -42,13 +42,6 @@ export default function ({ getService }: FtrProviderContext) { return resp.then((data) => { expect(data).to.have.property('source'); expect(data?.source.configuration.metricAlias).to.equal('metrics-*,metricbeat-*'); - expect(data?.source.configuration.fields).to.eql({ - container: 'container.id', - host: 'host.name', - pod: 'kubernetes.pod.uid', - tiebreaker: '_doc', - timestamp: '@timestamp', - }); expect(data?.source).to.have.property('status'); expect(data?.source.status?.metricIndicesExist).to.equal(true); }); diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts b/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts index 5b615c4b189168..516c262429299c 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts @@ -40,8 +40,6 @@ export default function ({ getService }: FtrProviderContext) { type: 'index_name', indexName: 'logs-*,filebeat-*,kibana_sample_data_logs*', }); - expect(configuration.fields.timestamp).to.be('@timestamp'); - expect(configuration.fields.tiebreaker).to.be('_doc'); expect(configuration.logColumns[0]).to.have.key('timestampColumn'); expect(configuration.logColumns[1]).to.have.key('fieldColumn'); expect(configuration.logColumns[2]).to.have.key('messageColumn'); @@ -58,10 +56,6 @@ export default function ({ getService }: FtrProviderContext) { type: 'index_pattern', indexPatternId: 'kip-id', }, - fields: { - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, logColumns: [ { messageColumn: { @@ -83,8 +77,6 @@ export default function ({ getService }: FtrProviderContext) { type: 'index_pattern', indexPatternId: 'kip-id', }); - expect(configuration.fields.timestamp).to.be('TIMESTAMP'); - expect(configuration.fields.tiebreaker).to.be('TIEBREAKER'); expect(configuration.logColumns).to.have.length(1); expect(configuration.logColumns[0]).to.have.key('messageColumn'); @@ -111,8 +103,6 @@ export default function ({ getService }: FtrProviderContext) { type: 'index_name', indexName: 'logs-*,filebeat-*,kibana_sample_data_logs*', }); - expect(configuration.fields.timestamp).to.be('@timestamp'); - expect(configuration.fields.tiebreaker).to.be('_doc'); expect(configuration.logColumns).to.have.length(3); expect(configuration.logColumns[0]).to.have.key('timestampColumn'); expect(configuration.logColumns[1]).to.have.key('fieldColumn'); @@ -142,10 +132,6 @@ export default function ({ getService }: FtrProviderContext) { type: 'index_pattern', indexPatternId: 'kip-id', }, - fields: { - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, logColumns: [ { messageColumn: { @@ -166,8 +152,6 @@ export default function ({ getService }: FtrProviderContext) { type: 'index_pattern', indexPatternId: 'kip-id', }); - expect(configuration.fields.timestamp).to.be('TIMESTAMP'); - expect(configuration.fields.tiebreaker).to.be('TIEBREAKER'); expect(configuration.logColumns).to.have.length(1); expect(configuration.logColumns[0]).to.have.key('messageColumn'); }); @@ -189,8 +173,6 @@ export default function ({ getService }: FtrProviderContext) { type: 'index_name', indexName: 'logs-*,filebeat-*,kibana_sample_data_logs*', }); - expect(configuration.fields.timestamp).to.be('@timestamp'); - expect(configuration.fields.tiebreaker).to.be('_doc'); expect(configuration.logColumns).to.have.length(3); expect(configuration.logColumns[0]).to.have.key('timestampColumn'); expect(configuration.logColumns[1]).to.have.key('fieldColumn'); diff --git a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts index bf5e9532edf256..4467afb7585ad4 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts @@ -54,11 +54,6 @@ export default function ({ getService }: FtrProviderContext) { metricsExplorerDefaultView: 'default', anomalyThreshold: 70, fields: { - container: 'container.id', - host: 'host.name', - pod: 'kubernetes.od.uid', - tiebreaker: '_doc', - timestamp: '@timestamp', message: ['message'], }, logColumns: [ diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics_alerting.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics_alerting.ts index f2c9d48ad46529..eb8888a613dc3c 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metrics_alerting.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metrics_alerting.ts @@ -37,11 +37,7 @@ export default function ({ getService }: FtrProviderContext) { start: moment().subtract(25, 'minutes').valueOf(), end: moment().valueOf(), }; - const searchBody = getElasticsearchMetricQuery( - getSearchParams(aggType), - '@timestamp', - timeframe - ); + const searchBody = getElasticsearchMetricQuery(getSearchParams(aggType), timeframe); const result = await client.search({ index, // @ts-expect-error @elastic/elasticsearch AggregationsBucketsPath is not valid @@ -61,7 +57,6 @@ export default function ({ getService }: FtrProviderContext) { }; const searchBody = getElasticsearchMetricQuery( getSearchParams('avg'), - '@timestamp', timeframe, undefined, '{"bool":{"should":[{"match_phrase":{"agent.hostname":"foo"}}],"minimum_should_match":1}}' @@ -85,7 +80,6 @@ export default function ({ getService }: FtrProviderContext) { }; const searchBody = getElasticsearchMetricQuery( getSearchParams(aggType), - '@timestamp', timeframe, 'agent.id' ); @@ -106,7 +100,6 @@ export default function ({ getService }: FtrProviderContext) { }; const searchBody = getElasticsearchMetricQuery( getSearchParams('avg'), - '@timestamp', timeframe, 'agent.id', '{"bool":{"should":[{"match_phrase":{"agent.hostname":"foo"}}],"minimum_should_match":1}}' diff --git a/x-pack/test/api_integration/apis/metrics_ui/sources.ts b/x-pack/test/api_integration/apis/metrics_ui/sources.ts index 8c43a05f5eeb62..e9ea8f725073f4 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/sources.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/sources.ts @@ -66,11 +66,6 @@ export default function ({ getService }: FtrProviderContext) { expect(configuration?.name).to.be('UPDATED_NAME'); expect(configuration?.description).to.be('UPDATED_DESCRIPTION'); expect(configuration?.metricAlias).to.be('metricbeat-**'); - expect(configuration?.fields.host).to.be('host.name'); - expect(configuration?.fields.pod).to.be('kubernetes.pod.uid'); - expect(configuration?.fields.tiebreaker).to.be('_doc'); - expect(configuration?.fields.timestamp).to.be('@timestamp'); - expect(configuration?.fields.container).to.be('container.id'); expect(configuration?.anomalyThreshold).to.be(50); expect(status?.metricIndicesExist).to.be(true); }); @@ -104,40 +99,6 @@ export default function ({ getService }: FtrProviderContext) { expect(status?.metricIndicesExist).to.be(true); }); - it('applies a single nested field update to an existing source', async () => { - const creationResponse = await patchRequest({ - name: 'NAME', - fields: { - host: 'HOST', - }, - }); - - const initialVersion = creationResponse?.source.version; - const createdAt = creationResponse?.source.updatedAt; - - expect(initialVersion).to.be.a('string'); - expect(createdAt).to.be.greaterThan(0); - - const updateResponse = await patchRequest({ - fields: { - container: 'UPDATED_CONTAINER', - }, - }); - - const version = updateResponse?.source.version; - const updatedAt = updateResponse?.source.updatedAt; - const configuration = updateResponse?.source.configuration; - - expect(version).to.be.a('string'); - expect(version).to.not.be(initialVersion); - expect(updatedAt).to.be.greaterThan(createdAt || 0); - expect(configuration?.fields.container).to.be('UPDATED_CONTAINER'); - expect(configuration?.fields.host).to.be('HOST'); - expect(configuration?.fields.pod).to.be('kubernetes.pod.uid'); - expect(configuration?.fields.tiebreaker).to.be('_doc'); - expect(configuration?.fields.timestamp).to.be('@timestamp'); - }); - it('validates anomalyThreshold is between range 1-100', async () => { // create config with bad request await supertest diff --git a/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts b/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts index c7b6bbb84436fd..24cf4699d952c4 100644 --- a/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts +++ b/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts @@ -58,7 +58,11 @@ export default function ({ getService }: FtrProviderContext) { }, strategy: 'securitySolutionSearchStrategy', }); - expect(networkDns.rawResponse.aggregations?.dns_count).to.eql({ value: 6604 }); + // This can have a odd unknown flake if we do anything more strict than this. + const dnsCount = networkDns.rawResponse.aggregations?.dns_count as unknown as { + value: number; + }; + expect(dnsCount.value).to.be.above(0); }); }); }); diff --git a/x-pack/test/api_integration/apis/security_solution/timeline_migrations.ts b/x-pack/test/api_integration/apis/security_solution/timeline_migrations.ts index 72607cbe8bf79c..c5ea6e2aeaa631 100644 --- a/x-pack/test/api_integration/apis/security_solution/timeline_migrations.ts +++ b/x-pack/test/api_integration/apis/security_solution/timeline_migrations.ts @@ -199,7 +199,6 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body.data.getOneTimeline.savedQueryId).to.be("It's me"); }); }); - describe('pinned events timelineId', () => { it('removes the timelineId in the saved object', async () => { const timelines = await getSavedObjectFromES( diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index 978f3f0d68673f..ab22522acac527 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -15,7 +15,7 @@ import { createApmUser, APM_TEST_PASSWORD, ApmUser } from './authentication'; import { APMFtrConfigName } from '../configs'; import { createApmApiClient } from './apm_api_supertest'; import { registry } from './registry'; -import { synthtraceEsClient } from './synthtrace_es_client'; +import { synthtraceEsClientService } from './synthtrace_es_client_service'; interface Config { name: APMFtrConfigName; @@ -77,7 +77,7 @@ export function createTestConfig(config: Config) { servers, services: { ...services, - synthtraceEsClient, + synthtraceEsClient: synthtraceEsClientService, apmApiClient: async (context: InheritedFtrProviderContext) => { const security = context.getService('security'); await security.init(); diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0/mappings.json new file mode 100644 index 00000000000000..2d05717fa5725c --- /dev/null +++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0/mappings.json @@ -0,0 +1,22073 @@ +{ + "type": "index", + "value": { + "aliases": { + ".ml-anomalies-.write-apm-environment_not_defined-337d-high_mean_transaction_duration": { + "is_hidden": true + }, + ".ml-anomalies-.write-apm-production-6117-high_mean_transaction_duration": { + "is_hidden": true + }, + ".ml-anomalies-.write-apm-testing-41e5-high_mean_transaction_duration": { + "is_hidden": true + }, + ".ml-anomalies-apm-environment_not_defined-337d-high_mean_transaction_duration": { + "filter": { + "term": { + "job_id": { + "boost": 1, + "value": "apm-environment_not_defined-337d-high_mean_transaction_duration" + } + } + }, + "is_hidden": true + }, + ".ml-anomalies-apm-production-6117-high_mean_transaction_duration": { + "filter": { + "term": { + "job_id": { + "boost": 1, + "value": "apm-production-6117-high_mean_transaction_duration" + } + } + }, + "is_hidden": true + }, + ".ml-anomalies-apm-testing-41e5-high_mean_transaction_duration": { + "filter": { + "term": { + "job_id": { + "boost": 1, + "value": "apm-testing-41e5-high_mean_transaction_duration" + } + } + }, + "is_hidden": true + } + }, + "index": ".ml-anomalies-shared", + "mappings": { + "_meta": { + "version": "7.14.0" + }, + "dynamic_templates": [ + { + "strings_as_keywords": { + "mapping": { + "type": "keyword" + }, + "match": "*" + } + } + ], + "properties": { + "actual": { + "type": "double" + }, + "all_field_values": { + "analyzer": "whitespace", + "type": "text" + }, + "anomaly_score": { + "type": "double" + }, + "assignment_memory_basis": { + "type": "keyword" + }, + "average_bucket_processing_time_ms": { + "type": "double" + }, + "bucket_allocation_failures_count": { + "type": "long" + }, + "bucket_count": { + "type": "long" + }, + "bucket_influencers": { + "properties": { + "anomaly_score": { + "type": "double" + }, + "bucket_span": { + "type": "long" + }, + "influencer_field_name": { + "type": "keyword" + }, + "initial_anomaly_score": { + "type": "double" + }, + "is_interim": { + "type": "boolean" + }, + "job_id": { + "type": "keyword" + }, + "probability": { + "type": "double" + }, + "raw_anomaly_score": { + "type": "double" + }, + "result_type": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + } + }, + "type": "nested" + }, + "bucket_span": { + "type": "long" + }, + "by_field_name": { + "type": "keyword" + }, + "by_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "categorization_status": { + "type": "keyword" + }, + "categorized_doc_count": { + "type": "keyword" + }, + "category_id": { + "type": "long" + }, + "causes": { + "properties": { + "actual": { + "type": "double" + }, + "by_field_name": { + "type": "keyword" + }, + "by_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "correlated_by_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "field_name": { + "type": "keyword" + }, + "function": { + "type": "keyword" + }, + "function_description": { + "type": "keyword" + }, + "geo_results": { + "properties": { + "actual_point": { + "type": "geo_point" + }, + "typical_point": { + "type": "geo_point" + } + } + }, + "over_field_name": { + "type": "keyword" + }, + "over_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "partition_field_name": { + "type": "keyword" + }, + "partition_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "probability": { + "type": "double" + }, + "typical": { + "type": "double" + } + }, + "type": "nested" + }, + "dead_category_count": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "detector_index": { + "type": "integer" + }, + "earliest_record_timestamp": { + "type": "date" + }, + "empty_bucket_count": { + "type": "long" + }, + "event_count": { + "type": "long" + }, + "examples": { + "type": "text" + }, + "exponential_average_bucket_processing_time_ms": { + "type": "double" + }, + "exponential_average_calculation_context": { + "properties": { + "incremental_metric_value_ms": { + "type": "double" + }, + "latest_timestamp": { + "type": "date" + }, + "previous_exponential_average_ms": { + "type": "double" + } + } + }, + "failed_category_count": { + "type": "keyword" + }, + "field_name": { + "type": "keyword" + }, + "forecast_create_timestamp": { + "type": "date" + }, + "forecast_end_timestamp": { + "type": "date" + }, + "forecast_expiry_timestamp": { + "type": "date" + }, + "forecast_id": { + "type": "keyword" + }, + "forecast_lower": { + "type": "double" + }, + "forecast_memory_bytes": { + "type": "long" + }, + "forecast_messages": { + "type": "keyword" + }, + "forecast_prediction": { + "type": "double" + }, + "forecast_progress": { + "type": "double" + }, + "forecast_start_timestamp": { + "type": "date" + }, + "forecast_status": { + "type": "keyword" + }, + "forecast_upper": { + "type": "double" + }, + "frequent_category_count": { + "type": "keyword" + }, + "function": { + "type": "keyword" + }, + "function_description": { + "type": "keyword" + }, + "geo_results": { + "properties": { + "actual_point": { + "type": "geo_point" + }, + "typical_point": { + "type": "geo_point" + } + } + }, + "influencer_field_name": { + "type": "keyword" + }, + "influencer_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "influencer_score": { + "type": "double" + }, + "influencers": { + "properties": { + "influencer_field_name": { + "type": "keyword" + }, + "influencer_field_values": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + } + }, + "type": "nested" + }, + "initial_anomaly_score": { + "type": "double" + }, + "initial_influencer_score": { + "type": "double" + }, + "initial_record_score": { + "type": "double" + }, + "input_bytes": { + "type": "long" + }, + "input_field_count": { + "type": "long" + }, + "input_record_count": { + "type": "long" + }, + "invalid_date_count": { + "type": "long" + }, + "is_interim": { + "type": "boolean" + }, + "job_id": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "last_data_time": { + "type": "date" + }, + "latest_empty_bucket_timestamp": { + "type": "date" + }, + "latest_record_time_stamp": { + "type": "date" + }, + "latest_record_timestamp": { + "type": "date" + }, + "latest_result_time_stamp": { + "type": "date" + }, + "latest_sparse_bucket_timestamp": { + "type": "date" + }, + "log_time": { + "type": "date" + }, + "max_matching_length": { + "type": "long" + }, + "maximum_bucket_processing_time_ms": { + "type": "double" + }, + "memory_status": { + "type": "keyword" + }, + "min_version": { + "type": "keyword" + }, + "minimum_bucket_processing_time_ms": { + "type": "double" + }, + "missing_field_count": { + "type": "long" + }, + "mlcategory": { + "type": "keyword" + }, + "model_bytes": { + "type": "long" + }, + "model_bytes_exceeded": { + "type": "keyword" + }, + "model_bytes_memory_limit": { + "type": "keyword" + }, + "model_feature": { + "type": "keyword" + }, + "model_lower": { + "type": "double" + }, + "model_median": { + "type": "double" + }, + "model_size_stats": { + "properties": { + "assignment_memory_basis": { + "type": "keyword" + }, + "bucket_allocation_failures_count": { + "type": "long" + }, + "categorization_status": { + "type": "keyword" + }, + "categorized_doc_count": { + "type": "keyword" + }, + "dead_category_count": { + "type": "keyword" + }, + "failed_category_count": { + "type": "keyword" + }, + "frequent_category_count": { + "type": "keyword" + }, + "job_id": { + "type": "keyword" + }, + "log_time": { + "type": "date" + }, + "memory_status": { + "type": "keyword" + }, + "model_bytes": { + "type": "long" + }, + "model_bytes_exceeded": { + "type": "keyword" + }, + "model_bytes_memory_limit": { + "type": "keyword" + }, + "peak_model_bytes": { + "type": "long" + }, + "rare_category_count": { + "type": "keyword" + }, + "result_type": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "total_by_field_count": { + "type": "long" + }, + "total_category_count": { + "type": "keyword" + }, + "total_over_field_count": { + "type": "long" + }, + "total_partition_field_count": { + "type": "long" + } + } + }, + "model_upper": { + "type": "double" + }, + "multi_bucket_impact": { + "type": "double" + }, + "num_matches": { + "type": "long" + }, + "out_of_order_timestamp_count": { + "type": "long" + }, + "over_field_name": { + "type": "keyword" + }, + "over_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "partition_field_name": { + "type": "keyword" + }, + "partition_field_value": { + "copy_to": [ + "all_field_values" + ], + "type": "keyword" + }, + "peak_model_bytes": { + "type": "keyword" + }, + "preferred_to_categories": { + "type": "long" + }, + "probability": { + "type": "double" + }, + "processed_field_count": { + "type": "long" + }, + "processed_record_count": { + "type": "long" + }, + "processing_time_ms": { + "type": "long" + }, + "quantiles": { + "enabled": false, + "type": "object" + }, + "rare_category_count": { + "type": "keyword" + }, + "raw_anomaly_score": { + "type": "double" + }, + "record_score": { + "type": "double" + }, + "regex": { + "type": "keyword" + }, + "result_type": { + "type": "keyword" + }, + "retain": { + "type": "boolean" + }, + "scheduled_events": { + "type": "keyword" + }, + "search_count": { + "type": "long" + }, + "service": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "snapshot_doc_count": { + "type": "integer" + }, + "snapshot_id": { + "type": "keyword" + }, + "sparse_bucket_count": { + "type": "long" + }, + "terms": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "total_by_field_count": { + "type": "long" + }, + "total_category_count": { + "type": "keyword" + }, + "total_over_field_count": { + "type": "long" + }, + "total_partition_field_count": { + "type": "long" + }, + "total_search_time_ms": { + "type": "double" + }, + "transaction": { + "properties": { + "type": { + "type": "keyword" + } + } + }, + "typical": { + "type": "double" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "hidden": "true", + "number_of_replicas": "1", + "number_of_shards": "1", + "translog": { + "durability": "async" + } + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": ".ml-config", + "mappings": { + "_meta": { + "version": "7.14.0" + }, + "dynamic_templates": [ + { + "strings_as_keywords": { + "mapping": { + "type": "keyword" + }, + "match": "*" + } + } + ], + "properties": { + "aggregations": { + "enabled": false, + "type": "object" + }, + "allow_lazy_open": { + "type": "keyword" + }, + "allow_lazy_start": { + "type": "keyword" + }, + "analysis": { + "properties": { + "classification": { + "properties": { + "alpha": { + "type": "double" + }, + "class_assignment_objective": { + "type": "keyword" + }, + "dependent_variable": { + "type": "keyword" + }, + "downsample_factor": { + "type": "double" + }, + "early_stopping_enabled": { + "type": "boolean" + }, + "eta": { + "type": "double" + }, + "eta_growth_rate_per_tree": { + "type": "double" + }, + "feature_bag_fraction": { + "type": "double" + }, + "feature_processors": { + "enabled": false, + "type": "object" + }, + "gamma": { + "type": "double" + }, + "lambda": { + "type": "double" + }, + "max_optimization_rounds_per_hyperparameter": { + "type": "integer" + }, + "max_trees": { + "type": "integer" + }, + "num_top_classes": { + "type": "integer" + }, + "num_top_feature_importance_values": { + "type": "integer" + }, + "prediction_field_name": { + "type": "keyword" + }, + "randomize_seed": { + "type": "keyword" + }, + "soft_tree_depth_limit": { + "type": "double" + }, + "soft_tree_depth_tolerance": { + "type": "double" + }, + "training_percent": { + "type": "double" + } + } + }, + "outlier_detection": { + "properties": { + "compute_feature_influence": { + "type": "keyword" + }, + "feature_influence_threshold": { + "type": "double" + }, + "method": { + "type": "keyword" + }, + "n_neighbors": { + "type": "integer" + }, + "outlier_fraction": { + "type": "keyword" + }, + "standardization_enabled": { + "type": "keyword" + } + } + }, + "regression": { + "properties": { + "alpha": { + "type": "double" + }, + "dependent_variable": { + "type": "keyword" + }, + "downsample_factor": { + "type": "double" + }, + "early_stopping_enabled": { + "type": "boolean" + }, + "eta": { + "type": "double" + }, + "eta_growth_rate_per_tree": { + "type": "double" + }, + "feature_bag_fraction": { + "type": "double" + }, + "feature_processors": { + "enabled": false, + "type": "object" + }, + "gamma": { + "type": "double" + }, + "lambda": { + "type": "double" + }, + "loss_function": { + "type": "keyword" + }, + "loss_function_parameter": { + "type": "double" + }, + "max_optimization_rounds_per_hyperparameter": { + "type": "integer" + }, + "max_trees": { + "type": "integer" + }, + "num_top_feature_importance_values": { + "type": "integer" + }, + "prediction_field_name": { + "type": "keyword" + }, + "randomize_seed": { + "type": "keyword" + }, + "soft_tree_depth_limit": { + "type": "double" + }, + "soft_tree_depth_tolerance": { + "type": "double" + }, + "training_percent": { + "type": "double" + } + } + } + } + }, + "analysis_config": { + "properties": { + "bucket_span": { + "type": "keyword" + }, + "categorization_analyzer": { + "enabled": false, + "type": "object" + }, + "categorization_field_name": { + "type": "keyword" + }, + "categorization_filters": { + "type": "keyword" + }, + "detectors": { + "properties": { + "by_field_name": { + "type": "keyword" + }, + "custom_rules": { + "properties": { + "actions": { + "type": "keyword" + }, + "conditions": { + "properties": { + "applies_to": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "double" + } + }, + "type": "nested" + }, + "scope": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "detector_description": { + "type": "text" + }, + "detector_index": { + "type": "integer" + }, + "exclude_frequent": { + "type": "keyword" + }, + "field_name": { + "type": "keyword" + }, + "function": { + "type": "keyword" + }, + "over_field_name": { + "type": "keyword" + }, + "partition_field_name": { + "type": "keyword" + }, + "use_null": { + "type": "boolean" + } + } + }, + "influencers": { + "type": "keyword" + }, + "latency": { + "type": "keyword" + }, + "multivariate_by_fields": { + "type": "boolean" + }, + "per_partition_categorization": { + "properties": { + "enabled": { + "type": "boolean" + }, + "stop_on_warn": { + "type": "boolean" + } + } + }, + "summary_count_field_name": { + "type": "keyword" + } + } + }, + "analysis_limits": { + "properties": { + "categorization_examples_limit": { + "type": "long" + }, + "model_memory_limit": { + "type": "keyword" + } + } + }, + "analyzed_fields": { + "enabled": false, + "type": "object" + }, + "background_persist_interval": { + "type": "keyword" + }, + "blocked": { + "properties": { + "reason": { + "type": "keyword" + }, + "task_id": { + "type": "keyword" + } + } + }, + "chunking_config": { + "properties": { + "mode": { + "type": "keyword" + }, + "time_span": { + "type": "keyword" + } + } + }, + "config_type": { + "type": "keyword" + }, + "create_time": { + "type": "date" + }, + "custom_settings": { + "enabled": false, + "type": "object" + }, + "daily_model_snapshot_retention_after_days": { + "type": "long" + }, + "data_description": { + "properties": { + "field_delimiter": { + "type": "keyword" + }, + "format": { + "type": "keyword" + }, + "quote_character": { + "type": "keyword" + }, + "time_field": { + "type": "keyword" + }, + "time_format": { + "type": "keyword" + } + } + }, + "datafeed_id": { + "type": "keyword" + }, + "delayed_data_check_config": { + "properties": { + "check_window": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + } + } + }, + "deleting": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "dest": { + "properties": { + "index": { + "type": "keyword" + }, + "results_field": { + "type": "keyword" + } + } + }, + "finished_time": { + "type": "date" + }, + "frequency": { + "type": "keyword" + }, + "groups": { + "type": "keyword" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "id": { + "type": "keyword" + }, + "indices": { + "type": "keyword" + }, + "indices_options": { + "enabled": false, + "type": "object" + }, + "job_id": { + "type": "keyword" + }, + "job_type": { + "type": "keyword" + }, + "job_version": { + "type": "keyword" + }, + "max_empty_searches": { + "type": "keyword" + }, + "max_num_threads": { + "type": "integer" + }, + "model_memory_limit": { + "type": "keyword" + }, + "model_plot_config": { + "properties": { + "annotations_enabled": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "terms": { + "type": "keyword" + } + } + }, + "model_snapshot_id": { + "type": "keyword" + }, + "model_snapshot_min_version": { + "type": "keyword" + }, + "model_snapshot_retention_days": { + "type": "long" + }, + "query": { + "enabled": false, + "type": "object" + }, + "query_delay": { + "type": "keyword" + }, + "renormalization_window_days": { + "type": "long" + }, + "results_index_name": { + "type": "keyword" + }, + "results_retention_days": { + "type": "long" + }, + "runtime_mappings": { + "enabled": false, + "type": "object" + }, + "script_fields": { + "enabled": false, + "type": "object" + }, + "scroll_size": { + "type": "long" + }, + "source": { + "properties": { + "_source": { + "enabled": false, + "type": "object" + }, + "index": { + "type": "keyword" + }, + "query": { + "enabled": false, + "type": "object" + }, + "runtime_mappings": { + "enabled": false, + "type": "object" + } + } + }, + "version": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "blocks": { + "read_only_allow_delete": "false" + }, + "max_result_window": "10000", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + "apm-7.14.0-error": { + "is_write_index": true + } + }, + "index": "apm-7.14.0-error-000001", + "mappings": { + "_meta": { + "beat": "apm", + "version": "7.14.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "kubernetes.selectors.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.selectors.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "histogram": { + "mapping": { + "type": "histogram" + } + } + }, + { + "transaction.marks": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "transaction.marks.*" + } + }, + { + "transaction.marks.*.*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "transaction.marks.*.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "child": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "dynamic": "false", + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "container": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "grouping_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "experimental": { + "dynamic": "true", + "type": "object" + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "dynamic": "false", + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "cpu": { + "properties": { + "usage": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "disk": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network": { + "properties": { + "egress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "ingress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "dynamic": "false", + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "finished": { + "type": "boolean" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "dynamic": "false", + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "path": "container.image.name", + "type": "alias" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "ip": { + "type": "ip" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selectors": { + "properties": { + "*": { + "type": "object" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "dynamic": "true", + "properties": { + "company": { + "type": "keyword" + }, + "customer_tier": { + "type": "keyword" + }, + "foo": { + "type": "keyword" + }, + "lorem": { + "type": "keyword" + }, + "multi-line": { + "type": "keyword" + }, + "request_id": { + "type": "keyword" + }, + "this-is-a-very-long-tag-name-without-any-spaces": { + "type": "keyword" + } + } + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "metricset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "period": { + "meta": { + "unit": "ms" + }, + "type": "long" + } + } + }, + "network": { + "dynamic": "false", + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "carrier": { + "properties": { + "icc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mcc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mnc": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "connection_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "inner": { + "properties": { + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "observer": { + "dynamic": "false", + "properties": { + "egress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "listening": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_major": { + "type": "byte" + } + } + }, + "orchestrator": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "dynamic": "false", + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "profile": { + "dynamic": "false", + "properties": { + "alloc_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "alloc_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cpu": { + "properties": { + "ns": { + "meta": { + "unit": "nanos" + }, + "type": "long" + } + } + }, + "duration": { + "meta": { + "unit": "nanos" + }, + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "inuse_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "inuse_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "samples": { + "properties": { + "count": { + "type": "long" + } + } + }, + "stack": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "top": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "wall": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "author": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "session": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + } + } + }, + "source": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "sourcemap": { + "dynamic": "false", + "properties": { + "bundle_filepath": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "db": { + "dynamic": "false", + "properties": { + "link": { + "ignore_above": 1024, + "type": "keyword" + }, + "rows_affected": { + "type": "long" + } + } + }, + "destination": { + "dynamic": "false", + "properties": { + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "response_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "total": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "process": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs": { + "properties": { + "period": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + }, + "quota": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + }, + "throttled": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + }, + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "mem": { + "properties": { + "limit": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "size": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "subtechnique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "histogram": { + "type": "histogram" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "experience": { + "properties": { + "cls": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fid": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "longtask": { + "properties": { + "count": { + "type": "long" + }, + "max": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "sum": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "tbt": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + } + } + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "root": { + "type": "boolean" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "dynamic": "false", + "properties": { + "changes": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "blocks": { + "read_only_allow_delete": "false" + }, + "codec": "best_compression", + "lifecycle": { + "name": "apm-rollover-30-days", + "rollover_alias": "apm-7.14.0-error" + }, + "mapping": { + "total_fields": { + "limit": "2000" + } + }, + "max_docvalue_fields_search": "200", + "number_of_replicas": "1", + "number_of_shards": "1", + "priority": "100", + "refresh_interval": "5s" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + "apm-7.14.0-metric": { + "is_write_index": true + } + }, + "index": "apm-7.14.0-metric-000001", + "mappings": { + "_meta": { + "beat": "apm", + "version": "7.14.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "kubernetes.selectors.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.selectors.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "histogram": { + "mapping": { + "type": "histogram" + } + } + }, + { + "transaction.marks": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "transaction.marks.*" + } + }, + { + "transaction.marks.*.*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "transaction.marks.*.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "agent_config_applied": { + "type": "long" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "child": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "dynamic": "false", + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "clr": { + "properties": { + "gc": { + "properties": { + "count": { + "type": "long" + }, + "gen0size": { + "type": "long" + }, + "gen1size": { + "type": "float" + }, + "gen2size": { + "type": "long" + }, + "gen3size": { + "type": "float" + }, + "time": { + "type": "float" + } + } + } + } + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "container": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "grouping_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "experimental": { + "dynamic": "true", + "type": "object" + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "golang": { + "properties": { + "goroutines": { + "type": "long" + }, + "heap": { + "properties": { + "allocations": { + "properties": { + "active": { + "type": "float" + }, + "allocated": { + "type": "float" + }, + "frees": { + "type": "long" + }, + "idle": { + "type": "float" + }, + "mallocs": { + "type": "long" + }, + "objects": { + "type": "long" + }, + "total": { + "type": "float" + } + } + }, + "gc": { + "properties": { + "cpu_fraction": { + "type": "float" + }, + "next_gc_limit": { + "type": "float" + }, + "total_count": { + "type": "long" + }, + "total_pause": { + "properties": { + "ns": { + "type": "float" + } + } + } + } + }, + "system": { + "properties": { + "obtained": { + "type": "float" + }, + "released": { + "type": "float" + }, + "stack": { + "type": "float" + }, + "total": { + "type": "float" + } + } + } + } + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "dynamic": "false", + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "cpu": { + "properties": { + "usage": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "disk": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network": { + "properties": { + "egress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "ingress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "dynamic": "false", + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "finished": { + "type": "boolean" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "alloc": { + "type": "float" + }, + "count": { + "type": "long" + }, + "time": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "heap": { + "properties": { + "committed": { + "type": "float" + }, + "max": { + "type": "float" + }, + "pool": { + "properties": { + "committed": { + "type": "float" + }, + "max": { + "type": "float" + }, + "used": { + "type": "float" + } + } + }, + "used": { + "type": "float" + } + } + }, + "non_heap": { + "properties": { + "committed": { + "type": "float" + }, + "max": { + "type": "long" + }, + "used": { + "type": "float" + } + } + } + } + }, + "thread": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "kubernetes": { + "dynamic": "false", + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "path": "container.image.name", + "type": "alias" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "ip": { + "type": "ip" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selectors": { + "properties": { + "*": { + "type": "object" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "dynamic": "true", + "properties": { + "a": { + "type": "keyword" + }, + "charset": { + "type": "keyword" + }, + "connection": { + "type": "keyword" + }, + "env": { + "type": "keyword" + }, + "etag": { + "type": "keyword" + }, + "generation": { + "type": "keyword" + }, + "hostname": { + "type": "keyword" + }, + "implementation": { + "type": "keyword" + }, + "major": { + "type": "keyword" + }, + "method": { + "type": "keyword" + }, + "minor": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "patchlevel": { + "type": "keyword" + }, + "status": { + "type": "keyword" + }, + "transport": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "view": { + "type": "keyword" + } + } + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "metricset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "period": { + "meta": { + "unit": "ms" + }, + "type": "long" + } + } + }, + "network": { + "dynamic": "false", + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "carrier": { + "properties": { + "icc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mcc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mnc": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "connection_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "inner": { + "properties": { + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "nodejs": { + "properties": { + "eventloop": { + "properties": { + "delay": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "handles": { + "properties": { + "active": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "arrayBuffers": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "external": { + "properties": { + "bytes": { + "type": "float" + } + } + }, + "heap": { + "properties": { + "allocated": { + "properties": { + "bytes": { + "type": "float" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "float" + } + } + } + } + } + } + }, + "requests": { + "properties": { + "active": { + "type": "long" + } + } + } + } + }, + "observer": { + "dynamic": "false", + "properties": { + "egress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "listening": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_major": { + "type": "byte" + } + } + }, + "orchestrator": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "dynamic": "false", + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "profile": { + "dynamic": "false", + "properties": { + "alloc_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "alloc_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cpu": { + "properties": { + "ns": { + "meta": { + "unit": "nanos" + }, + "type": "long" + } + } + }, + "duration": { + "meta": { + "unit": "nanos" + }, + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "inuse_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "inuse_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "samples": { + "properties": { + "count": { + "type": "long" + } + } + }, + "stack": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "top": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "wall": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "prometheus": { + "properties": { + "metrics": { + "properties": { + "django_http_ajax_requests": { + "type": "long" + }, + "django_http_exceptions_total_by_type": { + "type": "long" + }, + "django_http_exceptions_total_by_view": { + "type": "long" + }, + "django_http_requests_before_middlewares": { + "type": "long" + }, + "django_http_requests_total_by_method": { + "type": "long" + }, + "django_http_requests_total_by_transport": { + "type": "long" + }, + "django_http_requests_total_by_view_transport_method": { + "type": "long" + }, + "django_http_requests_unknown_latency": { + "type": "long" + }, + "django_http_requests_unknown_latency_including_middlewares": { + "type": "long" + }, + "django_http_responses_before_middlewares": { + "type": "long" + }, + "django_http_responses_streaming": { + "type": "long" + }, + "django_http_responses_total_by_charset": { + "type": "long" + }, + "django_http_responses_total_by_status": { + "type": "long" + }, + "django_http_responses_total_by_status_view_method": { + "type": "long" + }, + "django_migrations_applied_total": { + "type": "long" + }, + "django_migrations_unapplied_total": { + "type": "long" + }, + "opbeans_python_line_items": { + "type": "long" + }, + "opbeans_python_orders": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + }, + "process_cpu_seconds": { + "type": "float" + }, + "process_max_fds": { + "type": "float" + }, + "process_open_fds": { + "type": "long" + }, + "process_resident_memory_bytes": { + "type": "float" + }, + "process_start_time_seconds": { + "type": "float" + }, + "process_virtual_memory_bytes": { + "type": "float" + }, + "python_gc_collections": { + "type": "long" + }, + "python_gc_objects_collected": { + "type": "long" + }, + "python_gc_objects_uncollectable": { + "type": "long" + }, + "python_info": { + "type": "long" + }, + "random_counter": { + "type": "long" + }, + "random_gauge": { + "type": "float" + }, + "random_summary": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ruby": { + "properties": { + "gc": { + "properties": { + "count": { + "type": "long" + } + } + }, + "heap": { + "properties": { + "allocations": { + "properties": { + "total": { + "type": "long" + } + } + }, + "slots": { + "properties": { + "free": { + "type": "long" + }, + "live": { + "type": "long" + } + } + } + } + }, + "threads": { + "type": "long" + } + } + }, + "rule": { + "properties": { + "author": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "session": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + } + } + }, + "source": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "sourcemap": { + "dynamic": "false", + "properties": { + "bundle_filepath": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "db": { + "dynamic": "false", + "properties": { + "link": { + "ignore_above": 1024, + "type": "keyword" + }, + "rows_affected": { + "type": "long" + } + } + }, + "destination": { + "dynamic": "false", + "properties": { + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "response_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "total": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "process": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs": { + "properties": { + "period": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + }, + "quota": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + }, + "throttled": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + }, + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "mem": { + "properties": { + "limit": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + }, + "stats": { + "properties": { + "inactive_file": { + "properties": { + "bytes": { + "type": "float" + } + } + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "system": { + "properties": { + "norm": { + "properties": { + "pct": { + "type": "float" + } + } + } + } + }, + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "user": { + "properties": { + "norm": { + "properties": { + "pct": { + "type": "float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "size": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "subtechnique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "histogram": { + "type": "histogram" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "experience": { + "properties": { + "cls": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fid": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "longtask": { + "properties": { + "count": { + "type": "long" + }, + "max": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "sum": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "tbt": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + } + } + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "root": { + "type": "boolean" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "dynamic": "false", + "properties": { + "changes": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "blocks": { + "read_only_allow_delete": "false" + }, + "codec": "best_compression", + "lifecycle": { + "name": "apm-rollover-30-days", + "rollover_alias": "apm-7.14.0-metric" + }, + "mapping": { + "total_fields": { + "limit": "2000" + } + }, + "max_docvalue_fields_search": "200", + "number_of_replicas": "1", + "number_of_shards": "1", + "priority": "100", + "refresh_interval": "5s" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + "apm-7.14.0-span": { + "is_write_index": true + } + }, + "index": "apm-7.14.0-span-000001", + "mappings": { + "_meta": { + "beat": "apm", + "version": "7.14.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "kubernetes.selectors.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.selectors.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "histogram": { + "mapping": { + "type": "histogram" + } + } + }, + { + "transaction.marks": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "transaction.marks.*" + } + }, + { + "transaction.marks.*.*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "transaction.marks.*.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "child": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "dynamic": "false", + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "container": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "grouping_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "experimental": { + "dynamic": "true", + "type": "object" + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "dynamic": "false", + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "cpu": { + "properties": { + "usage": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "disk": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network": { + "properties": { + "egress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "ingress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "dynamic": "false", + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "finished": { + "type": "boolean" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "dynamic": "false", + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "path": "container.image.name", + "type": "alias" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "ip": { + "type": "ip" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selectors": { + "properties": { + "*": { + "type": "object" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "dynamic": "true", + "properties": { + "events_encoded": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "events_failed": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "events_original": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "events_published": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "foo": { + "type": "keyword" + }, + "productId": { + "type": "keyword" + } + } + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "metricset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "period": { + "meta": { + "unit": "ms" + }, + "type": "long" + } + } + }, + "network": { + "dynamic": "false", + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "carrier": { + "properties": { + "icc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mcc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mnc": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "connection_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "inner": { + "properties": { + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "observer": { + "dynamic": "false", + "properties": { + "egress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "listening": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_major": { + "type": "byte" + } + } + }, + "orchestrator": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "dynamic": "false", + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "profile": { + "dynamic": "false", + "properties": { + "alloc_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "alloc_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cpu": { + "properties": { + "ns": { + "meta": { + "unit": "nanos" + }, + "type": "long" + } + } + }, + "duration": { + "meta": { + "unit": "nanos" + }, + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "inuse_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "inuse_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "samples": { + "properties": { + "count": { + "type": "long" + } + } + }, + "stack": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "top": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "wall": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "author": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "session": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + } + } + }, + "source": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "sourcemap": { + "dynamic": "false", + "properties": { + "bundle_filepath": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "db": { + "dynamic": "false", + "properties": { + "link": { + "ignore_above": 1024, + "type": "keyword" + }, + "rows_affected": { + "type": "long" + } + } + }, + "destination": { + "dynamic": "false", + "properties": { + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "response_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "total": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "process": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs": { + "properties": { + "period": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + }, + "quota": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + }, + "throttled": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + }, + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "mem": { + "properties": { + "limit": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "size": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "subtechnique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "histogram": { + "type": "histogram" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "experience": { + "properties": { + "cls": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fid": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "longtask": { + "properties": { + "count": { + "type": "long" + }, + "max": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "sum": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "tbt": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + } + } + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "root": { + "type": "boolean" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "dynamic": "false", + "properties": { + "changes": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "blocks": { + "read_only_allow_delete": "false" + }, + "codec": "best_compression", + "lifecycle": { + "name": "apm-rollover-30-days", + "rollover_alias": "apm-7.14.0-span" + }, + "mapping": { + "total_fields": { + "limit": "2000" + } + }, + "max_docvalue_fields_search": "200", + "number_of_replicas": "1", + "number_of_shards": "1", + "priority": "100", + "refresh_interval": "5s" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + "apm-7.14.0-transaction": { + "is_write_index": true + } + }, + "index": "apm-7.14.0-transaction-000001", + "mappings": { + "_meta": { + "beat": "apm", + "version": "7.14.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "kubernetes.selectors.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.selectors.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "histogram": { + "mapping": { + "type": "histogram" + } + } + }, + { + "transaction.marks": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "transaction.marks.*" + } + }, + { + "transaction.marks.*.*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "transaction.marks.*.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "child": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "dynamic": "false", + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "container": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "grouping_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "experimental": { + "dynamic": "true", + "type": "object" + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "dynamic": "false", + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "cpu": { + "properties": { + "usage": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "disk": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network": { + "properties": { + "egress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "ingress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "dynamic": "false", + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "finished": { + "type": "boolean" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "dynamic": "false", + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "path": "container.image.name", + "type": "alias" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "ip": { + "type": "ip" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selectors": { + "properties": { + "*": { + "type": "object" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "dynamic": "true", + "properties": { + "company": { + "type": "keyword" + }, + "customer_email": { + "type": "keyword" + }, + "customer_name": { + "type": "keyword" + }, + "customer_tier": { + "type": "keyword" + }, + "foo": { + "type": "keyword" + }, + "lorem": { + "type": "keyword" + }, + "multi-line": { + "type": "keyword" + }, + "request_id": { + "type": "keyword" + }, + "served_from_cache": { + "type": "keyword" + }, + "this-is-a-very-long-tag-name-without-any-spaces": { + "type": "keyword" + }, + "worker": { + "type": "keyword" + } + } + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "metricset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "period": { + "meta": { + "unit": "ms" + }, + "type": "long" + } + } + }, + "network": { + "dynamic": "false", + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "carrier": { + "properties": { + "icc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mcc": { + "ignore_above": 1024, + "type": "keyword" + }, + "mnc": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "connection_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "inner": { + "properties": { + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "observer": { + "dynamic": "false", + "properties": { + "egress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "listening": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_major": { + "type": "byte" + } + } + }, + "orchestrator": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "dynamic": "false", + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "profile": { + "dynamic": "false", + "properties": { + "alloc_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "alloc_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cpu": { + "properties": { + "ns": { + "meta": { + "unit": "nanos" + }, + "type": "long" + } + } + }, + "duration": { + "meta": { + "unit": "nanos" + }, + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "inuse_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "inuse_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "samples": { + "properties": { + "count": { + "type": "long" + } + } + }, + "stack": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "top": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "wall": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "author": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "session": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + } + } + }, + "source": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "sourcemap": { + "dynamic": "false", + "properties": { + "bundle_filepath": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "db": { + "dynamic": "false", + "properties": { + "link": { + "ignore_above": 1024, + "type": "keyword" + }, + "rows_affected": { + "type": "long" + } + } + }, + "destination": { + "dynamic": "false", + "properties": { + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "response_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "total": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "process": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs": { + "properties": { + "period": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + }, + "quota": { + "properties": { + "us": { + "meta": { + "metric_type": "gauge", + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + }, + "throttled": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + }, + "periods": { + "meta": { + "metric_type": "counter" + }, + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "ns": { + "meta": { + "metric_type": "counter", + "unit": "nanos" + }, + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "mem": { + "properties": { + "limit": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "meta": { + "metric_type": "gauge", + "unit": "percent" + }, + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + }, + "size": { + "meta": { + "metric_type": "gauge", + "unit": "byte" + }, + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "subtechnique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "histogram": { + "type": "histogram" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "experience": { + "properties": { + "cls": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fid": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "longtask": { + "properties": { + "count": { + "type": "long" + }, + "max": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "sum": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "tbt": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + }, + "agent": { + "properties": { + "domComplete": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domInteractive": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "timeToFirstByte": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "navigationTiming": { + "properties": { + "connectEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "connectStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domComplete": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domContentLoadedEventEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domContentLoadedEventStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domInteractive": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domLoading": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domainLookupEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domainLookupStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fetchStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "loadEventEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "loadEventStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "requestStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "responseEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "responseStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + } + } + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "root": { + "type": "boolean" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "dynamic": "false", + "properties": { + "changes": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "blocks": { + "read_only_allow_delete": "false" + }, + "codec": "best_compression", + "lifecycle": { + "name": "apm-rollover-30-days", + "rollover_alias": "apm-7.14.0-transaction" + }, + "mapping": { + "total_fields": { + "limit": "2000" + } + }, + "max_docvalue_fields_search": "200", + "number_of_replicas": "1", + "number_of_shards": "1", + "priority": "100", + "refresh_interval": "5s" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/apm_api_integration/common/ftr_provider_context.ts b/x-pack/test/apm_api_integration/common/ftr_provider_context.ts index cac43046964317..b5f2a4a42d91ae 100644 --- a/x-pack/test/apm_api_integration/common/ftr_provider_context.ts +++ b/x-pack/test/apm_api_integration/common/ftr_provider_context.ts @@ -16,5 +16,5 @@ export type InheritedServices = InheritedFtrProviderContext extends GenericFtrPr ? TServices : {}; -export { InheritedFtrProviderContext }; +export type { InheritedFtrProviderContext }; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts index 78c5bcb383c936..55b5863e6d4441 100644 --- a/x-pack/test/apm_api_integration/common/registry.ts +++ b/x-pack/test/apm_api_integration/common/registry.ts @@ -15,7 +15,7 @@ import { FtrProviderContext } from './ftr_provider_context'; type ArchiveName = | 'apm_8.0.0' - | 'apm_8.0.0_empty' + | 'apm_mappings_only_8.0.0' | '8.0.0' | 'metrics_8.0.0' | 'ml_8.0.0' diff --git a/x-pack/test/apm_api_integration/common/synthtrace_es_client.ts b/x-pack/test/apm_api_integration/common/synthtrace_es_client.ts deleted file mode 100644 index 6a42ae16f0b26c..00000000000000 --- a/x-pack/test/apm_api_integration/common/synthtrace_es_client.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - getBreakdownMetrics, - getSpanDestinationMetrics, - getTransactionMetrics, - toElasticsearchOutput, -} from '@elastic/apm-synthtrace'; -import { chunk } from 'lodash'; -import pLimit from 'p-limit'; -import { inspect } from 'util'; -import { InheritedFtrProviderContext } from './ftr_provider_context'; - -export async function synthtraceEsClient(context: InheritedFtrProviderContext) { - const es = context.getService('es'); - return { - index: (events: any[]) => { - const esEvents = toElasticsearchOutput({ - events: [ - ...events, - ...getTransactionMetrics(events), - ...getSpanDestinationMetrics(events), - ...getBreakdownMetrics(events), - ], - writeTargets: { - transaction: 'apm-7.14.0-transaction', - span: 'apm-7.14.0-span', - error: 'apm-7.14.0-error', - metric: 'apm-7.14.0-metric', - }, - }); - - const batches = chunk(esEvents, 1000); - const limiter = pLimit(1); - - return Promise.all( - batches.map((batch) => - limiter(() => { - return es.bulk({ - body: batch.flatMap(({ _index, _source }) => [{ index: { _index } }, _source]), - require_alias: true, - refresh: true, - }); - }) - ) - ).then((results) => { - const errors = results - .flatMap((result) => result.items) - .filter((item) => !!item.index?.error) - .map((item) => item.index?.error); - - if (errors.length) { - // eslint-disable-next-line no-console - console.log(inspect(errors.slice(0, 10), { depth: null })); - throw new Error('Failed to upload some events'); - } - return results; - }); - }, - clean: () => { - return es.deleteByQuery({ - index: 'apm-*', - body: { - query: { - match_all: {}, - }, - }, - }); - }, - }; -} diff --git a/x-pack/test/apm_api_integration/common/synthtrace_es_client_service.ts b/x-pack/test/apm_api_integration/common/synthtrace_es_client_service.ts new file mode 100644 index 00000000000000..14e746a55a3d11 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/synthtrace_es_client_service.ts @@ -0,0 +1,15 @@ +/* + * 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 { SynthtraceEsClient, createLogger, LogLevel } from '@elastic/apm-synthtrace'; +import { InheritedFtrProviderContext } from './ftr_provider_context'; + +export async function synthtraceEsClientService(context: InheritedFtrProviderContext) { + const es = context.getService('es'); + + return new SynthtraceEsClient(es, createLogger(LogLevel.info)); +} diff --git a/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts new file mode 100644 index 00000000000000..e36e99b447aa33 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts @@ -0,0 +1,57 @@ +/* + * 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 { service, timerange } from '@elastic/apm-synthtrace'; +import type { SynthtraceEsClient } from '@elastic/apm-synthtrace'; + +export const dataConfig = { + rate: 20, + transaction: { + name: 'GET /api/product/list', + duration: 1000, + }, + span: { + name: 'GET apm-*/_search', + type: 'db', + subType: 'elasticsearch', + destination: 'elasticsearch', + }, +}; + +export async function generateData({ + synthtraceEsClient, + start, + end, +}: { + synthtraceEsClient: SynthtraceEsClient; + start: number; + end: number; +}) { + const instance = service('synth-go', 'production', 'go').instance('instance-a'); + const { rate, transaction, span } = dataConfig; + + await synthtraceEsClient.index( + timerange(start, end) + .interval('1m') + .rate(rate) + .flatMap((timestamp) => + instance + .transaction(transaction.name) + .timestamp(timestamp) + .duration(transaction.duration) + .success() + .children( + instance + .span(span.name, span.type, span.subType) + .duration(transaction.duration) + .success() + .destination(span.destination) + .timestamp(timestamp) + ) + .serialize() + ) + ); +} diff --git a/x-pack/test/apm_api_integration/tests/dependencies/metadata.ts b/x-pack/test/apm_api_integration/tests/dependencies/metadata.ts new file mode 100644 index 00000000000000..071370a8e6c736 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/dependencies/metadata.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { dataConfig, generateData } from './generate_data'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi() { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/backends/metadata`, + params: { + query: { + backendName: dataConfig.span.destination, + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }, + }, + }); + } + + registry.when( + 'Dependency metadata when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const { status, body } = await callApi(); + + expect(status).to.be(200); + expect(body.metadata).to.empty(); + }); + } + ); + + registry.when( + 'Dependency metadata when data is generated', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + it('returns correct metadata for the dependency', async () => { + await generateData({ synthtraceEsClient, start, end }); + const { status, body } = await callApi(); + const { span } = dataConfig; + + expect(status).to.be(200); + expect(body.metadata.spanType).to.equal(span.type); + expect(body.metadata.spanSubtype).to.equal(span.subType); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.ts new file mode 100644 index 00000000000000..6d32defed460df --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.ts @@ -0,0 +1,142 @@ +/* + * 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 { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { dataConfig, generateData } from './generate_data'; +import { NodeType, BackendNode } from '../../../../plugins/apm/common/connections'; +import { roundNumber } from '../../utils'; + +type TopDependencies = APIReturnType<'GET /internal/apm/backends/top_backends'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi() { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/backends/top_backends', + params: { + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + numBuckets: 20, + offset: '', + }, + }, + }); + } + + registry.when( + 'Top dependencies when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const { status, body } = await callApi(); + expect(status).to.be(200); + expect(body.backends).to.empty(); + }); + } + ); + + registry.when( + 'Top dependencies', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe.skip('when data is generated', () => { + let topDependencies: TopDependencies; + + before(async () => { + await generateData({ synthtraceEsClient, start, end }); + const response = await callApi(); + topDependencies = response.body; + }); + + after(() => synthtraceEsClient.clean()); + + it('returns an array of dependencies', () => { + expect(topDependencies).to.have.property('backends'); + expect(topDependencies.backends).to.have.length(1); + }); + + it('returns correct dependency information', () => { + const location = topDependencies.backends[0].location as BackendNode; + const { span } = dataConfig; + + expect(location.type).to.be(NodeType.backend); + expect(location.backendName).to.be(span.destination); + expect(location.spanType).to.be(span.type); + expect(location.spanSubtype).to.be(span.subType); + expect(location).to.have.property('id'); + }); + + describe('returns the correct stats', () => { + let backends: TopDependencies['backends'][number]; + + before(() => { + backends = topDependencies.backends[0]; + }); + + it("doesn't have previous stats", () => { + expect(backends.previousStats).to.be(null); + }); + + it('has an "impact" property', () => { + expect(backends.currentStats).to.have.property('impact'); + }); + + it('returns the correct latency', () => { + const { + currentStats: { latency }, + } = backends; + + const { transaction } = dataConfig; + + expect(latency.value).to.be(transaction.duration * 1000); + expect(latency.timeseries.every(({ y }) => y === transaction.duration * 1000)).to.be( + true + ); + }); + + it('returns the correct throughput', () => { + const { + currentStats: { throughput }, + } = backends; + const { rate } = dataConfig; + + expect(roundNumber(throughput.value)).to.be(roundNumber(rate)); + }); + + it('returns the correct total time', () => { + const { + currentStats: { totalTime }, + } = backends; + const { rate, transaction } = dataConfig; + + expect( + totalTime.timeseries.every(({ y }) => y === rate * transaction.duration * 1000) + ).to.be(true); + }); + + it('returns the correct error rate', () => { + const { + currentStats: { errorRate }, + } = backends; + expect(errorRate.value).to.be(0); + expect(errorRate.timeseries.every(({ y }) => y === 0)).to.be(true); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts index 76f8d20c8ada80..d5bf0bd21c7e85 100644 --- a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts +++ b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts @@ -87,7 +87,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { serviceInventoryAPIResponse.body.items[0].transactionErrorRate; const errorRateChartApiMean = meanBy( - transactionsErrorRateChartAPIResponse.body.currentPeriod.transactionErrorRate.filter( + transactionsErrorRateChartAPIResponse.body.currentPeriod.timeseries.filter( (item) => isFiniteNumber(item.y) && item.y > 0 ), 'y' @@ -114,7 +114,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let errorRateMetricValues: PromiseReturnType; let errorTransactionValues: PromiseReturnType; - registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { + registry.when('Services APIs', { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded ', () => { const GO_PROD_LIST_RATE = 75; const GO_PROD_LIST_ERROR_RATE = 25; diff --git a/x-pack/test/apm_api_integration/tests/errors/distribution.ts b/x-pack/test/apm_api_integration/tests/errors/distribution.ts new file mode 100644 index 00000000000000..487b5ff8a12c93 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/errors/distribution.ts @@ -0,0 +1,189 @@ +/* + * 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 { first, last, sumBy } from 'lodash'; +import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { RecursivePartial } from '../../../../plugins/apm/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { config, generateData } from './generate_data'; + +type ErrorsDistribution = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/distribution'>['params'] + > + ) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/distribution', + params: { + path: { + serviceName, + ...overrides?.path, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + return response; + } + + registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.currentPeriod.length).to.be(0); + expect(response.body.previousPeriod.length).to.be(0); + }); + }); + + registry.when( + 'when data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('errors distribution', () => { + const { appleTransaction, bananaTransaction } = config; + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('without comparison', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi(); + errorsDistribution = response.body; + }); + + it('displays combined number of occurrences', () => { + const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); + const numberOfBuckets = 15; + expect(countSum).to.equal( + (appleTransaction.failureRate + bananaTransaction.failureRate) * numberOfBuckets + ); + }); + }); + + describe('displays occurrences for type "apple transaction" only', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi({ + query: { kuery: `error.exception.type:"${appleTransaction.name}"` }, + }); + errorsDistribution = response.body; + }); + it('displays combined number of occurrences', () => { + const countSum = sumBy(errorsDistribution.currentPeriod, 'y'); + const numberOfBuckets = 15; + expect(countSum).to.equal(appleTransaction.failureRate * numberOfBuckets); + }); + }); + + describe('with comparison', () => { + describe('when data is returned', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const fiveMinutes = 5 * 60 * 1000; + const response = await callApi({ + query: { + start: new Date(end - fiveMinutes).toISOString(), + end: new Date(end).toISOString(), + comparisonStart: new Date(start).toISOString(), + comparisonEnd: new Date(start + fiveMinutes).toISOString(), + }, + }); + errorsDistribution = response.body; + }); + it('returns some data', () => { + const hasCurrentPeriodData = errorsDistribution.currentPeriod.some(({ y }) => + isFiniteNumber(y) + ); + + const hasPreviousPeriodData = errorsDistribution.previousPeriod.some(({ y }) => + isFiniteNumber(y) + ); + + expect(hasCurrentPeriodData).to.equal(true); + expect(hasPreviousPeriodData).to.equal(true); + }); + + it('has same start time for both periods', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistribution.previousPeriod)?.x + ); + }); + + it('has same end time for both periods', () => { + expect(last(errorsDistribution.currentPeriod)?.x).to.equal( + last(errorsDistribution.previousPeriod)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect(errorsDistribution.currentPeriod.length).to.equal( + errorsDistribution.previousPeriod.length + ); + }); + }); + + describe('when no data is returned', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi({ + query: { + start: '2021-01-03T00:00:00.000Z', + end: '2021-01-03T00:15:00.000Z', + comparisonStart: '2021-01-02T00:00:00.000Z', + comparisonEnd: '2021-01-02T00:15:00.000Z', + }, + }); + errorsDistribution = response.body; + }); + + it('has same start time for both periods', () => { + expect(first(errorsDistribution.currentPeriod)?.x).to.equal( + first(errorsDistribution.previousPeriod)?.x + ); + }); + + it('has same end time for both periods', () => { + expect(last(errorsDistribution.currentPeriod)?.x).to.equal( + last(errorsDistribution.previousPeriod)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect(errorsDistribution.currentPeriod.length).to.equal( + errorsDistribution.previousPeriod.length + ); + }); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/errors/error_group_list.ts b/x-pack/test/apm_api_integration/tests/errors/error_group_list.ts new file mode 100644 index 00000000000000..4b5cbf4a2662ae --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/errors/error_group_list.ts @@ -0,0 +1,150 @@ +/* + * 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 { service, timerange } from '@elastic/apm-synthtrace'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { RecursivePartial } from '../../../../plugins/apm/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +type ErrorGroups = APIReturnType<'GET /internal/apm/services/{serviceName}/errors'>['errorGroups']; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/errors`, + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + } + + registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.errorGroups).to.empty(); + }); + }); + + registry.when( + 'when data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('errors group', () => { + const appleTransaction = { + name: 'GET /apple 🍎 ', + successRate: 75, + failureRate: 25, + }; + + const bananaTransaction = { + name: 'GET /banana 🍌', + successRate: 50, + failureRate: 50, + }; + + before(async () => { + const serviceInstance = service(serviceName, 'production', 'go').instance('instance-a'); + + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(appleTransaction.successRate) + .flatMap((timestamp) => + serviceInstance + .transaction(appleTransaction.name) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(appleTransaction.failureRate) + .flatMap((timestamp) => + serviceInstance + .transaction(appleTransaction.name) + .errors(serviceInstance.error('error 1', 'foo').timestamp(timestamp)) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(bananaTransaction.successRate) + .flatMap((timestamp) => + serviceInstance + .transaction(bananaTransaction.name) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(bananaTransaction.failureRate) + .flatMap((timestamp) => + serviceInstance + .transaction(bananaTransaction.name) + .errors(serviceInstance.error('error 2', 'bar').timestamp(timestamp)) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), + ]); + }); + + after(() => synthtraceEsClient.clean()); + + describe('returns the correct data', () => { + let errorGroups: ErrorGroups; + before(async () => { + const response = await callApi(); + errorGroups = response.body.errorGroups; + }); + + it('returns correct number of errors', () => { + expect(errorGroups.length).to.equal(2); + expect(errorGroups.map((error) => error.message).sort()).to.eql(['error 1', 'error 2']); + }); + + it('returns correct occurences', () => { + const numberOfBuckets = 15; + expect(errorGroups.map((error) => error.occurrenceCount).sort()).to.eql([ + appleTransaction.failureRate * numberOfBuckets, + bananaTransaction.failureRate * numberOfBuckets, + ]); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/errors/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts new file mode 100644 index 00000000000000..f7874b1c614954 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts @@ -0,0 +1,72 @@ +/* + * 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 { service, SynthtraceEsClient, timerange } from '@elastic/apm-synthtrace'; + +export const config = { + appleTransaction: { + name: 'GET /apple 🍎 ', + successRate: 75, + failureRate: 25, + }, + bananaTransaction: { + name: 'GET /banana 🍌', + successRate: 50, + failureRate: 50, + }, +}; + +export async function generateData({ + synthtraceEsClient, + serviceName, + start, + end, +}: { + synthtraceEsClient: SynthtraceEsClient; + serviceName: string; + start: number; + end: number; +}) { + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance('instance-a'); + + const interval = '1m'; + + const { bananaTransaction, appleTransaction } = config; + + const documents = [appleTransaction, bananaTransaction] + .map((transaction, index) => { + return [ + ...timerange(start, end) + .interval(interval) + .rate(transaction.successRate) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transaction.name) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval(interval) + .rate(transaction.failureRate) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transaction.name) + .errors( + serviceGoProdInstance.error(`Error ${index}`, transaction.name).timestamp(timestamp) + ) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), + ]; + }) + .flatMap((_) => _); + + await synthtraceEsClient.index(documents); +} diff --git a/x-pack/test/apm_api_integration/tests/errors/group_id.ts b/x-pack/test/apm_api_integration/tests/errors/group_id.ts new file mode 100644 index 00000000000000..ef9e293355a7fc --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/errors/group_id.ts @@ -0,0 +1,92 @@ +/* + * 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 { + APIClientRequestParamsOf, + APIReturnType, +} from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { RecursivePartial } from '../../../../plugins/apm/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { config, generateData } from './generate_data'; + +type ErrorsDistribution = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/{groupId}'>['params'] + > + ) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}', + params: { + path: { + serviceName, + groupId: 'foo', + ...overrides?.path, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + return response; + } + + registry.when('when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.occurrencesCount).to.be(0); + }); + }); + + registry.when( + 'when data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + const { bananaTransaction } = config; + describe('error group id', () => { + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('return correct data', () => { + let errorsDistribution: ErrorsDistribution; + before(async () => { + const response = await callApi({ + path: { groupId: '0000000000000000000000000Error 1' }, + }); + errorsDistribution = response.body; + }); + + it('displays correct number of occurrences', () => { + const numberOfBuckets = 15; + expect(errorsDistribution.occurrencesCount).to.equal( + bananaTransaction.failureRate * numberOfBuckets + ); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/metadata/event_metadata.ts b/x-pack/test/apm_api_integration/tests/event_metadata/event_metadata.ts similarity index 95% rename from x-pack/test/apm_api_integration/tests/metadata/event_metadata.ts rename to x-pack/test/apm_api_integration/tests/event_metadata/event_metadata.ts index 40af7b132eb8fe..fe461a94783a58 100644 --- a/x-pack/test/apm_api_integration/tests/metadata/event_metadata.ts +++ b/x-pack/test/apm_api_integration/tests/event_metadata/event_metadata.ts @@ -36,7 +36,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { } registry.when('Event metadata', { config: 'basic', archives: ['apm_8.0.0'] }, () => { - it('fetches transaction metadata', async () => { + it('fetches transaction event metadata', async () => { const id = await getLastDocId(ProcessorEvent.transaction); const { body } = await apmApiClient.readUser({ @@ -66,7 +66,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); }); - it('fetches error metadata', async () => { + it('fetches error event metadata', async () => { const id = await getLastDocId(ProcessorEvent.error); const { body } = await apmApiClient.readUser({ @@ -96,7 +96,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); }); - it('fetches span metadata', async () => { + it('fetches span event metadata', async () => { const id = await getLastDocId(ProcessorEvent.span); const { body } = await apmApiClient.readUser({ diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index f68a49658f2ee4..46966834a176e1 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -37,8 +37,8 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./correlations/latency')); }); - describe('metadata/event_metadata', function () { - loadTestFile(require.resolve('./metadata/event_metadata')); + describe('event_metadata/event_metadata', function () { + loadTestFile(require.resolve('./event_metadata/event_metadata')); }); describe('metrics_charts/metrics_charts', function () { @@ -113,11 +113,11 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte }); describe('services/error_groups_main_statistics', function () { - loadTestFile(require.resolve('./services/error_groups_main_statistics')); + loadTestFile(require.resolve('./services/error_groups/error_groups_main_statistics')); }); describe('services/error_groups_detailed_statistics', function () { - loadTestFile(require.resolve('./services/error_groups_detailed_statistics')); + loadTestFile(require.resolve('./services/error_groups/error_groups_detailed_statistics')); }); describe('services/detailed_statistics', function () { @@ -241,6 +241,28 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./latency/service_apis')); }); + // Errors + describe('errors/group_id', function () { + loadTestFile(require.resolve('./errors/group_id')); + }); + + describe('errors/distribution', function () { + loadTestFile(require.resolve('./errors/distribution')); + }); + + describe('errors/error_group_list', function () { + loadTestFile(require.resolve('./errors/error_group_list')); + }); + + // Dependencies + describe('dependencies/metadata', function () { + loadTestFile(require.resolve('./dependencies/metadata')); + }); + + describe('dependencies/top_dependencies', function () { + loadTestFile(require.resolve('./dependencies/top_dependencies')); + }); + registry.run(providerContext); }); } diff --git a/x-pack/test/apm_api_integration/tests/latency/service_apis.ts b/x-pack/test/apm_api_integration/tests/latency/service_apis.ts index d3ec68c51782d2..aa8282ccb0cc59 100644 --- a/x-pack/test/apm_api_integration/tests/latency/service_apis.ts +++ b/x-pack/test/apm_api_integration/tests/latency/service_apis.ts @@ -116,7 +116,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let latencyMetricValues: PromiseReturnType; let latencyTransactionValues: PromiseReturnType; - registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { + registry.when('Services APIs', { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded ', () => { const GO_PROD_RATE = 80; const GO_DEV_RATE = 20; diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts index e4aad4f3f69755..9082c5dec3b798 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts @@ -83,89 +83,95 @@ export default function ApiTest({ getService }: FtrProviderContext) { } ); - registry.when('data is loaded', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { - describe('Observability overview api ', () => { - const GO_PROD_RATE = 50; - const GO_DEV_RATE = 5; - const JAVA_PROD_RATE = 45; - before(async () => { - const serviceGoProdInstance = service('synth-go', 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service('synth-go', 'development', 'go').instance( - 'instance-b' - ); - const serviceJavaInstance = service('synth-java', 'production', 'java').instance( - 'instance-c' - ); + registry.when( + 'data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('Observability overview api ', () => { + const GO_PROD_RATE = 50; + const GO_DEV_RATE = 5; + const JAVA_PROD_RATE = 45; + before(async () => { + const serviceGoProdInstance = service('synth-go', 'production', 'go').instance( + 'instance-a' + ); + const serviceGoDevInstance = service('synth-go', 'development', 'go').instance( + 'instance-b' + ); + const serviceJavaInstance = service('synth-java', 'production', 'java').instance( + 'instance-c' + ); - await synthtraceEsClient.index([ - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction('GET /api/product/list') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(GO_DEV_RATE) - .flatMap((timestamp) => - serviceGoDevInstance - .transaction('GET /api/product/:id') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(JAVA_PROD_RATE) - .flatMap((timestamp) => - serviceJavaInstance - .transaction('POST /api/product/buy') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ]); - }); + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction('GET /api/product/list') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(GO_DEV_RATE) + .flatMap((timestamp) => + serviceGoDevInstance + .transaction('GET /api/product/:id') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(JAVA_PROD_RATE) + .flatMap((timestamp) => + serviceJavaInstance + .transaction('POST /api/product/buy') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ]); + }); - after(() => synthtraceEsClient.clean()); + after(() => synthtraceEsClient.clean()); - describe('compare throughput values', () => { - let throughputValues: PromiseReturnType; - before(async () => { - throughputValues = await getThroughputValues(); - }); + describe('compare throughput values', () => { + let throughputValues: PromiseReturnType; + before(async () => { + throughputValues = await getThroughputValues(); + }); - it('returns same number of service as shown on service inventory API', () => { - const { serviceInventoryCount, observabilityOverview } = throughputValues; - [serviceInventoryCount, observabilityOverview.serviceCount].forEach((value) => - expect(value).to.be.equal(2) - ); - }); + it('returns same number of service as shown on service inventory API', () => { + const { serviceInventoryCount, observabilityOverview } = throughputValues; + [serviceInventoryCount, observabilityOverview.serviceCount].forEach((value) => + expect(value).to.be.equal(2) + ); + }); - it('returns same throughput value on service inventory and obs throughput count', () => { - const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; - const obsThroughputCount = roundNumber(observabilityOverview.transactionPerMinute.value); - [serviceInventoryThroughputSum, obsThroughputCount].forEach((value) => - expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) - ); - }); + it('returns same throughput value on service inventory and obs throughput count', () => { + const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; + const obsThroughputCount = roundNumber( + observabilityOverview.transactionPerMinute.value + ); + [serviceInventoryThroughputSum, obsThroughputCount].forEach((value) => + expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) + ); + }); - it('returns same throughput value on service inventory and obs mean throughput timeseries', () => { - const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; - const obsThroughputMean = roundNumber( - meanBy(observabilityOverview.transactionPerMinute.timeseries, 'y') - ); - [serviceInventoryThroughputSum, obsThroughputMean].forEach((value) => - expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) - ); + it('returns same throughput value on service inventory and obs mean throughput timeseries', () => { + const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; + const obsThroughputMean = roundNumber( + meanBy(observabilityOverview.transactionPerMinute.timeseries, 'y') + ); + [serviceInventoryThroughputSum, obsThroughputMean].forEach((value) => + expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) + ); + }); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts index 2da57c0a257793..d4733785c62b82 100644 --- a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts @@ -298,8 +298,8 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) "avgErrorRate": 0, "avgMemoryUsage": 0.202572668763642, "transactionStats": Object { - "avgRequestsPerMinute": 7.13333333333333, - "avgTransactionDuration": 53147.5747663551, + "avgRequestsPerMinute": 5.2, + "avgTransactionDuration": 53906.6603773585, }, } `); diff --git a/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instance_details.snap b/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instance_details.snap index 91b0b7f2da6e4c..8eb8ad2adb1649 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instance_details.snap +++ b/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instance_details.snap @@ -2,7 +2,7 @@ exports[`APM API tests basic apm_8.0.0 Instance details when data is loaded fetch instance details return the correct data 1`] = ` Object { - "@timestamp": "2021-08-03T06:50:20.205Z", + "@timestamp": "2021-08-03T06:57:50.204Z", "agent": Object { "ephemeral_id": "2745d454-f57f-4473-a09b-fe6bef295860", "name": "java", diff --git a/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instances_detailed_statistics.snap b/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instances_detailed_statistics.snap index 971c14262f0b0b..b8a57f26d85bc4 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instances_detailed_statistics.snap +++ b/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instances_detailed_statistics.snap @@ -73,11 +73,11 @@ Object { "errorRate": Array [ Object { "x": 1627974300000, - "y": null, + "y": 0, }, Object { "x": 1627974360000, - "y": 0, + "y": null, }, Object { "x": 1627974420000, @@ -93,15 +93,15 @@ Object { }, Object { "x": 1627974600000, - "y": 0.5, + "y": 0.125, }, Object { "x": 1627974660000, - "y": 0.4, + "y": 0.6, }, Object { "x": 1627974720000, - "y": 0, + "y": 0.2, }, Object { "x": 1627974780000, @@ -109,11 +109,11 @@ Object { }, Object { "x": 1627974840000, - "y": 0.166666666666667, + "y": 0, }, Object { "x": 1627974900000, - "y": 0, + "y": 0.0666666666666667, }, Object { "x": 1627974960000, @@ -125,11 +125,11 @@ Object { }, Object { "x": 1627975080000, - "y": 0.142857142857143, + "y": 0, }, Object { "x": 1627975140000, - "y": 0.2, + "y": 0.181818181818182, }, Object { "x": 1627975200000, @@ -139,63 +139,63 @@ Object { "latency": Array [ Object { "x": 1627974300000, - "y": null, + "y": 34887.8888888889, }, Object { "x": 1627974360000, - "y": 5578, + "y": null, }, Object { "x": 1627974420000, - "y": 34851.1666666667, + "y": 4983, }, Object { "x": 1627974480000, - "y": 15896.4, + "y": 41285.4, }, Object { "x": 1627974540000, - "y": 15174.1666666667, + "y": 13820.3333333333, }, Object { "x": 1627974600000, - "y": 9185.16666666667, + "y": 13782, }, Object { "x": 1627974660000, - "y": 12363.2, + "y": 13392.6, }, Object { "x": 1627974720000, - "y": 6206.44444444444, + "y": 6991, }, Object { "x": 1627974780000, - "y": 6707, + "y": 6885.85714285714, }, Object { "x": 1627974840000, - "y": 12409.1666666667, + "y": 7935, }, Object { "x": 1627974900000, - "y": 9188.36363636364, + "y": 10828.3333333333, }, Object { "x": 1627974960000, - "y": 4279.6, + "y": 6079, }, Object { "x": 1627975020000, - "y": 6827.3, + "y": 5217, }, Object { "x": 1627975080000, - "y": 7445.78571428571, + "y": 8477.76923076923, }, Object { "x": 1627975140000, - "y": 563288.6, + "y": 5937.18181818182, }, Object { "x": 1627975200000, @@ -272,15 +272,15 @@ Object { "throughput": Array [ Object { "x": 1627974300000, - "y": 0, + "y": 9, }, Object { "x": 1627974360000, - "y": 2, + "y": 0, }, Object { "x": 1627974420000, - "y": 6, + "y": 4, }, Object { "x": 1627974480000, @@ -292,7 +292,7 @@ Object { }, Object { "x": 1627974600000, - "y": 6, + "y": 8, }, Object { "x": 1627974660000, @@ -300,35 +300,35 @@ Object { }, Object { "x": 1627974720000, - "y": 9, + "y": 5, }, Object { "x": 1627974780000, - "y": 3, + "y": 7, }, Object { "x": 1627974840000, - "y": 6, + "y": 2, }, Object { "x": 1627974900000, - "y": 11, + "y": 15, }, Object { "x": 1627974960000, - "y": 5, + "y": 3, }, Object { "x": 1627975020000, - "y": 10, + "y": 8, }, Object { "x": 1627975080000, - "y": 14, + "y": 13, }, Object { "x": 1627975140000, - "y": 10, + "y": 11, }, Object { "x": 1627975200000, @@ -412,15 +412,15 @@ Object { }, Object { "x": 1627974360000, - "y": 0.25, + "y": 0, }, Object { "x": 1627974420000, - "y": 0.111111111111111, + "y": 0.333333333333333, }, Object { "x": 1627974480000, - "y": 0.2, + "y": 0.181818181818182, }, Object { "x": 1627974540000, @@ -428,15 +428,15 @@ Object { }, Object { "x": 1627974600000, - "y": 0.142857142857143, + "y": 0, }, Object { "x": 1627974660000, - "y": 0.1, + "y": 0.166666666666667, }, Object { "x": 1627974720000, - "y": 0.125, + "y": 0.181818181818182, }, Object { "x": 1627974780000, @@ -444,15 +444,15 @@ Object { }, Object { "x": 1627974840000, - "y": 0.111111111111111, + "y": 0, }, Object { "x": 1627974900000, - "y": 0, + "y": 0.0833333333333333, }, Object { "x": 1627974960000, - "y": 0.333333333333333, + "y": 0.0769230769230769, }, Object { "x": 1627975020000, @@ -460,81 +460,81 @@ Object { }, Object { "x": 1627975080000, - "y": 0.0833333333333333, + "y": 0.1, }, Object { "x": 1627975140000, - "y": 0.1, + "y": 0.153846153846154, }, Object { "x": 1627975200000, - "y": 0, + "y": null, }, ], "latency": Array [ Object { "x": 1627974300000, - "y": 5372.5, + "y": 11839, }, Object { "x": 1627974360000, - "y": 1441598.25, + "y": 7407, }, Object { "x": 1627974420000, - "y": 9380.22222222222, + "y": 1925569.66666667, }, Object { "x": 1627974480000, - "y": 10949.4, + "y": 9017.18181818182, }, Object { "x": 1627974540000, - "y": 77148.6666666667, + "y": 63575, }, Object { "x": 1627974600000, - "y": 6461, + "y": 7577.66666666667, }, Object { "x": 1627974660000, - "y": 549308.4, + "y": 6844.33333333333, }, Object { "x": 1627974720000, - "y": 10797.75, + "y": 503471, }, Object { "x": 1627974780000, - "y": 9758.53846153846, + "y": 6247.8, }, Object { "x": 1627974840000, - "y": 1281052.66666667, + "y": 1137247, }, Object { "x": 1627974900000, - "y": 9511.0625, + "y": 27951.6666666667, }, Object { "x": 1627974960000, - "y": 11151203.3333333, + "y": 10248.8461538462, }, Object { "x": 1627975020000, - "y": 8647.2, + "y": 13529, }, Object { "x": 1627975080000, - "y": 9048.33333333333, + "y": 6691247.8, }, Object { "x": 1627975140000, - "y": 12671.6, + "y": 12098.6923076923, }, Object { "x": 1627975200000, - "y": 57275.4, + "y": null, }, ], "memoryUsage": Array [ @@ -607,67 +607,67 @@ Object { "throughput": Array [ Object { "x": 1627974300000, - "y": 2, + "y": 4, }, Object { "x": 1627974360000, - "y": 4, + "y": 2, }, Object { "x": 1627974420000, - "y": 9, + "y": 3, }, Object { "x": 1627974480000, - "y": 5, + "y": 11, }, Object { "x": 1627974540000, - "y": 3, + "y": 4, }, Object { "x": 1627974600000, - "y": 7, + "y": 6, }, Object { "x": 1627974660000, - "y": 10, + "y": 6, }, Object { "x": 1627974720000, - "y": 8, + "y": 11, }, Object { "x": 1627974780000, - "y": 13, + "y": 10, }, Object { "x": 1627974840000, - "y": 9, + "y": 10, }, Object { "x": 1627974900000, - "y": 16, + "y": 12, }, Object { "x": 1627974960000, - "y": 6, + "y": 13, }, Object { "x": 1627975020000, - "y": 10, + "y": 8, }, Object { "x": 1627975080000, - "y": 12, + "y": 10, }, Object { "x": 1627975140000, - "y": 10, + "y": 13, }, Object { "x": 1627975200000, - "y": 5, + "y": 0, }, ], }, @@ -812,15 +812,15 @@ Object { }, Object { "x": 1627973460000, - "y": 0.25, + "y": 0, }, Object { "x": 1627973520000, - "y": 0.111111111111111, + "y": 0.333333333333333, }, Object { "x": 1627973580000, - "y": 0.2, + "y": 0.181818181818182, }, Object { "x": 1627973640000, @@ -828,15 +828,15 @@ Object { }, Object { "x": 1627973700000, - "y": 0.142857142857143, + "y": 0, }, Object { "x": 1627973760000, - "y": 0.1, + "y": 0.166666666666667, }, Object { "x": 1627973820000, - "y": 0.125, + "y": 0.181818181818182, }, Object { "x": 1627973880000, @@ -844,15 +844,15 @@ Object { }, Object { "x": 1627973940000, - "y": 0.111111111111111, + "y": 0, }, Object { "x": 1627974000000, - "y": 0, + "y": 0.0833333333333333, }, Object { "x": 1627974060000, - "y": 0.333333333333333, + "y": 0.0769230769230769, }, Object { "x": 1627974120000, @@ -860,11 +860,11 @@ Object { }, Object { "x": 1627974180000, - "y": 0.0833333333333333, + "y": 0.1, }, Object { "x": 1627974240000, - "y": 0.1, + "y": 0.153846153846154, }, Object { "x": 1627974300000, @@ -872,7 +872,7 @@ Object { }, Object { "x": 1627974360000, - "y": 0, + "y": null, }, Object { "x": 1627974420000, @@ -888,15 +888,15 @@ Object { }, Object { "x": 1627974600000, - "y": 0.5, + "y": 0.125, }, Object { "x": 1627974660000, - "y": 0.4, + "y": 0.6, }, Object { "x": 1627974720000, - "y": 0, + "y": 0.2, }, Object { "x": 1627974780000, @@ -904,11 +904,11 @@ Object { }, Object { "x": 1627974840000, - "y": 0.166666666666667, + "y": 0, }, Object { "x": 1627974900000, - "y": 0, + "y": 0.0666666666666667, }, Object { "x": 1627974960000, @@ -920,11 +920,11 @@ Object { }, Object { "x": 1627975080000, - "y": 0.142857142857143, + "y": 0, }, Object { "x": 1627975140000, - "y": 0.2, + "y": 0.181818181818182, }, Object { "x": 1627975200000, @@ -934,123 +934,123 @@ Object { "latency": Array [ Object { "x": 1627973400000, - "y": 5372.5, + "y": 11839, }, Object { "x": 1627973460000, - "y": 1441598.25, + "y": 7407, }, Object { "x": 1627973520000, - "y": 9380.22222222222, + "y": 1925569.66666667, }, Object { "x": 1627973580000, - "y": 10949.4, + "y": 9017.18181818182, }, Object { "x": 1627973640000, - "y": 77148.6666666667, + "y": 63575, }, Object { "x": 1627973700000, - "y": 6461, + "y": 7577.66666666667, }, Object { "x": 1627973760000, - "y": 549308.4, + "y": 6844.33333333333, }, Object { "x": 1627973820000, - "y": 10797.75, + "y": 503471, }, Object { "x": 1627973880000, - "y": 9758.53846153846, + "y": 6247.8, }, Object { "x": 1627973940000, - "y": 1281052.66666667, + "y": 1137247, }, Object { "x": 1627974000000, - "y": 9511.0625, + "y": 27951.6666666667, }, Object { "x": 1627974060000, - "y": 11151203.3333333, + "y": 10248.8461538462, }, Object { "x": 1627974120000, - "y": 8647.2, + "y": 13529, }, Object { "x": 1627974180000, - "y": 9048.33333333333, + "y": 6691247.8, }, Object { "x": 1627974240000, - "y": 12671.6, + "y": 12098.6923076923, }, Object { "x": 1627974300000, - "y": 57275.4, + "y": 34887.8888888889, }, Object { "x": 1627974360000, - "y": 5578, + "y": null, }, Object { "x": 1627974420000, - "y": 34851.1666666667, + "y": 4983, }, Object { "x": 1627974480000, - "y": 15896.4, + "y": 41285.4, }, Object { "x": 1627974540000, - "y": 15174.1666666667, + "y": 13820.3333333333, }, Object { "x": 1627974600000, - "y": 9185.16666666667, + "y": 13782, }, Object { "x": 1627974660000, - "y": 12363.2, + "y": 13392.6, }, Object { "x": 1627974720000, - "y": 6206.44444444444, + "y": 6991, }, Object { "x": 1627974780000, - "y": 6707, + "y": 6885.85714285714, }, Object { "x": 1627974840000, - "y": 12409.1666666667, + "y": 7935, }, Object { "x": 1627974900000, - "y": 9188.36363636364, + "y": 10828.3333333333, }, Object { "x": 1627974960000, - "y": 4279.6, + "y": 6079, }, Object { "x": 1627975020000, - "y": 6827.3, + "y": 5217, }, Object { "x": 1627975080000, - "y": 7445.78571428571, + "y": 8477.76923076923, }, Object { "x": 1627975140000, - "y": 563288.6, + "y": 5937.18181818182, }, Object { "x": 1627975200000, @@ -1187,75 +1187,75 @@ Object { "throughput": Array [ Object { "x": 1627973400000, - "y": 2, + "y": 4, }, Object { "x": 1627973460000, - "y": 4, + "y": 2, }, Object { "x": 1627973520000, - "y": 9, + "y": 3, }, Object { "x": 1627973580000, - "y": 5, + "y": 11, }, Object { "x": 1627973640000, - "y": 3, + "y": 4, }, Object { "x": 1627973700000, - "y": 7, + "y": 6, }, Object { "x": 1627973760000, - "y": 10, + "y": 6, }, Object { "x": 1627973820000, - "y": 8, + "y": 11, }, Object { "x": 1627973880000, - "y": 13, + "y": 10, }, Object { "x": 1627973940000, - "y": 9, + "y": 10, }, Object { "x": 1627974000000, - "y": 16, + "y": 12, }, Object { "x": 1627974060000, - "y": 6, + "y": 13, }, Object { "x": 1627974120000, - "y": 10, + "y": 8, }, Object { "x": 1627974180000, - "y": 12, + "y": 10, }, Object { "x": 1627974240000, - "y": 10, + "y": 13, }, Object { "x": 1627974300000, - "y": 5, + "y": 9, }, Object { "x": 1627974360000, - "y": 2, + "y": 0, }, Object { "x": 1627974420000, - "y": 6, + "y": 4, }, Object { "x": 1627974480000, @@ -1267,7 +1267,7 @@ Object { }, Object { "x": 1627974600000, - "y": 6, + "y": 8, }, Object { "x": 1627974660000, @@ -1275,35 +1275,35 @@ Object { }, Object { "x": 1627974720000, - "y": 9, + "y": 5, }, Object { "x": 1627974780000, - "y": 3, + "y": 7, }, Object { "x": 1627974840000, - "y": 6, + "y": 2, }, Object { "x": 1627974900000, - "y": 11, + "y": 15, }, Object { "x": 1627974960000, - "y": 5, + "y": 3, }, Object { "x": 1627975020000, - "y": 10, + "y": 8, }, Object { "x": 1627975080000, - "y": 14, + "y": 13, }, Object { "x": 1627975140000, - "y": 10, + "y": 11, }, Object { "x": 1627975200000, diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts index 1e9051b64a90bf..909e58a5566a57 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts @@ -122,10 +122,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(values).toMatchInline(` Object { "cpuUsage": 0.002, - "errorRate": 0.092511013215859, - "latency": 430318.696035242, + "errorRate": 0.0848214285714286, + "latency": 411589.785714286, "memoryUsage": 0.786029688517253, - "throughput": 7.56666666666667, + "throughput": 7.46666666666667, } `); }); @@ -183,9 +183,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(values).toMatchInline(` Object { "cpuUsage": 0.001, - "errorRate": 0.00343642611683849, - "latency": 21520.4776632302, - "throughput": 9.7, + "errorRate": 0.00341296928327645, + "latency": 40989.5802047782, + "throughput": 9.76666666666667, } `); @@ -272,10 +272,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(values).toMatchInline(` Object { "cpuUsage": 0.00223333333333333, - "errorRate": 0.0852713178294574, - "latency": 706173.046511628, + "errorRate": 0.0894308943089431, + "latency": 739013.634146341, "memoryUsage": 0.783296203613281, - "throughput": 8.6, + "throughput": 8.2, } `); }); @@ -285,7 +285,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when( 'Service overview instances main statistics when data is generated', - { config: 'basic', archives: ['apm_8.0.0_empty'] }, + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('for two go instances and one java instance', () => { const GO_A_INSTANCE_RATE_SUCCESS = 10; diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.ts new file mode 100644 index 00000000000000..54bbd4eb0bf715 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.ts @@ -0,0 +1,199 @@ +/* + * 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 { first, last, sumBy } from 'lodash'; +import moment from 'moment'; +import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { RecursivePartial } from '../../../../../plugins/apm/typings/common'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { registry } from '../../../common/registry'; +import { config, generateData } from './generate_data'; +import { getErrorGroupIds } from './get_error_group_ids'; + +type ErrorGroupsDetailedStatistics = + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics`, + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + numBuckets: 20, + transactionType: 'request', + groupIds: JSON.stringify(['foo']), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + } + + registry.when( + 'Error groups detailed statistics when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); + }); + } + ); + + registry.when( + 'Error groups detailed statistics', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('when data is loaded', () => { + const { PROD_LIST_ERROR_RATE, PROD_ID_ERROR_RATE } = config; + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('without data comparison', () => { + let errorGroupsDetailedStatistics: ErrorGroupsDetailedStatistics; + let errorIds: string[] = []; + before(async () => { + errorIds = await getErrorGroupIds({ serviceName, start, end, apmApiClient }); + const response = await callApi({ + query: { + groupIds: JSON.stringify(errorIds), + }, + }); + errorGroupsDetailedStatistics = response.body; + }); + + it('return detailed statistics for all errors found', () => { + expect(Object.keys(errorGroupsDetailedStatistics.currentPeriod).sort()).to.eql( + errorIds + ); + }); + + it('returns correct number of occurrencies', () => { + const numberOfBuckets = 15; + const detailedStatisticsOccurrenciesSum = Object.values( + errorGroupsDetailedStatistics.currentPeriod + ) + .sort() + .map(({ timeseries }) => { + return sumBy(timeseries, 'y'); + }); + + expect(detailedStatisticsOccurrenciesSum).to.eql([ + PROD_ID_ERROR_RATE * numberOfBuckets, + PROD_LIST_ERROR_RATE * numberOfBuckets, + ]); + }); + }); + + describe('return empty state when invalid group id', () => { + let errorGroupsDetailedStatistics: ErrorGroupsDetailedStatistics; + before(async () => { + const response = await callApi({ + query: { + groupIds: JSON.stringify(['foo']), + }, + }); + errorGroupsDetailedStatistics = response.body; + }); + + it('returns empty state', () => { + expect(errorGroupsDetailedStatistics).to.be.eql({ + currentPeriod: {}, + previousPeriod: {}, + }); + }); + }); + + describe('with comparison', () => { + let errorGroupsDetailedStatistics: ErrorGroupsDetailedStatistics; + let errorIds: string[] = []; + before(async () => { + errorIds = await getErrorGroupIds({ serviceName, start, end, apmApiClient }); + const response = await callApi({ + query: { + groupIds: JSON.stringify(errorIds), + start: moment(end).subtract(7, 'minutes').toISOString(), + end: new Date(end).toISOString(), + comparisonStart: new Date(start).toISOString(), + comparisonEnd: moment(start).add(7, 'minutes').toISOString(), + }, + }); + errorGroupsDetailedStatistics = response.body; + }); + + it('returns some data', () => { + expect( + Object.keys(errorGroupsDetailedStatistics.currentPeriod).length + ).to.be.greaterThan(0); + expect( + Object.keys(errorGroupsDetailedStatistics.previousPeriod).length + ).to.be.greaterThan(0); + + const hasCurrentPeriodData = Object.values( + errorGroupsDetailedStatistics.currentPeriod + )[0].timeseries.some(({ y }) => isFiniteNumber(y)); + + const hasPreviousPeriodData = Object.values( + errorGroupsDetailedStatistics.previousPeriod + )[0].timeseries.some(({ y }) => isFiniteNumber(y)); + + expect(hasCurrentPeriodData).to.equal(true); + expect(hasPreviousPeriodData).to.equal(true); + }); + + it('has same start time for both periods', () => { + expect( + first(Object.values(errorGroupsDetailedStatistics.currentPeriod)[0].timeseries)?.x + ).to.equal( + first(Object.values(errorGroupsDetailedStatistics.previousPeriod)[0].timeseries)?.x + ); + }); + + it('has same end time for both periods', () => { + expect( + last(Object.values(errorGroupsDetailedStatistics.currentPeriod)[0].timeseries)?.x + ).to.equal( + last(Object.values(errorGroupsDetailedStatistics.previousPeriod)[0].timeseries)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect( + Object.values(errorGroupsDetailedStatistics.currentPeriod)[0].timeseries.length + ).to.equal( + Object.values(errorGroupsDetailedStatistics.previousPeriod)[0].timeseries.length + ); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.ts new file mode 100644 index 00000000000000..bc6bd023a0f5e2 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.ts @@ -0,0 +1,110 @@ +/* + * 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 moment from 'moment'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { RecursivePartial } from '../../../../../plugins/apm/typings/common'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { registry } from '../../../common/registry'; +import { generateData, config } from './generate_data'; + +type ErrorGroupsMainStatistics = + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/error_groups/main_statistics`, + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + transactionType: 'request', + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + } + + registry.when( + 'Error groups main statistics when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.error_groups).to.empty(); + expect(response.body.is_aggregation_accurate).to.eql(true); + }); + } + ); + + registry.when( + 'Error groups main statistics', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('when data is loaded', () => { + const { PROD_LIST_ERROR_RATE, PROD_ID_ERROR_RATE, ERROR_NAME_1, ERROR_NAME_2 } = config; + + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('returns the correct data', () => { + let errorGroupMainStatistics: ErrorGroupsMainStatistics; + before(async () => { + const response = await callApi(); + errorGroupMainStatistics = response.body; + }); + + it('returns correct number of occurrencies', () => { + expect(errorGroupMainStatistics.error_groups.length).to.equal(2); + expect(errorGroupMainStatistics.error_groups.map((error) => error.name).sort()).to.eql([ + ERROR_NAME_1, + ERROR_NAME_2, + ]); + }); + + it('returns correct occurences', () => { + const numberOfBuckets = 15; + expect( + errorGroupMainStatistics.error_groups.map((error) => error.occurrences).sort() + ).to.eql([ + PROD_LIST_ERROR_RATE * numberOfBuckets, + PROD_ID_ERROR_RATE * numberOfBuckets, + ]); + }); + + it('has same last seen value as end date', () => { + errorGroupMainStatistics.error_groups.map((error) => { + expect(error.lastSeen).to.equal(moment(end).startOf('minute').valueOf()); + }); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts new file mode 100644 index 00000000000000..f02f1e7493ff0a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts @@ -0,0 +1,92 @@ +/* + * 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 { service, timerange } from '@elastic/apm-synthtrace'; +import type { SynthtraceEsClient } from '@elastic/apm-synthtrace'; + +export const config = { + PROD_LIST_RATE: 75, + PROD_LIST_ERROR_RATE: 25, + PROD_ID_RATE: 50, + PROD_ID_ERROR_RATE: 50, + ERROR_NAME_1: 'Error test 1', + ERROR_NAME_2: 'Error test 2', +}; + +export async function generateData({ + synthtraceEsClient, + serviceName, + start, + end, +}: { + synthtraceEsClient: SynthtraceEsClient; + serviceName: string; + start: number; + end: number; +}) { + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance('instance-a'); + + const transactionNameProductList = 'GET /api/product/list'; + const transactionNameProductId = 'GET /api/product/:id'; + + const { + PROD_LIST_RATE, + PROD_LIST_ERROR_RATE, + PROD_ID_RATE, + PROD_ID_ERROR_RATE, + ERROR_NAME_1, + ERROR_NAME_2, + } = config; + + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(PROD_LIST_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductList) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(PROD_LIST_ERROR_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductList) + .errors(serviceGoProdInstance.error(ERROR_NAME_1, 'foo').timestamp(timestamp)) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(PROD_ID_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductId) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(PROD_ID_ERROR_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductId) + .errors(serviceGoProdInstance.error(ERROR_NAME_2, 'bar').timestamp(timestamp)) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), + ]); +} diff --git a/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts similarity index 66% rename from x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts rename to x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts index 9fa7232240db19..cfc0867fdcfb9f 100644 --- a/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts @@ -5,28 +5,29 @@ * 2.0. */ import { take } from 'lodash'; -import { ApmApiSupertest } from '../../common/apm_api_supertest'; +import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; +import { ApmServices } from '../../../common/config'; export async function getErrorGroupIds({ - apmApiSupertest, + apmApiClient, start, end, serviceName = 'opbeans-java', count = 5, }: { - apmApiSupertest: ApmApiSupertest; - start: string; - end: string; + apmApiClient: PromiseReturnType; + start: number; + end: number; serviceName?: string; count?: number; }) { - const { body } = await apmApiSupertest({ + const { body } = await apmApiClient.readUser({ endpoint: `GET /internal/apm/services/{serviceName}/error_groups/main_statistics`, params: { path: { serviceName }, query: { - start, - end, + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), transactionType: 'request', environment: 'ENVIRONMENT_ALL', kuery: '', diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts deleted file mode 100644 index 3587a3e96c0d16..00000000000000 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts +++ /dev/null @@ -1,202 +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 url from 'url'; -import expect from '@kbn/expect'; -import moment from 'moment'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registry } from '../../common/registry'; -import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { createApmApiClient } from '../../common/apm_api_supertest'; -import { getErrorGroupIds } from './get_error_group_ids'; - -type ErrorGroupsDetailedStatistics = - APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('legacySupertestAsApmReadUser'); - const apmApiSupertest = createApmApiClient(supertest); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - const { start, end } = metadata; - - registry.when( - 'Error groups detailed statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { - it('handles empty state', async () => { - const groupIds = await getErrorGroupIds({ apmApiSupertest, start, end }); - - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(groupIds), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); - }); - } - ); - - registry.when( - 'Error groups detailed statistics when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const groupIds = await getErrorGroupIds({ apmApiSupertest, start, end }); - - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(groupIds), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - - const errorGroupsComparisonStatistics = response.body as ErrorGroupsDetailedStatistics; - expect(Object.keys(errorGroupsComparisonStatistics.currentPeriod).sort()).to.eql( - groupIds.sort() - ); - - groupIds.forEach((groupId) => { - expect(errorGroupsComparisonStatistics.currentPeriod[groupId]).not.to.be.empty(); - }); - - const errorgroupsComparisonStatistics = - errorGroupsComparisonStatistics.currentPeriod[groupIds[0]]; - expect( - errorgroupsComparisonStatistics.timeseries.map(({ y }) => y && isFinite(y)).length - ).to.be.greaterThan(0); - expectSnapshot(errorgroupsComparisonStatistics).toMatch(); - }); - - it('returns an empty state when requested groupIds are not available in the given time range', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(['foo']), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); - }); - } - ); - - registry.when( - 'Error groups detailed statistics when data is loaded with previous data', - { config: 'basic', archives: [archiveName] }, - () => { - describe('returns the correct data', async () => { - let response: { - status: number; - body: ErrorGroupsDetailedStatistics; - }; - let groupIds: string[]; - - before(async () => { - groupIds = await getErrorGroupIds({ apmApiSupertest, start, end }); - - response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(groupIds), - start: moment(end).subtract(15, 'minutes').toISOString(), - end, - comparisonStart: start, - comparisonEnd: moment(start).add(15, 'minutes').toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - }); - - it('returns correct timeseries', () => { - const errorGroupsComparisonStatistics = response.body as ErrorGroupsDetailedStatistics; - const errorgroupsComparisonStatistics = - errorGroupsComparisonStatistics.currentPeriod[groupIds[0]]; - expect( - errorgroupsComparisonStatistics.timeseries.map(({ y }) => y && isFinite(y)).length - ).to.be.greaterThan(0); - expectSnapshot(errorgroupsComparisonStatistics).toMatch(); - }); - - it('matches x-axis on current period and previous period', () => { - const errorGroupsComparisonStatistics = response.body as ErrorGroupsDetailedStatistics; - - const currentPeriodItems = Object.values(errorGroupsComparisonStatistics.currentPeriod); - const previousPeriodItems = Object.values(errorGroupsComparisonStatistics.previousPeriod); - - const currentPeriodFirstItem = currentPeriodItems[0]; - const previousPeriodFirstItem = previousPeriodItems[0]; - - expect(currentPeriodFirstItem.timeseries.map(({ x }) => x)).to.be.eql( - previousPeriodFirstItem.timeseries.map(({ x }) => x) - ); - }); - }); - - it('returns an empty state when requested groupIds are not available in the given time range', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(['foo']), - start: moment(end).subtract(15, 'minutes').toISOString(), - end, - comparisonStart: start, - comparisonEnd: moment(start).add(15, 'minutes').toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts deleted file mode 100644 index b6fb0696f3f74d..00000000000000 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts +++ /dev/null @@ -1,123 +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 url from 'url'; -import expect from '@kbn/expect'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registry } from '../../common/registry'; -import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; - -type ErrorGroupsMainStatistics = - APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('legacySupertestAsApmReadUser'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - const { start, end } = metadata; - - registry.when( - 'Error groups main statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { - it('handles empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/main_statistics`, - query: { - start, - end, - transactionType: 'request', - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - - expect(response.status).to.be(200); - expect(response.body.error_groups).to.empty(); - expect(response.body.is_aggregation_accurate).to.eql(true); - }); - } - ); - - registry.when( - 'Error groups main statistics when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/main_statistics`, - query: { - start, - end, - transactionType: 'request', - environment: 'production', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - - const errorGroupMainStatistics = response.body as ErrorGroupsMainStatistics; - - expect(errorGroupMainStatistics.is_aggregation_accurate).to.eql(true); - expect(errorGroupMainStatistics.error_groups.length).to.be.greaterThan(0); - - expectSnapshot(errorGroupMainStatistics.error_groups.map(({ name }) => name)) - .toMatchInline(` - Array [ - "Response status 404", - "No converter found for return value of type: class com.sun.proxy.$Proxy162", - "Response status 404", - "Broken pipe", - "java.io.IOException: Connection reset by peer", - "Request method 'POST' not supported", - "java.io.IOException: Connection reset by peer", - "null", - ] - `); - - const occurences = errorGroupMainStatistics.error_groups.map( - ({ occurrences }) => occurrences - ); - - occurences.forEach((occurence) => expect(occurence).to.be.greaterThan(0)); - - expectSnapshot(occurences).toMatchInline(` - Array [ - 17, - 12, - 4, - 4, - 3, - 2, - 1, - 1, - ] - `); - - const firstItem = errorGroupMainStatistics.error_groups[0]; - - expectSnapshot(firstItem).toMatchInline(` - Object { - "group_id": "d16d39e7fa133b8943cea035430a7b4e", - "lastSeen": 1627975146078, - "name": "Response status 404", - "occurrences": 17, - } - `); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.ts b/x-pack/test/apm_api_integration/tests/services/throughput.ts index abc7988af823d4..a9865a0c3bb38a 100644 --- a/x-pack/test/apm_api_integration/tests/services/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/services/throughput.ts @@ -63,217 +63,223 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - registry.when('data is loaded', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { - describe('Throughput chart api', () => { - const GO_PROD_RATE = 50; - const GO_DEV_RATE = 5; - const JAVA_PROD_RATE = 45; - - before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( - 'instance-b' - ); - - const serviceJavaInstance = service('synth-java', 'development', 'java').instance( - 'instance-c' - ); - - await synthtraceEsClient.index([ - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction('GET /api/product/list') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(GO_DEV_RATE) - .flatMap((timestamp) => - serviceGoDevInstance - .transaction('GET /api/product/:id') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(JAVA_PROD_RATE) - .flatMap((timestamp) => - serviceJavaInstance - .transaction('POST /api/product/buy') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ]); - }); + registry.when( + 'data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('Throughput chart api', () => { + const GO_PROD_RATE = 50; + const GO_DEV_RATE = 5; + const JAVA_PROD_RATE = 45; - after(() => synthtraceEsClient.clean()); + before(async () => { + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( + 'instance-a' + ); + const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( + 'instance-b' + ); - describe('compare transactions and metrics based throughput', () => { - let throughputMetrics: ThroughputReturn; - let throughputTransactions: ThroughputReturn; + const serviceJavaInstance = service('synth-java', 'development', 'java').instance( + 'instance-c' + ); - before(async () => { - const [throughputMetricsResponse, throughputTransactionsResponse] = await Promise.all([ - callApi({ query: { kuery: 'processor.event : "metric"' } }), - callApi({ query: { kuery: 'processor.event : "transaction"' } }), + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction('GET /api/product/list') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(GO_DEV_RATE) + .flatMap((timestamp) => + serviceGoDevInstance + .transaction('GET /api/product/:id') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(JAVA_PROD_RATE) + .flatMap((timestamp) => + serviceJavaInstance + .transaction('POST /api/product/buy') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), ]); - throughputMetrics = throughputMetricsResponse.body; - throughputTransactions = throughputTransactionsResponse.body; }); - it('returns some transactions data', () => { - expect(throughputTransactions.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughputTransactions.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); - }); + after(() => synthtraceEsClient.clean()); - it('returns some metrics data', () => { - expect(throughputMetrics.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughputMetrics.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); - }); + describe('compare transactions and metrics based throughput', () => { + let throughputMetrics: ThroughputReturn; + let throughputTransactions: ThroughputReturn; - it('has same mean value for metrics and transactions data', () => { - const transactionsMean = meanBy(throughputTransactions.currentPeriod, 'y'); - const metricsMean = meanBy(throughputMetrics.currentPeriod, 'y'); - [transactionsMean, metricsMean].forEach((value) => - expect(roundNumber(value)).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE)) - ); - }); + before(async () => { + const [throughputMetricsResponse, throughputTransactionsResponse] = await Promise.all([ + callApi({ query: { kuery: 'processor.event : "metric"' } }), + callApi({ query: { kuery: 'processor.event : "transaction"' } }), + ]); + throughputMetrics = throughputMetricsResponse.body; + throughputTransactions = throughputTransactionsResponse.body; + }); - it('has a bucket size of 10 seconds for transactions data', () => { - const firstTimerange = throughputTransactions.currentPeriod[0].x; - const secondTimerange = throughputTransactions.currentPeriod[1].x; - const timeIntervalAsSeconds = (secondTimerange - firstTimerange) / 1000; - expect(timeIntervalAsSeconds).to.equal(10); - }); + it('returns some transactions data', () => { + expect(throughputTransactions.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughputTransactions.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); - it('has a bucket size of 1 minute for metrics data', () => { - const firstTimerange = throughputMetrics.currentPeriod[0].x; - const secondTimerange = throughputMetrics.currentPeriod[1].x; - const timeIntervalAsMinutes = (secondTimerange - firstTimerange) / 1000 / 60; - expect(timeIntervalAsMinutes).to.equal(1); - }); - }); + it('returns some metrics data', () => { + expect(throughputMetrics.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughputMetrics.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); - describe('production environment', () => { - let throughput: ThroughputReturn; + it('has same mean value for metrics and transactions data', () => { + const transactionsMean = meanBy(throughputTransactions.currentPeriod, 'y'); + const metricsMean = meanBy(throughputMetrics.currentPeriod, 'y'); + [transactionsMean, metricsMean].forEach((value) => + expect(roundNumber(value)).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE)) + ); + }); - before(async () => { - const throughputResponse = await callApi({ query: { environment: 'production' } }); - throughput = throughputResponse.body; - }); + it('has a bucket size of 10 seconds for transactions data', () => { + const firstTimerange = throughputTransactions.currentPeriod[0].x; + const secondTimerange = throughputTransactions.currentPeriod[1].x; + const timeIntervalAsSeconds = (secondTimerange - firstTimerange) / 1000; + expect(timeIntervalAsSeconds).to.equal(10); + }); - it('returns some data', () => { - expect(throughput.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); + it('has a bucket size of 1 minute for metrics data', () => { + const firstTimerange = throughputMetrics.currentPeriod[0].x; + const secondTimerange = throughputMetrics.currentPeriod[1].x; + const timeIntervalAsMinutes = (secondTimerange - firstTimerange) / 1000 / 60; + expect(timeIntervalAsMinutes).to.equal(1); + }); }); - it('returns correct average throughput', () => { - const throughputMean = meanBy(throughput.currentPeriod, 'y'); - expect(roundNumber(throughputMean)).to.be.equal(roundNumber(GO_PROD_RATE)); - }); - }); + describe('production environment', () => { + let throughput: ThroughputReturn; - describe('when synth-java is selected', () => { - let throughput: ThroughputReturn; + before(async () => { + const throughputResponse = await callApi({ query: { environment: 'production' } }); + throughput = throughputResponse.body; + }); - before(async () => { - const throughputResponse = await callApi({ path: { serviceName: 'synth-java' } }); - throughput = throughputResponse.body; - }); + it('returns some data', () => { + expect(throughput.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); - it('returns some data', () => { - expect(throughput.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); + it('returns correct average throughput', () => { + const throughputMean = meanBy(throughput.currentPeriod, 'y'); + expect(roundNumber(throughputMean)).to.be.equal(roundNumber(GO_PROD_RATE)); + }); }); - it('returns throughput related to java agent', () => { - const throughputMean = meanBy(throughput.currentPeriod, 'y'); - expect(roundNumber(throughputMean)).to.be.equal(roundNumber(JAVA_PROD_RATE)); - }); - }); + describe('when synth-java is selected', () => { + let throughput: ThroughputReturn; - describe('time comparisons', () => { - let throughputResponse: ThroughputReturn; + before(async () => { + const throughputResponse = await callApi({ path: { serviceName: 'synth-java' } }); + throughput = throughputResponse.body; + }); - before(async () => { - const response = await callApi({ - query: { - start: moment(end).subtract(7, 'minutes').toISOString(), - end: new Date(end).toISOString(), - comparisonStart: new Date(start).toISOString(), - comparisonEnd: moment(start).add(7, 'minutes').toISOString(), - }, + it('returns some data', () => { + expect(throughput.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); + + it('returns throughput related to java agent', () => { + const throughputMean = meanBy(throughput.currentPeriod, 'y'); + expect(roundNumber(throughputMean)).to.be.equal(roundNumber(JAVA_PROD_RATE)); }); - throughputResponse = response.body; }); - it('returns some data', () => { - expect(throughputResponse.currentPeriod.length).to.be.greaterThan(0); - expect(throughputResponse.previousPeriod.length).to.be.greaterThan(0); + describe('time comparisons', () => { + let throughputResponse: ThroughputReturn; + + before(async () => { + const response = await callApi({ + query: { + start: moment(end).subtract(7, 'minutes').toISOString(), + end: new Date(end).toISOString(), + comparisonStart: new Date(start).toISOString(), + comparisonEnd: moment(start).add(7, 'minutes').toISOString(), + }, + }); + throughputResponse = response.body; + }); - const hasCurrentPeriodData = throughputResponse.currentPeriod.some(({ y }) => - isFiniteNumber(y) - ); - const hasPreviousPeriodData = throughputResponse.previousPeriod.some(({ y }) => - isFiniteNumber(y) - ); + it('returns some data', () => { + expect(throughputResponse.currentPeriod.length).to.be.greaterThan(0); + expect(throughputResponse.previousPeriod.length).to.be.greaterThan(0); - expect(hasCurrentPeriodData).to.equal(true); - expect(hasPreviousPeriodData).to.equal(true); - }); + const hasCurrentPeriodData = throughputResponse.currentPeriod.some(({ y }) => + isFiniteNumber(y) + ); + const hasPreviousPeriodData = throughputResponse.previousPeriod.some(({ y }) => + isFiniteNumber(y) + ); - it('has same start time for both periods', () => { - expect(first(throughputResponse.currentPeriod)?.x).to.equal( - first(throughputResponse.previousPeriod)?.x - ); - }); + expect(hasCurrentPeriodData).to.equal(true); + expect(hasPreviousPeriodData).to.equal(true); + }); - it('has same end time for both periods', () => { - expect(last(throughputResponse.currentPeriod)?.x).to.equal( - last(throughputResponse.previousPeriod)?.x - ); - }); + it('has same start time for both periods', () => { + expect(first(throughputResponse.currentPeriod)?.x).to.equal( + first(throughputResponse.previousPeriod)?.x + ); + }); - it('returns same number of buckets for both periods', () => { - expect(throughputResponse.currentPeriod.length).to.be( - throughputResponse.previousPeriod.length - ); - }); + it('has same end time for both periods', () => { + expect(last(throughputResponse.currentPeriod)?.x).to.equal( + last(throughputResponse.previousPeriod)?.x + ); + }); - it('has same mean value for both periods', () => { - const currentPeriodMean = meanBy( - throughputResponse.currentPeriod.filter((item) => isFiniteNumber(item.y) && item.y > 0), - 'y' - ); - const previousPeriodMean = meanBy( - throughputResponse.previousPeriod.filter( - (item) => isFiniteNumber(item.y) && item.y > 0 - ), - 'y' - ); - const currentPeriod = throughputResponse.currentPeriod; - const bucketSize = currentPeriod[1].x - currentPeriod[0].x; - const durationAsMinutes = bucketSize / 1000 / 60; - [currentPeriodMean, previousPeriodMean].every((value) => - expect(roundNumber(value)).to.be.equal( - roundNumber((GO_PROD_RATE + GO_DEV_RATE) / durationAsMinutes) - ) - ); + it('returns same number of buckets for both periods', () => { + expect(throughputResponse.currentPeriod.length).to.be( + throughputResponse.previousPeriod.length + ); + }); + + it('has same mean value for both periods', () => { + const currentPeriodMean = meanBy( + throughputResponse.currentPeriod.filter( + (item) => isFiniteNumber(item.y) && item.y > 0 + ), + 'y' + ); + const previousPeriodMean = meanBy( + throughputResponse.previousPeriod.filter( + (item) => isFiniteNumber(item.y) && item.y > 0 + ), + 'y' + ); + const currentPeriod = throughputResponse.currentPeriod; + const bucketSize = currentPeriod[1].x - currentPeriod[0].x; + const durationAsMinutes = bucketSize / 1000 / 60; + [currentPeriodMean, previousPeriodMean].every((value) => + expect(roundNumber(value)).to.be.equal( + roundNumber((GO_PROD_RATE + GO_DEV_RATE) / durationAsMinutes) + ) + ); + }); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts index d85331b8be45de..42d98f38e4e4a7 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -6,30 +6,39 @@ */ import expect from '@kbn/expect'; -import { sortBy, pick, isEmpty } from 'lodash'; +import { sortBy } from 'lodash'; +import { service, timerange } from '@elastic/apm-synthtrace'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; +import { ENVIRONMENT_ALL } from '../../../../plugins/apm/common/environment_filter_values'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); + + const apmApiClient = getService('apmApiClient'); + const synthtrace = getService('synthtraceEsClient'); + const supertestAsApmReadUserWithoutMlAccess = getService( 'legacySupertestAsApmReadUserWithoutMlAccess' ); const archiveName = 'apm_8.0.0'; - const range = archives_metadata[archiveName]; + const archiveRange = archives_metadata[archiveName]; // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); + const archiveStart = encodeURIComponent(archiveRange.start); + const archiveEnd = encodeURIComponent(archiveRange.end); + + const start = '2021-10-01T00:00:00.000Z'; + const end = '2021-10-01T00:05:00.000Z'; registry.when( - 'APM Services Overview with a basic license when data is not loaded', - { config: 'basic', archives: [] }, + 'APM Services Overview with a basic license when data is not generated', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { it('handles the empty state', async () => { const response = await supertest.get( @@ -44,187 +53,242 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); registry.when( - 'APM Services Overview with a basic license when data is loaded', - { config: 'basic', archives: [archiveName] }, + 'APM Services Overview with a basic license when data is generated', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { let response: { status: number; body: APIReturnType<'GET /internal/apm/services'>; }; - let sortedItems: typeof response.body.items; + const range = timerange(new Date(start).getTime(), new Date(end).getTime()); + const transactionInterval = range.interval('1s'); + const metricInterval = range.interval('30s'); + + const multipleEnvServiceProdInstance = service( + 'multiple-env-service', + 'production', + 'go' + ).instance('multiple-env-service-production'); + + const multipleEnvServiceDevInstance = service( + 'multiple-env-service', + 'development', + 'go' + ).instance('multiple-env-service-development'); + + const metricOnlyInstance = service('metric-only-service', 'production', 'java').instance( + 'metric-only-production' + ); + + const config = { + multiple: { + prod: { + rps: 4, + duration: 1000, + }, + dev: { + rps: 1, + duration: 500, + }, + }, + }; before(async () => { - response = await supertest.get( - `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` - ); - sortedItems = sortBy(response.body.items, 'serviceName'); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('returns hasLegacyData: false', () => { - expect(response.body.hasLegacyData).to.be(false); + return synthtrace.index([ + ...transactionInterval + .rate(config.multiple.prod.rps) + .flatMap((timestamp) => [ + ...multipleEnvServiceProdInstance + .transaction('GET /api') + .timestamp(timestamp) + .duration(config.multiple.prod.duration) + .success() + .serialize(), + ]), + ...transactionInterval + .rate(config.multiple.dev.rps) + .flatMap((timestamp) => [ + ...multipleEnvServiceDevInstance + .transaction('GET /api') + .timestamp(timestamp) + .duration(config.multiple.dev.duration) + .failure() + .serialize(), + ]), + ...transactionInterval + .rate(config.multiple.prod.rps) + .flatMap((timestamp) => [ + ...multipleEnvServiceDevInstance + .transaction('non-request', 'rpc') + .timestamp(timestamp) + .duration(config.multiple.prod.duration) + .success() + .serialize(), + ]), + ...metricInterval.rate(1).flatMap((timestamp) => [ + ...metricOnlyInstance + .appMetrics({ + 'system.memory.actual.free': 1, + 'system.cpu.total.norm.pct': 1, + 'system.memory.total': 1, + 'system.process.cpu.total.norm.pct': 1, + }) + .timestamp(timestamp) + .serialize(), + ]), + ]); }); - it('returns the correct service names', () => { - expectSnapshot(sortedItems.map((item) => item.serviceName)).toMatchInline(` - Array [ - "auditbeat", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] - `); + after(() => { + return synthtrace.clean(); }); - it('returns the correct metrics averages', () => { - expectSnapshot( - sortedItems.map((item) => pick(item, 'transactionErrorRate', 'latency', 'throughput')) - ).toMatchInline(` - Array [ - Object {}, - Object { - "latency": 496794.054441261, - "throughput": 11.6333333333333, - "transactionErrorRate": 0.0315186246418338, - }, - Object { - "latency": 83395.638576779, - "throughput": 17.8, - "transactionErrorRate": 0.00936329588014981, - }, - Object { - "latency": 430318.696035242, - "throughput": 7.56666666666667, - "transactionErrorRate": 0.092511013215859, - }, - Object { - "latency": 53147.5747663551, - "throughput": 7.13333333333333, - "transactionErrorRate": 0, - }, - Object { - "latency": 419826.24375, - "throughput": 5.33333333333333, - "transactionErrorRate": 0.025, - }, - Object { - "latency": 21520.4776632302, - "throughput": 9.7, - "transactionErrorRate": 0.00343642611683849, - }, - Object { - "latency": 1040388.88888889, - "throughput": 2.4, - "transactionErrorRate": null, + describe('when no additional filters are applied', () => { + before(async () => { + response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services', + params: { + query: { + start, + end, + environment: ENVIRONMENT_ALL.value, + kuery: '', + }, }, - ] - `); - }); + }); + }); - it('returns environments', () => { - expectSnapshot(sortedItems.map((item) => item.environments ?? [])).toMatchInline(` - Array [ - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - ] - `); - }); + it('returns a successful response', () => { + expect(response.status).to.be(200); + }); - it(`RUM services don't report any transaction error rates`, () => { - // RUM transactions don't have event.outcome set, - // so they should not have an error rate + it('returns the correct statistics', () => { + const multipleEnvService = response.body.items.find( + (item) => item.serviceName === 'multiple-env-service' + ); - const rumServices = sortedItems.filter((item) => item.agentName === 'rum-js'); + const totalRps = config.multiple.prod.rps + config.multiple.dev.rps; + + expect(multipleEnvService).to.eql({ + serviceName: 'multiple-env-service', + transactionType: 'request', + environments: ['production', 'development'], + agentName: 'go', + latency: + 1000 * + ((config.multiple.prod.duration * config.multiple.prod.rps + + config.multiple.dev.duration * config.multiple.dev.rps) / + totalRps), + throughput: totalRps * 60, + transactionErrorRate: + config.multiple.dev.rps / (config.multiple.prod.rps + config.multiple.dev.rps), + }); + }); - expect(rumServices.length).to.be.greaterThan(0); + it('returns services without transaction data', () => { + const serviceNames = response.body.items.map((item) => item.serviceName); - expect(rumServices.every((item) => isEmpty(item.transactionErrorRate))); + expect(serviceNames).to.contain('metric-only-service'); + }); }); - it('non-RUM services all report transaction error rates', () => { - const nonRumServices = sortedItems.filter( - (item) => item.agentName !== 'rum-js' && item.serviceName !== 'auditbeat' - ); + describe('when applying an environment filter', () => { + before(async () => { + response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services', + params: { + query: { + start, + end, + environment: 'production', + kuery: '', + }, + }, + }); + }); - expect( - nonRumServices.every((item) => { - return typeof item.transactionErrorRate === 'number'; - }) - ).to.be(true); + it('returns data only for that environment', () => { + const multipleEnvService = response.body.items.find( + (item) => item.serviceName === 'multiple-env-service' + ); + + const totalRps = config.multiple.prod.rps; + + expect(multipleEnvService).to.eql({ + serviceName: 'multiple-env-service', + transactionType: 'request', + environments: ['production'], + agentName: 'go', + latency: 1000 * ((config.multiple.prod.duration * config.multiple.prod.rps) / totalRps), + throughput: totalRps * 60, + transactionErrorRate: 0, + }); + }); }); - } - ); - registry.when( - 'APM Services Overview with a basic license when data is loaded excluding transaction events', - { config: 'basic', archives: [archiveName] }, - () => { - it('includes services that only report metric data', async () => { - interface Response { - status: number; - body: APIReturnType<'GET /internal/apm/services'>; - } - - const [unfilteredResponse, filteredResponse] = await Promise.all([ - supertest.get( - `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` - ) as Promise, - supertest.get( - `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=${encodeURIComponent( - 'not (processor.event:transaction)' - )}` - ) as Promise, - ]); + describe('when applying a kuery filter', () => { + before(async () => { + response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services', + params: { + query: { + start, + end, + environment: ENVIRONMENT_ALL.value, + kuery: 'service.node.name:"multiple-env-service-development"', + }, + }, + }); + }); - expect(unfilteredResponse.body.items.length).to.be.greaterThan(0); + it('returns data for that kuery filter only', () => { + const multipleEnvService = response.body.items.find( + (item) => item.serviceName === 'multiple-env-service' + ); - const unfilteredServiceNames = unfilteredResponse.body.items - .map((item) => item.serviceName) - .sort(); + const totalRps = config.multiple.dev.rps; - const filteredServiceNames = filteredResponse.body.items - .map((item) => item.serviceName) - .sort(); + expect(multipleEnvService).to.eql({ + serviceName: 'multiple-env-service', + transactionType: 'request', + environments: ['development'], + agentName: 'go', + latency: 1000 * ((config.multiple.dev.duration * config.multiple.dev.rps) / totalRps), + throughput: totalRps * 60, + transactionErrorRate: 1, + }); + }); + }); - expect(unfilteredServiceNames).to.eql(filteredServiceNames); + describe('when excluding default transaction types', () => { + before(async () => { + response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services', + params: { + query: { + start, + end, + environment: ENVIRONMENT_ALL.value, + kuery: 'not (transaction.type:request)', + }, + }, + }); + }); - expect(filteredResponse.body.items.every((item) => !!item.agentName)).to.be(true); + it('returns data for the top transaction type that is not a default', () => { + const multipleEnvService = response.body.items.find( + (item) => item.serviceName === 'multiple-env-service' + ); + + expect(multipleEnvService?.transactionType).to.eql('rpc'); + }); }); } ); registry.when( - 'APM Services overview with a trial license when data is loaded', + 'APM Services Overview with a trial license when data is loaded', { config: 'trial', archives: [archiveName] }, () => { describe('with the default APM read user', () => { @@ -236,7 +300,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( - `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services?start=${archiveStart}&end=${archiveEnd}&environment=ENVIRONMENT_ALL&kuery=` ); }); @@ -282,7 +346,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let response: PromiseReturnType; before(async () => { response = await supertestAsApmReadUserWithoutMlAccess.get( - `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services?start=${archiveStart}&end=${archiveEnd}&environment=ENVIRONMENT_ALL&kuery=` ); }); @@ -307,7 +371,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let response: PromiseReturnType; before(async () => { response = await supertest.get( - `/internal/apm/services?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&kuery=${encodeURIComponent( + `/internal/apm/services?environment=ENVIRONMENT_ALL&start=${archiveStart}&end=${archiveEnd}&kuery=${encodeURIComponent( 'service.name:opbeans-java' )}` ); diff --git a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts index 1aa3ebb7b985b2..4cc9b2f0679fef 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts @@ -88,7 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when( 'Dependencies throughput value', - { config: 'basic', archives: ['apm_8.0.0_empty'] }, + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded', () => { const GO_PROD_RATE = 75; diff --git a/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts b/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts index ea5252d3490ca0..c4fffd8d79afbf 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts @@ -104,7 +104,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let throughputMetricValues: PromiseReturnType; let throughputTransactionValues: PromiseReturnType; - registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { + registry.when('Services APIs', { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded ', () => { const GO_PROD_RATE = 80; const GO_DEV_RATE = 20; diff --git a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap index 64e1754c9570f6..604348355f38c8 100644 --- a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap @@ -3,7 +3,7 @@ exports[`APM API tests basic apm_8.0.0 Top traces when data is loaded returns the correct buckets 1`] = ` Array [ Object { - "averageResponseTime": 1638, + "averageResponseTime": 1639, "impact": 0, "key": Object { "service.name": "opbeans-java", @@ -15,8 +15,8 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { - "averageResponseTime": 3278, - "impact": 0.00153950779720334, + "averageResponseTime": 3279, + "impact": 0.00144735571024101, "key": Object { "service.name": "opbeans-node", "transaction.name": "POST /api/orders", @@ -27,8 +27,8 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { - "averageResponseTime": 6169, - "impact": 0.00425335965190752, + "averageResponseTime": 6175, + "impact": 0.00400317408637392, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/products/:id", @@ -39,8 +39,8 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { - "averageResponseTime": 3486, - "impact": 0.00500715523797721, + "averageResponseTime": 3495, + "impact": 0.00472243927164613, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "POST Orders/Post", @@ -51,8 +51,8 @@ Array [ "transactionsPerMinute": 0.0666666666666667, }, Object { - "averageResponseTime": 7022, - "impact": 0.00505409145130658, + "averageResponseTime": 7039, + "impact": 0.00476568343615943, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.product", @@ -63,8 +63,8 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { - "averageResponseTime": 6279, - "impact": 0.0102508689911344, + "averageResponseTime": 6303, + "impact": 0.00967875004525193, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::OrdersController#create", @@ -75,8 +75,8 @@ Array [ "transactionsPerMinute": 0.0666666666666667, }, Object { - "averageResponseTime": 7959, - "impact": 0.0134049825268681, + "averageResponseTime": 7209.66666666667, + "impact": 0.0176418540534865, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#products", @@ -84,35 +84,11 @@ Array [ "serviceName": "opbeans-java", "transactionName": "APIRestController#products", "transactionType": "request", - "transactionsPerMinute": 0.0666666666666667, - }, - Object { - "averageResponseTime": 7411.33333333333, - "impact": 0.0193339649946342, - "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.order", - }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.order", - "transactionType": "request", "transactionsPerMinute": 0.1, }, Object { - "averageResponseTime": 11729, - "impact": 0.0204829634969371, - "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::OrdersController#show", - }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::OrdersController#show", - "transactionType": "request", - "transactionsPerMinute": 0.0666666666666667, - }, - Object { - "averageResponseTime": 4499.16666666667, - "impact": 0.0238032312278568, + "averageResponseTime": 4511, + "impact": 0.0224401912465233, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#orders", @@ -123,8 +99,20 @@ Array [ "transactionsPerMinute": 0.2, }, Object { - "averageResponseTime": 10126.3333333333, - "impact": 0.0269798741459886, + "averageResponseTime": 7607, + "impact": 0.0254072704525173, + "key": Object { + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.order", + }, + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.order", + "transactionType": "request", + "transactionsPerMinute": 0.133333333333333, + }, + Object { + "averageResponseTime": 10143, + "impact": 0.025408152986487, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/types", @@ -135,32 +123,32 @@ Array [ "transactionsPerMinute": 0.1, }, Object { - "averageResponseTime": 6089.83333333333, - "impact": 0.032762415628167, + "averageResponseTime": 6105.66666666667, + "impact": 0.0308842762682221, "key": Object { - "service.name": "opbeans-java", - "transaction.name": "APIRestController#customerWhoBought", + "service.name": "opbeans-ruby", + "transaction.name": "Api::TypesController#index", }, - "serviceName": "opbeans-java", - "transactionName": "APIRestController#customerWhoBought", + "serviceName": "opbeans-ruby", + "transactionName": "Api::TypesController#index", "transactionType": "request", "transactionsPerMinute": 0.2, }, Object { - "averageResponseTime": 6094.83333333333, - "impact": 0.0327905773561646, + "averageResponseTime": 6116.33333333333, + "impact": 0.0309407584422802, "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::TypesController#index", + "service.name": "opbeans-java", + "transaction.name": "APIRestController#customerWhoBought", }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::TypesController#index", + "serviceName": "opbeans-java", + "transactionName": "APIRestController#customerWhoBought", "transactionType": "request", "transactionsPerMinute": 0.2, }, Object { - "averageResponseTime": 12527.3333333333, - "impact": 0.0337415050382176, + "averageResponseTime": 12543, + "impact": 0.0317623975680329, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#customers", @@ -171,8 +159,8 @@ Array [ "transactionsPerMinute": 0.1, }, Object { - "averageResponseTime": 5534.85714285714, - "impact": 0.0348323026359922, + "averageResponseTime": 5551, + "impact": 0.0328461492827744, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/orders/:id", @@ -183,8 +171,8 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 13149.3333333333, - "impact": 0.0354931645196697, + "averageResponseTime": 13183, + "impact": 0.0334568627897785, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#stats", @@ -195,8 +183,8 @@ Array [ "transactionsPerMinute": 0.1, }, Object { - "averageResponseTime": 8025.8, - "impact": 0.0361324357452157, + "averageResponseTime": 8050.2, + "impact": 0.0340764016364792, "key": Object { "service.name": "opbeans-go", "transaction.name": "POST /api/orders", @@ -207,8 +195,20 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { - "averageResponseTime": 8432.8, - "impact": 0.0380427396277211, + "averageResponseTime": 10079, + "impact": 0.0341337663445071, + "key": Object { + "service.name": "opbeans-ruby", + "transaction.name": "Api::OrdersController#show", + }, + "serviceName": "opbeans-ruby", + "transactionName": "Api::OrdersController#show", + "transactionType": "request", + "transactionsPerMinute": 0.133333333333333, + }, + Object { + "averageResponseTime": 8463, + "impact": 0.0358979517498557, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/products/:id/customers", @@ -219,32 +219,32 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { - "averageResponseTime": 7408, - "impact": 0.0401867858526067, + "averageResponseTime": 10799, + "impact": 0.0366754641771254, "key": Object { "service.name": "opbeans-ruby", - "transaction.name": "Api::TypesController#show", + "transaction.name": "Api::ProductsController#show", }, "serviceName": "opbeans-ruby", - "transactionName": "Api::TypesController#show", + "transactionName": "Api::ProductsController#show", "transactionType": "request", - "transactionsPerMinute": 0.2, + "transactionsPerMinute": 0.133333333333333, }, Object { - "averageResponseTime": 6869.42857142857, - "impact": 0.0436018647344517, + "averageResponseTime": 7428.33333333333, + "impact": 0.0378880658514371, "key": Object { - "service.name": "opbeans-java", - "transaction.name": "APIRestController#order", + "service.name": "opbeans-ruby", + "transaction.name": "Api::TypesController#show", }, - "serviceName": "opbeans-java", - "transactionName": "APIRestController#order", + "serviceName": "opbeans-ruby", + "transactionName": "Api::TypesController#show", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.2, }, Object { - "averageResponseTime": 3050, - "impact": 0.0442721138607951, + "averageResponseTime": 3105.13333333333, + "impact": 0.039659311528543, "key": Object { "service.name": "opbeans-java", "transaction.name": "ResourceHttpRequestHandler", @@ -252,23 +252,23 @@ Array [ "serviceName": "opbeans-java", "transactionName": "ResourceHttpRequestHandler", "transactionType": "request", - "transactionsPerMinute": 0.533333333333333, + "transactionsPerMinute": 0.5, }, Object { - "averageResponseTime": 10786.2, - "impact": 0.0490887080726551, + "averageResponseTime": 6883.57142857143, + "impact": 0.0410784261517549, "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::ProductsController#show", + "service.name": "opbeans-java", + "transaction.name": "APIRestController#order", }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::ProductsController#show", + "serviceName": "opbeans-java", + "transactionName": "APIRestController#order", "transactionType": "request", - "transactionsPerMinute": 0.166666666666667, + "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 3497.6875, - "impact": 0.0509961957823607, + "averageResponseTime": 3505, + "impact": 0.0480460318422139, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Products/Get", @@ -279,8 +279,8 @@ Array [ "transactionsPerMinute": 0.533333333333333, }, Object { - "averageResponseTime": 5604.9, - "impact": 0.0510769260692872, + "averageResponseTime": 5621.4, + "impact": 0.0481642913941483, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#topProducts", @@ -291,44 +291,44 @@ Array [ "transactionsPerMinute": 0.333333333333333, }, Object { - "averageResponseTime": 8500.57142857143, - "impact": 0.0543202184103467, + "averageResponseTime": 8428.71428571429, + "impact": 0.0506239135675883, "key": Object { "service.name": "opbeans-node", - "transaction.name": "GET /api/customers/:id", + "transaction.name": "GET /api/orders", }, "serviceName": "opbeans-node", - "transactionName": "GET /api/customers/:id", + "transactionName": "GET /api/orders", "transactionType": "request", "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 6658.88888888889, - "impact": 0.0547201149479129, + "averageResponseTime": 8520.14285714286, + "impact": 0.0511887353081702, "key": Object { "service.name": "opbeans-node", - "transaction.name": "GET /api/products/top", + "transaction.name": "GET /api/customers/:id", }, "serviceName": "opbeans-node", - "transactionName": "GET /api/products/top", + "transactionName": "GET /api/customers/:id", "transactionType": "request", - "transactionsPerMinute": 0.3, + "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 8141.125, - "impact": 0.0596005424099008, + "averageResponseTime": 6683.44444444444, + "impact": 0.0516388276326964, "key": Object { "service.name": "opbeans-node", - "transaction.name": "GET /api/orders", + "transaction.name": "GET /api/products/top", }, "serviceName": "opbeans-node", - "transactionName": "GET /api/orders", + "transactionName": "GET /api/products/top", "transactionType": "request", - "transactionsPerMinute": 0.266666666666667, + "transactionsPerMinute": 0.3, }, Object { - "averageResponseTime": 3473.52631578947, - "impact": 0.0604153550732987, + "averageResponseTime": 3482.78947368421, + "impact": 0.0569534471979838, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Types/Get", @@ -339,20 +339,20 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { - "averageResponseTime": 7875, - "impact": 0.064994452045712, + "averageResponseTime": 16703, + "impact": 0.057517386404596, "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/types/:id", + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.product_type", }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/types/:id", + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.product_type", "transactionType": "request", - "transactionsPerMinute": 0.3, + "transactionsPerMinute": 0.133333333333333, }, Object { - "averageResponseTime": 4889, - "impact": 0.0673037137415171, + "averageResponseTime": 4943, + "impact": 0.0596266425920813, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Products/Get {id}", @@ -360,23 +360,23 @@ Array [ "serviceName": "opbeans-dotnet", "transactionName": "GET Products/Get {id}", "transactionType": "request", - "transactionsPerMinute": 0.5, + "transactionsPerMinute": 0.466666666666667, }, Object { - "averageResponseTime": 14902.4, - "impact": 0.0684085922032904, + "averageResponseTime": 7892.33333333333, + "impact": 0.0612407972225879, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.product_type", + "service.name": "opbeans-node", + "transaction.name": "GET /api/types/:id", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.product_type", + "serviceName": "opbeans-node", + "transactionName": "GET /api/types/:id", "transactionType": "request", - "transactionsPerMinute": 0.166666666666667, + "transactionsPerMinute": 0.3, }, Object { - "averageResponseTime": 6329.35714285714, - "impact": 0.0816436656379062, + "averageResponseTime": 6346.42857142857, + "impact": 0.0769666700279444, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Orders/Get {id}", @@ -387,8 +387,8 @@ Array [ "transactionsPerMinute": 0.466666666666667, }, Object { - "averageResponseTime": 6627.42857142857, - "impact": 0.0855609620023755, + "averageResponseTime": 7052.84615384615, + "impact": 0.0794704188998674, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/products", @@ -396,11 +396,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/products", "transactionType": "request", - "transactionsPerMinute": 0.466666666666667, + "transactionsPerMinute": 0.433333333333333, }, Object { - "averageResponseTime": 10459, - "impact": 0.0868254235894687, + "averageResponseTime": 10484.3333333333, + "impact": 0.0818285496667966, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#product", @@ -411,8 +411,8 @@ Array [ "transactionsPerMinute": 0.3, }, Object { - "averageResponseTime": 23652.5, - "impact": 0.087275072513164, + "averageResponseTime": 23711, + "impact": 0.0822565786420813, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/stats", @@ -423,8 +423,8 @@ Array [ "transactionsPerMinute": 0.133333333333333, }, Object { - "averageResponseTime": 4480.95454545455, - "impact": 0.0910027465757826, + "averageResponseTime": 4491.36363636364, + "impact": 0.0857567083657495, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Types/Get {id}", @@ -435,8 +435,8 @@ Array [ "transactionsPerMinute": 0.733333333333333, }, Object { - "averageResponseTime": 20677.4, - "impact": 0.0955142554010017, + "averageResponseTime": 20715.8, + "impact": 0.089965512867054, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.stats", @@ -447,8 +447,8 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { - "averageResponseTime": 9015.33333333333, - "impact": 0.100017315707821, + "averageResponseTime": 9036.33333333333, + "impact": 0.0942519803576885, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/products", @@ -459,20 +459,8 @@ Array [ "transactionsPerMinute": 0.4, }, Object { - "averageResponseTime": 12096.8888888889, - "impact": 0.100663158003234, - "key": Object { - "service.name": "opbeans-go", - "transaction.name": "GET /api/customers", - }, - "serviceName": "opbeans-go", - "transactionName": "GET /api/customers", - "transactionType": "request", - "transactionsPerMinute": 0.3, - }, - Object { - "averageResponseTime": 7361.46666666667, - "impact": 0.102118180616444, + "averageResponseTime": 7504.06666666667, + "impact": 0.0978924329825326, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#customer", @@ -483,8 +471,8 @@ Array [ "transactionsPerMinute": 0.5, }, Object { - "averageResponseTime": 4141.07142857143, - "impact": 0.107307448362139, + "averageResponseTime": 4250.55555555556, + "impact": 0.0998375378516613, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/types/:id", @@ -492,11 +480,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/types/:id", "transactionType": "request", - "transactionsPerMinute": 0.933333333333333, + "transactionsPerMinute": 0.9, }, Object { - "averageResponseTime": 21277, - "impact": 0.118301786972411, + "averageResponseTime": 21343, + "impact": 0.11156906191034, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/customers", @@ -507,8 +495,8 @@ Array [ "transactionsPerMinute": 0.2, }, Object { - "averageResponseTime": 16602.25, - "impact": 0.123141849290936, + "averageResponseTime": 16655, + "impact": 0.116142352941114, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::ProductsController#top", @@ -519,8 +507,20 @@ Array [ "transactionsPerMinute": 0.266666666666667, }, Object { - "averageResponseTime": 9924.07142857143, - "impact": 0.128885903078184, + "averageResponseTime": 5749, + "impact": 0.12032203382142, + "key": Object { + "service.name": "opbeans-go", + "transaction.name": "GET /api/types", + }, + "serviceName": "opbeans-go", + "transactionName": "GET /api/types", + "transactionType": "request", + "transactionsPerMinute": 0.8, + }, + Object { + "averageResponseTime": 9951, + "impact": 0.121502864272824, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::StatsController#index", @@ -531,20 +531,20 @@ Array [ "transactionsPerMinute": 0.466666666666667, }, Object { - "averageResponseTime": 5444.5, - "impact": 0.131345360656643, + "averageResponseTime": 14040.6, + "impact": 0.122466591367692, "key": Object { "service.name": "opbeans-go", - "transaction.name": "GET /api/types", + "transaction.name": "GET /api/customers", }, "serviceName": "opbeans-go", - "transactionName": "GET /api/types", + "transactionName": "GET /api/customers", "transactionType": "request", - "transactionsPerMinute": 0.866666666666667, + "transactionsPerMinute": 0.333333333333333, }, Object { - "averageResponseTime": 20932.4285714286, - "impact": 0.136010820261582, + "averageResponseTime": 20963.5714285714, + "impact": 0.128060974201361, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::OrdersController#index", @@ -555,20 +555,8 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 12301.3333333333, - "impact": 0.137033090987896, - "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::ProductsController#index", - }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::ProductsController#index", - "transactionType": "request", - "transactionsPerMinute": 0.4, - }, - Object { - "averageResponseTime": 22818.7142857143, - "impact": 0.148405735477602, + "averageResponseTime": 22874.4285714286, + "impact": 0.139865748579522, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.customer", @@ -579,8 +567,8 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 32098.4, - "impact": 0.149120104644475, + "averageResponseTime": 32203.8, + "impact": 0.140658264084276, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.customers", @@ -591,8 +579,8 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { - "averageResponseTime": 4585.91428571429, - "impact": 0.149134185508474, + "averageResponseTime": 4482.11111111111, + "impact": 0.140955678032051, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/customers/:id", @@ -600,23 +588,23 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/customers/:id", "transactionType": "request", - "transactionsPerMinute": 1.16666666666667, + "transactionsPerMinute": 1.2, }, Object { - "averageResponseTime": 20449.3333333333, - "impact": 0.171228938571142, + "averageResponseTime": 12582.3846153846, + "impact": 0.142910490774846, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.orders", + "service.name": "opbeans-ruby", + "transaction.name": "Api::ProductsController#index", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.orders", + "serviceName": "opbeans-ruby", + "transactionName": "Api::ProductsController#index", "transactionType": "request", - "transactionsPerMinute": 0.3, + "transactionsPerMinute": 0.433333333333333, }, Object { - "averageResponseTime": 9981.1052631579, - "impact": 0.176482978291232, + "averageResponseTime": 10009.9473684211, + "impact": 0.166401779979233, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::CustomersController#show", @@ -627,8 +615,8 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { - "averageResponseTime": 27758.7142857143, - "impact": 0.180866820616195, + "averageResponseTime": 27825.2857142857, + "impact": 0.170450845832029, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.product_types", @@ -639,20 +627,20 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 8332.06896551724, - "impact": 0.225286314186844, + "averageResponseTime": 20562.2, + "impact": 0.180021926732983, "key": Object { - "service.name": "opbeans-go", - "transaction.name": "GET /api/orders/:id", + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.orders", }, - "serviceName": "opbeans-go", - "transactionName": "GET /api/orders/:id", + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.orders", "transactionType": "request", - "transactionsPerMinute": 0.966666666666667, + "transactionsPerMinute": 0.333333333333333, }, Object { - "averageResponseTime": 6976.62857142857, - "impact": 0.227681938515175, + "averageResponseTime": 7106.76470588235, + "impact": 0.21180020991247, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Customers/Get {id}", @@ -660,11 +648,23 @@ Array [ "serviceName": "opbeans-dotnet", "transactionName": "GET Customers/Get {id}", "transactionType": "request", - "transactionsPerMinute": 1.16666666666667, + "transactionsPerMinute": 1.13333333333333, + }, + Object { + "averageResponseTime": 8612.51724137931, + "impact": 0.218977858687708, + "key": Object { + "service.name": "opbeans-go", + "transaction.name": "GET /api/orders/:id", + }, + "serviceName": "opbeans-go", + "transactionName": "GET /api/orders/:id", + "transactionType": "request", + "transactionsPerMinute": 0.966666666666667, }, Object { - "averageResponseTime": 11321.7777777778, - "impact": 0.2854191132559, + "averageResponseTime": 11295, + "impact": 0.277663720068132, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::CustomersController#index", @@ -672,11 +672,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::CustomersController#index", "transactionType": "request", - "transactionsPerMinute": 0.9, + "transactionsPerMinute": 0.933333333333333, }, Object { - "averageResponseTime": 64824.2, - "impact": 0.302722617661906, + "averageResponseTime": 65035.8, + "impact": 0.285535040543522, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.product_customers", @@ -687,8 +687,8 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { - "averageResponseTime": 32155.5625, - "impact": 0.481425678843616, + "averageResponseTime": 30999.4705882353, + "impact": 0.463640986028375, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/stats", @@ -696,23 +696,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/stats", "transactionType": "request", - "transactionsPerMinute": 0.533333333333333, + "transactionsPerMinute": 0.566666666666667, }, Object { - "averageResponseTime": 32890.5714285714, - "impact": 0.646841098031782, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Customers/Get", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Customers/Get", - "transactionType": "request", - "transactionsPerMinute": 0.7, - }, - Object { - "averageResponseTime": 20133.0571428571, - "impact": 0.65994099517201, + "averageResponseTime": 20197.4, + "impact": 0.622424732781511, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/products/:id/customers", @@ -723,8 +711,8 @@ Array [ "transactionsPerMinute": 1.16666666666667, }, Object { - "averageResponseTime": 64534, - "impact": 0.725417951490748, + "averageResponseTime": 64681.6666666667, + "impact": 0.68355874339377, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.top_products", @@ -735,8 +723,20 @@ Array [ "transactionsPerMinute": 0.4, }, Object { - "averageResponseTime": 19259.8775510204, - "impact": 0.884368376654926, + "averageResponseTime": 41416.1428571429, + "impact": 0.766127739061111, + "key": Object { + "service.name": "opbeans-dotnet", + "transaction.name": "GET Customers/Get", + }, + "serviceName": "opbeans-dotnet", + "transactionName": "GET Customers/Get", + "transactionType": "request", + "transactionsPerMinute": 0.7, + }, + Object { + "averageResponseTime": 19429, + "impact": 0.821597646656097, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/products/:id", @@ -744,11 +744,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/products/:id", "transactionType": "request", - "transactionsPerMinute": 1.63333333333333, + "transactionsPerMinute": 1.6, }, Object { - "averageResponseTime": 62237.5652173913, - "impact": 1.3422123631976, + "averageResponseTime": 62390.652173913, + "impact": 1.26497653527507, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Products/Customerwhobought {id}", @@ -759,8 +759,8 @@ Array [ "transactionsPerMinute": 0.766666666666667, }, Object { - "averageResponseTime": 33203.5666666667, - "impact": 1.86860199568649, + "averageResponseTime": 33266.2, + "impact": 1.76006661931225, "key": Object { "service.name": "opbeans-python", "transaction.name": "opbeans.tasks.sync_orders", @@ -771,8 +771,8 @@ Array [ "transactionsPerMinute": 2, }, Object { - "averageResponseTime": 37042.1607142857, - "impact": 1.94571537801384, + "averageResponseTime": 38491.4444444444, + "impact": 1.83293391905112, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api", @@ -780,11 +780,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api", "transactionType": "request", - "transactionsPerMinute": 1.86666666666667, + "transactionsPerMinute": 1.8, }, Object { - "averageResponseTime": 116654.571428571, - "impact": 2.29809838682675, + "averageResponseTime": 118488.6, + "impact": 2.08995781717084, "key": Object { "service.name": "opbeans-dotnet", "transaction.name": "GET Stats/Get", @@ -792,35 +792,47 @@ Array [ "serviceName": "opbeans-dotnet", "transactionName": "GET Stats/Get", "transactionType": "request", - "transactionsPerMinute": 0.7, + "transactionsPerMinute": 0.666666666666667, }, Object { - "averageResponseTime": 28741.0333333333, - "impact": 2.4266538589631, + "averageResponseTime": 250440.142857143, + "impact": 4.64001412901584, "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Rack", + "service.name": "opbeans-dotnet", + "transaction.name": "GET Products/Top", }, - "serviceName": "opbeans-ruby", - "transactionName": "Rack", + "serviceName": "opbeans-dotnet", + "transactionName": "GET Products/Top", "transactionType": "request", - "transactionsPerMinute": 3, + "transactionsPerMinute": 0.7, }, Object { - "averageResponseTime": 249714.952380952, - "impact": 4.9211455657754, + "averageResponseTime": 312096.523809524, + "impact": 5.782704992387, "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Products/Top", + "service.name": "opbeans-java", + "transaction.name": "DispatcherServlet#doGet", }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Products/Top", + "serviceName": "opbeans-java", + "transactionName": "DispatcherServlet#doGet", "transactionType": "request", "transactionsPerMinute": 0.7, }, Object { - "averageResponseTime": 653461.538461538, - "impact": 7.97292501431132, + "averageResponseTime": 91519.7032967033, + "impact": 7.34855500859826, + "key": Object { + "service.name": "opbeans-ruby", + "transaction.name": "Rack", + }, + "serviceName": "opbeans-ruby", + "transactionName": "Rack", + "transactionType": "request", + "transactionsPerMinute": 3.03333333333333, + }, + Object { + "averageResponseTime": 648269.769230769, + "impact": 7.43611473386403, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/customers", @@ -831,20 +843,8 @@ Array [ "transactionsPerMinute": 0.433333333333333, }, Object { - "averageResponseTime": 575328.095238095, - "impact": 11.340025698891, - "key": Object { - "service.name": "opbeans-java", - "transaction.name": "DispatcherServlet#doGet", - }, - "serviceName": "opbeans-java", - "transactionName": "DispatcherServlet#doGet", - "transactionType": "request", - "transactionsPerMinute": 0.7, - }, - Object { - "averageResponseTime": 1534606.6, - "impact": 14.4041869205032, + "averageResponseTime": 1398919.72727273, + "impact": 13.5790895084132, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.products", @@ -852,11 +852,11 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.products", "transactionType": "request", - "transactionsPerMinute": 0.333333333333333, + "transactionsPerMinute": 0.366666666666667, }, Object { - "averageResponseTime": 1197500, - "impact": 15.7361746989891, + "averageResponseTime": 1199907.57142857, + "impact": 14.8239822181408, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/orders", @@ -867,8 +867,8 @@ Array [ "transactionsPerMinute": 0.466666666666667, }, Object { - "averageResponseTime": 953684.210526316, - "impact": 17.0081460802151, + "averageResponseTime": 955876.052631579, + "impact": 16.026822184214, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/products", @@ -879,8 +879,8 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { - "averageResponseTime": 962252.473684211, - "impact": 17.1609675746427, + "averageResponseTime": 965009.526315789, + "impact": 16.1799735991728, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/orders", @@ -891,8 +891,8 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { - "averageResponseTime": 1212615.38461538, - "impact": 29.594561046619, + "averageResponseTime": 1213675.30769231, + "impact": 27.8474053933734, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/dashboard", @@ -903,8 +903,8 @@ Array [ "transactionsPerMinute": 0.866666666666667, }, Object { - "averageResponseTime": 896783.309523809, - "impact": 35.3554170595149, + "averageResponseTime": 924019.363636364, + "impact": 35.8796065162284, "key": Object { "service.name": "opbeans-node", "transaction.name": "Update shipping status", @@ -912,11 +912,23 @@ Array [ "serviceName": "opbeans-node", "transactionName": "Update shipping status", "transactionType": "Worker", - "transactionsPerMinute": 1.4, + "transactionsPerMinute": 1.46666666666667, + }, + Object { + "averageResponseTime": 1060469.15384615, + "impact": 36.498655556576, + "key": Object { + "service.name": "opbeans-node", + "transaction.name": "Process payment", + }, + "serviceName": "opbeans-node", + "transactionName": "Process payment", + "transactionType": "Worker", + "transactionsPerMinute": 1.3, }, Object { - "averageResponseTime": 119062.672222222, - "impact": 40.2345894471584, + "averageResponseTime": 118686.822222222, + "impact": 37.7068083771466, "key": Object { "service.name": "opbeans-python", "transaction.name": "opbeans.tasks.update_stats", @@ -927,20 +939,8 @@ Array [ "transactionsPerMinute": 12, }, Object { - "averageResponseTime": 1078328.675, - "impact": 40.488594152833, - "key": Object { - "service.name": "opbeans-node", - "transaction.name": "Process payment", - }, - "serviceName": "opbeans-node", - "transactionName": "Process payment", - "transactionType": "Worker", - "transactionsPerMinute": 1.33333333333333, - }, - Object { - "averageResponseTime": 1057995.65957447, - "impact": 46.6772737502262, + "averageResponseTime": 1039228.27659574, + "impact": 43.1048035741496, "key": Object { "service.name": "opbeans-node", "transaction.name": "Process completed order", @@ -951,8 +951,8 @@ Array [ "transactionsPerMinute": 1.56666666666667, }, Object { - "averageResponseTime": 1947354.08333333, - "impact": 65.8074895815218, + "averageResponseTime": 1949922.55555556, + "impact": 61.9499776921889, "key": Object { "service.name": "opbeans-python", "transaction.name": "opbeans.tasks.sync_customers", @@ -963,7 +963,7 @@ Array [ "transactionsPerMinute": 1.2, }, Object { - "averageResponseTime": 5918288.44444444, + "averageResponseTime": 5963775, "impact": 100, "key": Object { "service.name": "opbeans-dotnet", @@ -972,7 +972,7 @@ Array [ "serviceName": "opbeans-dotnet", "transactionName": "GET Orders/Get", "transactionType": "request", - "transactionsPerMinute": 0.6, + "transactionsPerMinute": 0.633333333333333, }, ] `; diff --git a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts index e67c2cb66df698..4968732f822033 100644 --- a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts @@ -63,7 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(firstItem).toMatchInline(` Object { - "averageResponseTime": 1638, + "averageResponseTime": 1639, "impact": 0, "key": Object { "service.name": "opbeans-java", @@ -78,7 +78,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(lastItem).toMatchInline(` Object { - "averageResponseTime": 5918288.44444444, + "averageResponseTime": 5963775, "impact": 100, "key": Object { "service.name": "opbeans-dotnet", @@ -87,7 +87,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { "serviceName": "opbeans-dotnet", "transactionName": "GET Orders/Get", "transactionType": "request", - "transactionsPerMinute": 0.6, + "transactionsPerMinute": 0.633333333333333, } `); diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap index 878e5775c4ef52..c4dfaf346d0155 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap @@ -6,241 +6,121 @@ Array [ "x": 1627973400000, "y": 0, }, - Object { - "x": 1627973430000, - "y": 0, - }, Object { "x": 1627973460000, "y": 0, }, - Object { - "x": 1627973490000, - "y": 0.333333333333333, - }, Object { "x": 1627973520000, - "y": 0, - }, - Object { - "x": 1627973550000, - "y": 0.125, + "y": 0.333333333333333, }, Object { "x": 1627973580000, - "y": 0.25, - }, - Object { - "x": 1627973610000, - "y": 0, + "y": 0.181818181818182, }, Object { "x": 1627973640000, "y": 0, }, - Object { - "x": 1627973670000, - "y": 0, - }, Object { "x": 1627973700000, "y": 0, }, - Object { - "x": 1627973730000, - "y": 0.333333333333333, - }, Object { "x": 1627973760000, "y": 0.166666666666667, }, - Object { - "x": 1627973790000, - "y": 0, - }, Object { "x": 1627973820000, - "y": 0.2, - }, - Object { - "x": 1627973850000, - "y": 0, + "y": 0.181818181818182, }, Object { "x": 1627973880000, "y": 0, }, - Object { - "x": 1627973910000, - "y": 0, - }, Object { "x": 1627973940000, "y": 0, }, - Object { - "x": 1627973970000, - "y": 0.166666666666667, - }, Object { "x": 1627974000000, - "y": 0, - }, - Object { - "x": 1627974030000, - "y": 0, + "y": 0.0833333333333333, }, Object { "x": 1627974060000, - "y": 0.25, - }, - Object { - "x": 1627974090000, - "y": 0.5, + "y": 0.0769230769230769, }, Object { "x": 1627974120000, "y": 0, }, - Object { - "x": 1627974150000, - "y": 0, - }, Object { "x": 1627974180000, - "y": 0, - }, - Object { - "x": 1627974210000, - "y": 0.2, + "y": 0.1, }, Object { "x": 1627974240000, - "y": 0.142857142857143, - }, - Object { - "x": 1627974270000, - "y": 0, + "y": 0.153846153846154, }, Object { "x": 1627974300000, "y": 0, }, - Object { - "x": 1627974330000, - "y": null, - }, Object { "x": 1627974360000, "y": null, }, - Object { - "x": 1627974390000, - "y": 0, - }, Object { "x": 1627974420000, "y": 0, }, - Object { - "x": 1627974450000, - "y": 0, - }, Object { "x": 1627974480000, "y": 0, }, - Object { - "x": 1627974510000, - "y": 0, - }, Object { "x": 1627974540000, "y": 0, }, - Object { - "x": 1627974570000, - "y": 0, - }, Object { "x": 1627974600000, - "y": 0.4, - }, - Object { - "x": 1627974630000, - "y": 1, + "y": 0.125, }, Object { "x": 1627974660000, - "y": 0.333333333333333, - }, - Object { - "x": 1627974690000, - "y": 0.5, + "y": 0.6, }, Object { "x": 1627974720000, - "y": 0, - }, - Object { - "x": 1627974750000, - "y": 0, + "y": 0.2, }, Object { "x": 1627974780000, "y": 0, }, - Object { - "x": 1627974810000, - "y": 0, - }, Object { "x": 1627974840000, - "y": 0.333333333333333, - }, - Object { - "x": 1627974870000, "y": 0, }, Object { "x": 1627974900000, - "y": 0, - }, - Object { - "x": 1627974930000, - "y": 0, + "y": 0.0666666666666667, }, Object { "x": 1627974960000, "y": 0, }, - Object { - "x": 1627974990000, - "y": 0, - }, Object { "x": 1627975020000, "y": 0, }, - Object { - "x": 1627975050000, - "y": 0, - }, Object { "x": 1627975080000, "y": 0, }, - Object { - "x": 1627975110000, - "y": 0.4, - }, Object { "x": 1627975140000, - "y": 0, - }, - Object { - "x": 1627975170000, - "y": 0.333333333333333, + "y": 0.181818181818182, }, Object { "x": 1627975200000, @@ -252,736 +132,136 @@ Array [ exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate with comparison data has the correct error rate 1`] = ` Array [ Object { - "x": 1627974310000, - "y": null, - }, - Object { - "x": 1627974320000, - "y": null, - }, - Object { - "x": 1627974330000, - "y": null, - }, - Object { - "x": 1627974340000, - "y": null, - }, - Object { - "x": 1627974350000, - "y": null, + "x": 1627974300000, + "y": 0, }, Object { "x": 1627974360000, "y": null, }, - Object { - "x": 1627974370000, - "y": null, - }, - Object { - "x": 1627974380000, - "y": null, - }, - Object { - "x": 1627974390000, - "y": null, - }, - Object { - "x": 1627974400000, - "y": null, - }, - Object { - "x": 1627974410000, - "y": 0, - }, Object { "x": 1627974420000, "y": 0, }, - Object { - "x": 1627974430000, - "y": null, - }, - Object { - "x": 1627974440000, - "y": 0, - }, - Object { - "x": 1627974450000, - "y": 0, - }, - Object { - "x": 1627974460000, - "y": 0, - }, - Object { - "x": 1627974470000, - "y": null, - }, Object { "x": 1627974480000, "y": 0, }, Object { - "x": 1627974490000, - "y": null, - }, - Object { - "x": 1627974500000, + "x": 1627974540000, "y": 0, }, Object { - "x": 1627974510000, - "y": null, + "x": 1627974600000, + "y": 0.125, }, Object { - "x": 1627974520000, - "y": 0, + "x": 1627974660000, + "y": 0.6, }, Object { - "x": 1627974530000, - "y": null, + "x": 1627974720000, + "y": 0.2, }, Object { - "x": 1627974540000, + "x": 1627974780000, "y": 0, }, Object { - "x": 1627974550000, + "x": 1627974840000, "y": 0, }, Object { - "x": 1627974560000, - "y": null, + "x": 1627974900000, + "y": 0.0666666666666667, }, Object { - "x": 1627974570000, + "x": 1627974960000, "y": 0, }, Object { - "x": 1627974580000, - "y": null, - }, - Object { - "x": 1627974590000, - "y": null, - }, - Object { - "x": 1627974600000, - "y": null, - }, - Object { - "x": 1627974610000, + "x": 1627975020000, "y": 0, }, Object { - "x": 1627974620000, - "y": 1, - }, - Object { - "x": 1627974630000, - "y": null, - }, - Object { - "x": 1627974640000, - "y": null, - }, - Object { - "x": 1627974650000, - "y": 1, - }, - Object { - "x": 1627974660000, + "x": 1627975080000, "y": 0, }, Object { - "x": 1627974670000, - "y": 0.5, - }, - Object { - "x": 1627974680000, - "y": null, - }, - Object { - "x": 1627974690000, - "y": 1, - }, - Object { - "x": 1627974700000, - "y": null, - }, - Object { - "x": 1627974710000, - "y": 0, + "x": 1627975140000, + "y": 0.181818181818182, }, Object { - "x": 1627974720000, + "x": 1627975200000, "y": null, }, +] +`; + +exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate with comparison data has the correct error rate 2`] = ` +Array [ Object { - "x": 1627974730000, + "x": 1627974300000, "y": 0, }, Object { - "x": 1627974740000, + "x": 1627974360000, "y": 0, }, Object { - "x": 1627974750000, - "y": 0, + "x": 1627974420000, + "y": 0.333333333333333, }, Object { - "x": 1627974760000, - "y": null, + "x": 1627974480000, + "y": 0.181818181818182, }, Object { - "x": 1627974770000, + "x": 1627974540000, "y": 0, }, Object { - "x": 1627974780000, + "x": 1627974600000, "y": 0, }, Object { - "x": 1627974790000, - "y": 0, + "x": 1627974660000, + "y": 0.166666666666667, }, Object { - "x": 1627974800000, - "y": null, + "x": 1627974720000, + "y": 0.181818181818182, }, Object { - "x": 1627974810000, + "x": 1627974780000, "y": 0, }, - Object { - "x": 1627974820000, - "y": null, - }, - Object { - "x": 1627974830000, - "y": null, - }, Object { "x": 1627974840000, - "y": null, - }, - Object { - "x": 1627974850000, - "y": 0.5, - }, - Object { - "x": 1627974860000, - "y": 0, - }, - Object { - "x": 1627974870000, - "y": 0, - }, - Object { - "x": 1627974880000, - "y": null, - }, - Object { - "x": 1627974890000, "y": 0, }, Object { "x": 1627974900000, - "y": 0, - }, - Object { - "x": 1627974910000, - "y": 0, + "y": 0.0833333333333333, }, Object { - "x": 1627974920000, - "y": null, + "x": 1627974960000, + "y": 0.0769230769230769, }, Object { - "x": 1627974930000, + "x": 1627975020000, "y": 0, }, Object { - "x": 1627974940000, - "y": null, - }, - Object { - "x": 1627974950000, - "y": null, - }, - Object { - "x": 1627974960000, - "y": null, + "x": 1627975080000, + "y": 0.1, }, Object { - "x": 1627974970000, - "y": 0, - }, - Object { - "x": 1627974980000, - "y": null, - }, - Object { - "x": 1627974990000, - "y": 0, - }, - Object { - "x": 1627975000000, - "y": null, - }, - Object { - "x": 1627975010000, - "y": 0, - }, - Object { - "x": 1627975020000, - "y": 0, - }, - Object { - "x": 1627975030000, - "y": 0, - }, - Object { - "x": 1627975040000, - "y": null, - }, - Object { - "x": 1627975050000, - "y": 0, - }, - Object { - "x": 1627975060000, - "y": 0, - }, - Object { - "x": 1627975070000, - "y": 0, - }, - Object { - "x": 1627975080000, - "y": 0, - }, - Object { - "x": 1627975090000, - "y": 0, - }, - Object { - "x": 1627975100000, - "y": 0, - }, - Object { - "x": 1627975110000, - "y": 0.333333333333333, - }, - Object { - "x": 1627975120000, - "y": 0, - }, - Object { - "x": 1627975130000, - "y": 1, - }, - Object { - "x": 1627975140000, - "y": 0, - }, - Object { - "x": 1627975150000, - "y": 0, - }, - Object { - "x": 1627975160000, - "y": null, - }, - Object { - "x": 1627975170000, - "y": 0, - }, - Object { - "x": 1627975180000, - "y": 0.25, - }, - Object { - "x": 1627975190000, - "y": 1, - }, - Object { - "x": 1627975200000, - "y": null, - }, - Object { - "x": 1627975210000, - "y": null, - }, -] -`; - -exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate with comparison data has the correct error rate 2`] = ` -Array [ - Object { - "x": 1627974310000, - "y": null, - }, - Object { - "x": 1627974320000, - "y": 0, - }, - Object { - "x": 1627974330000, - "y": null, - }, - Object { - "x": 1627974340000, - "y": null, - }, - Object { - "x": 1627974350000, - "y": 0, - }, - Object { - "x": 1627974360000, - "y": 0, - }, - Object { - "x": 1627974370000, - "y": null, - }, - Object { - "x": 1627974380000, - "y": null, - }, - Object { - "x": 1627974390000, - "y": 0.5, - }, - Object { - "x": 1627974400000, - "y": null, - }, - Object { - "x": 1627974410000, - "y": 0, - }, - Object { - "x": 1627974420000, - "y": null, - }, - Object { - "x": 1627974430000, - "y": 0, - }, - Object { - "x": 1627974440000, - "y": null, - }, - Object { - "x": 1627974450000, - "y": 0.142857142857143, - }, - Object { - "x": 1627974460000, - "y": 0, - }, - Object { - "x": 1627974470000, - "y": null, - }, - Object { - "x": 1627974480000, - "y": 0.5, - }, - Object { - "x": 1627974490000, - "y": 0, - }, - Object { - "x": 1627974500000, - "y": null, - }, - Object { - "x": 1627974510000, - "y": null, - }, - Object { - "x": 1627974520000, - "y": null, - }, - Object { - "x": 1627974530000, - "y": 0, - }, - Object { - "x": 1627974540000, - "y": null, - }, - Object { - "x": 1627974550000, - "y": 0, - }, - Object { - "x": 1627974560000, - "y": null, - }, - Object { - "x": 1627974570000, - "y": 0, - }, - Object { - "x": 1627974580000, - "y": null, - }, - Object { - "x": 1627974590000, - "y": 0, - }, - Object { - "x": 1627974600000, - "y": 0, - }, - Object { - "x": 1627974610000, - "y": 0, - }, - Object { - "x": 1627974620000, - "y": null, - }, - Object { - "x": 1627974630000, - "y": null, - }, - Object { - "x": 1627974640000, - "y": 0, - }, - Object { - "x": 1627974650000, - "y": 0.5, - }, - Object { - "x": 1627974660000, - "y": 0, - }, - Object { - "x": 1627974670000, - "y": 0.5, - }, - Object { - "x": 1627974680000, - "y": 0, - }, - Object { - "x": 1627974690000, - "y": 0, - }, - Object { - "x": 1627974700000, - "y": 0, - }, - Object { - "x": 1627974710000, - "y": 0, - }, - Object { - "x": 1627974720000, - "y": 0.25, - }, - Object { - "x": 1627974730000, - "y": null, - }, - Object { - "x": 1627974740000, - "y": 0, - }, - Object { - "x": 1627974750000, - "y": 0, - }, - Object { - "x": 1627974760000, - "y": 0, - }, - Object { - "x": 1627974770000, - "y": 0, - }, - Object { - "x": 1627974780000, - "y": 0, - }, - Object { - "x": 1627974790000, - "y": 0, - }, - Object { - "x": 1627974800000, - "y": 0, - }, - Object { - "x": 1627974810000, - "y": 0, - }, - Object { - "x": 1627974820000, - "y": 0, - }, - Object { - "x": 1627974830000, - "y": null, - }, - Object { - "x": 1627974840000, - "y": 0, - }, - Object { - "x": 1627974850000, - "y": 0, - }, - Object { - "x": 1627974860000, - "y": null, - }, - Object { - "x": 1627974870000, - "y": 0.5, - }, - Object { - "x": 1627974880000, - "y": null, - }, - Object { - "x": 1627974890000, - "y": 0, - }, - Object { - "x": 1627974900000, - "y": 0, - }, - Object { - "x": 1627974910000, - "y": 0, - }, - Object { - "x": 1627974920000, - "y": 0, - }, - Object { - "x": 1627974930000, - "y": 0, - }, - Object { - "x": 1627974940000, - "y": 0, - }, - Object { - "x": 1627974950000, - "y": 0, - }, - Object { - "x": 1627974960000, - "y": 0.333333333333333, - }, - Object { - "x": 1627974970000, - "y": 0, - }, - Object { - "x": 1627974980000, - "y": null, - }, - Object { - "x": 1627974990000, - "y": 0, - }, - Object { - "x": 1627975000000, - "y": 1, - }, - Object { - "x": 1627975010000, - "y": null, - }, - Object { - "x": 1627975020000, - "y": 0, - }, - Object { - "x": 1627975030000, - "y": 0, - }, - Object { - "x": 1627975040000, - "y": 0, - }, - Object { - "x": 1627975050000, - "y": 0, - }, - Object { - "x": 1627975060000, - "y": 0, - }, - Object { - "x": 1627975070000, - "y": null, - }, - Object { - "x": 1627975080000, - "y": 0, - }, - Object { - "x": 1627975090000, - "y": 0, - }, - Object { - "x": 1627975100000, - "y": 0, - }, - Object { - "x": 1627975110000, - "y": 0, - }, - Object { - "x": 1627975120000, - "y": 0, - }, - Object { - "x": 1627975130000, - "y": 0.333333333333333, - }, - Object { - "x": 1627975140000, - "y": 0.333333333333333, - }, - Object { - "x": 1627975150000, - "y": 0, - }, - Object { - "x": 1627975160000, - "y": null, - }, - Object { - "x": 1627975170000, - "y": null, - }, - Object { - "x": 1627975180000, - "y": 0, - }, - Object { - "x": 1627975190000, - "y": 0, + "x": 1627975140000, + "y": 0.153846153846154, }, Object { "x": 1627975200000, - "y": 0, - }, - Object { - "x": 1627975210000, "y": null, }, ] diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap index 25b995acb87f14..ff9e52b57aeaa0 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -3,196 +3,64 @@ exports[`APM API tests basic apm_8.0.0 Latency with a basic license when data is loaded time comparison returns some data 1`] = ` Array [ Object { - "x": 1627974350000, - "y": 32153, + "x": 1627974300000, + "y": 22799, }, Object { - "x": 1627974390000, - "y": 15341.3333333333, - }, - Object { - "x": 1627974410000, - "y": 15170.5, + "x": 1627974360000, + "y": 3227391, }, Object { "x": 1627974420000, - "y": 17329, - }, - Object { - "x": 1627974460000, - "y": 50039, + "y": 15565.2222222222, }, Object { "x": 1627974480000, - "y": 55816.6, - }, - Object { - "x": 1627974490000, - "y": 17533, - }, - Object { - "x": 1627974510000, - "y": 14888, + "y": 54307.5714285714, }, Object { "x": 1627974540000, - "y": 12191.5, - }, - Object { - "x": 1627974550000, - "y": 20445.5, - }, - Object { - "x": 1627974590000, - "y": 9859, + "y": 16655, }, Object { "x": 1627974600000, - "y": 11796, - }, - Object { - "x": 1627974610000, - "y": 8774.4, - }, - Object { - "x": 1627974620000, - "y": 42225.6666666667, - }, - Object { - "x": 1627974630000, - "y": 16168, - }, - Object { - "x": 1627974640000, - "y": 7851.33333333333, + "y": 9453, }, Object { "x": 1627974660000, - "y": 45852, - }, - Object { - "x": 1627974700000, - "y": 21823, - }, - Object { - "x": 1627974710000, - "y": 4156, - }, - Object { - "x": 1627974730000, - "y": 14191.5, - }, - Object { - "x": 1627974740000, - "y": 3997, - }, - Object { - "x": 1627974750000, - "y": 21823.75, - }, - Object { - "x": 1627974760000, - "y": 58178.5, + "y": 31119, }, Object { - "x": 1627974770000, - "y": 24291.5, + "x": 1627974720000, + "y": 15282.2, }, Object { "x": 1627974780000, - "y": 7527.75, + "y": 18709, }, Object { "x": 1627974840000, - "y": 12028, - }, - Object { - "x": 1627974860000, - "y": 6773.8, + "y": 12095, }, Object { - "x": 1627974870000, - "y": 37030, - }, - Object { - "x": 1627974890000, - "y": 29564, - }, - Object { - "x": 1627974910000, - "y": 15606, - }, - Object { - "x": 1627974930000, - "y": 11747, - }, - Object { - "x": 1627974940000, - "y": 9425, - }, - Object { - "x": 1627974950000, - "y": 20220, - }, - Object { - "x": 1627975000000, - "y": 12995.5, - }, - Object { - "x": 1627975030000, - "y": 13606, - }, - Object { - "x": 1627975050000, - "y": 22030, + "x": 1627974900000, + "y": 16291, }, Object { - "x": 1627975060000, - "y": 10819, + "x": 1627974960000, + "y": 13444.3333333333, }, Object { - "x": 1627975070000, - "y": 26343, + "x": 1627975020000, + "y": 13241.6666666667, }, Object { "x": 1627975080000, - "y": 33080.5, + "y": 25535, }, Object { - "x": 1627975090000, - "y": 11899, - }, - Object { - "x": 1627975100000, - "y": 5253, - }, - Object { - "x": 1627975110000, - "y": 16502, - }, - Object { - "x": 1627975120000, - "y": 6945.5, - }, - Object { - "x": 1627975130000, - "y": 7244, - }, - Object { - "x": 1627975150000, - "y": 22631.5, - }, - Object { - "x": 1627975180000, - "y": 23489, - }, - Object { - "x": 1627975190000, - "y": 10133.3333333333, - }, - Object { - "x": 1627975210000, - "y": 52108, + "x": 1627975140000, + "y": 11024.6, }, ] `; @@ -200,200 +68,64 @@ Array [ exports[`APM API tests basic apm_8.0.0 Latency with a basic license when data is loaded time comparison returns some data 2`] = ` Array [ Object { - "x": 1627974310000, - "y": 107053.5, - }, - Object { - "x": 1627974370000, - "y": 9857, - }, - Object { - "x": 1627974380000, - "y": 266341, - }, - Object { - "x": 1627974390000, - "y": 11715.75, - }, - Object { - "x": 1627974410000, - "y": 7805.25, - }, - Object { - "x": 1627974430000, - "y": 15880, + "x": 1627974300000, + "y": 34866.2, }, Object { - "x": 1627974460000, - "y": 21836, + "x": 1627974360000, + "y": 104799, }, Object { - "x": 1627974470000, - "y": 23962, + "x": 1627974420000, + "y": 36247, }, Object { "x": 1627974480000, - "y": 21352.5, - }, - Object { - "x": 1627974490000, - "y": 21639, - }, - Object { - "x": 1627974530000, - "y": 13970, - }, - Object { - "x": 1627974550000, - "y": 58140, - }, - Object { - "x": 1627974570000, - "y": 9853.75, + "y": 22207, }, Object { - "x": 1627974580000, - "y": 6490, - }, - Object { - "x": 1627974590000, - "y": 18894, - }, - Object { - "x": 1627974610000, - "y": 14125, + "x": 1627974540000, + "y": 80191, }, Object { - "x": 1627974640000, - "y": 86268.25, + "x": 1627974600000, + "y": 11520.4545454545, }, Object { "x": 1627974660000, - "y": 14218, - }, - Object { - "x": 1627974670000, - "y": 19127, - }, - Object { - "x": 1627974680000, - "y": 31538, + "y": 47031.8888888889, }, Object { "x": 1627974720000, - "y": 29535, - }, - Object { - "x": 1627974740000, - "y": 11229, - }, - Object { - "x": 1627974750000, - "y": 23940, + "y": 30249.6666666667, }, Object { - "x": 1627974760000, - "y": 9262, - }, - Object { - "x": 1627974790000, - "y": 15650, + "x": 1627974780000, + "y": 14868.3333333333, }, Object { "x": 1627974840000, - "y": 17656.3333333333, - }, - Object { - "x": 1627974880000, - "y": 8371.5, - }, - Object { - "x": 1627974890000, - "y": 11386.5, + "y": 17199, }, Object { "x": 1627974900000, - "y": 28923.75, - }, - Object { - "x": 1627974910000, - "y": 22670, - }, - Object { - "x": 1627974920000, - "y": 13607.6666666667, - }, - Object { - "x": 1627974930000, - "y": 19640, - }, - Object { - "x": 1627974940000, - "y": 20511, + "y": 19837.2222222222, }, Object { "x": 1627974960000, - "y": 34862, - }, - Object { - "x": 1627974990000, - "y": 27929.2, + "y": 19397.6666666667, }, Object { "x": 1627975020000, - "y": 25569, - }, - Object { - "x": 1627975030000, - "y": 6817.33333333333, - }, - Object { - "x": 1627975040000, - "y": 10467.6666666667, - }, - Object { - "x": 1627975060000, - "y": 6754.33333333333, - }, - Object { - "x": 1627975070000, - "y": 22049, + "y": 22473.6666666667, }, Object { "x": 1627975080000, - "y": 15029, - }, - Object { - "x": 1627975090000, - "y": 14744, - }, - Object { - "x": 1627975110000, - "y": 32192.3333333333, - }, - Object { - "x": 1627975130000, - "y": 8321, - }, - Object { - "x": 1627975160000, - "y": 11648, - }, - Object { - "x": 1627975170000, - "y": 13157, - }, - Object { - "x": 1627975190000, - "y": 12855, - }, - Object { - "x": 1627975200000, - "y": 1322026.8, + "y": 11362.2, }, Object { - "x": 1627975210000, - "y": 4650.33333333333, + "x": 1627975140000, + "y": 26319, }, ] `; diff --git a/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts index 89f818f58e875f..aeb7cc61a582a2 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts @@ -38,8 +38,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { const body = response.body as ErrorRate; expect(body).to.be.eql({ - currentPeriod: { noHits: true, transactionErrorRate: [], average: null }, - previousPeriod: { noHits: true, transactionErrorRate: [], average: null }, + currentPeriod: { timeseries: [], average: null }, + previousPeriod: { timeseries: [], average: null }, }); }); @@ -62,8 +62,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { const body = response.body as ErrorRate; expect(body).to.be.eql({ - currentPeriod: { noHits: true, transactionErrorRate: [], average: null }, - previousPeriod: { noHits: true, transactionErrorRate: [], average: null }, + currentPeriod: { timeseries: [], average: null }, + previousPeriod: { timeseries: [], average: null }, }); }); }); @@ -89,10 +89,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0); expect(errorRateResponse.previousPeriod.average).to.be(null); - expect(errorRateResponse.currentPeriod.transactionErrorRate.length).to.be.greaterThan(0); - expect(errorRateResponse.previousPeriod.transactionErrorRate).to.empty(); + expect(errorRateResponse.currentPeriod.timeseries.length).to.be.greaterThan(0); + expect(errorRateResponse.previousPeriod.timeseries).to.empty(); - const nonNullDataPoints = errorRateResponse.currentPeriod.transactionErrorRate.filter( + const nonNullDataPoints = errorRateResponse.currentPeriod.timeseries.filter( ({ y }) => y !== null ); @@ -101,34 +101,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('has the correct start date', () => { expectSnapshot( - new Date( - first(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN - ).toISOString() + new Date(first(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() ).toMatchInline(`"2021-08-03T06:50:00.000Z"`); }); it('has the correct end date', () => { expectSnapshot( - new Date( - last(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN - ).toISOString() + new Date(last(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() ).toMatchInline(`"2021-08-03T07:20:00.000Z"`); }); it('has the correct number of buckets', () => { - expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate.length).toMatchInline( - `61` - ); + expectSnapshot(errorRateResponse.currentPeriod.timeseries.length).toMatchInline(`31`); }); it('has the correct calculation for average', () => { expectSnapshot(errorRateResponse.currentPeriod.average).toMatchInline( - `0.092511013215859` + `0.0848214285714286` ); }); it('has the correct error rate', () => { - expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate).toMatch(); + expectSnapshot(errorRateResponse.currentPeriod.timeseries).toMatch(); }); }); @@ -157,14 +151,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0); expect(errorRateResponse.previousPeriod.average).to.be.greaterThan(0); - expect(errorRateResponse.currentPeriod.transactionErrorRate.length).to.be.greaterThan(0); - expect(errorRateResponse.previousPeriod.transactionErrorRate.length).to.be.greaterThan(0); + expect(errorRateResponse.currentPeriod.timeseries.length).to.be.greaterThan(0); + expect(errorRateResponse.previousPeriod.timeseries.length).to.be.greaterThan(0); - const currentPeriodNonNullDataPoints = - errorRateResponse.currentPeriod.transactionErrorRate.filter(({ y }) => y !== null); + const currentPeriodNonNullDataPoints = errorRateResponse.currentPeriod.timeseries.filter( + ({ y }) => y !== null + ); const previousPeriodNonNullDataPoints = - errorRateResponse.previousPeriod.transactionErrorRate.filter(({ y }) => y !== null); + errorRateResponse.previousPeriod.timeseries.filter(({ y }) => y !== null); expect(currentPeriodNonNullDataPoints.length).to.be.greaterThan(0); expect(previousPeriodNonNullDataPoints.length).to.be.greaterThan(0); @@ -172,56 +167,44 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('has the correct start date', () => { expectSnapshot( - new Date( - first(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN - ).toISOString() - ).toMatchInline(`"2021-08-03T07:05:10.000Z"`); + new Date(first(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() + ).toMatchInline(`"2021-08-03T07:05:00.000Z"`); expectSnapshot( - new Date( - first(errorRateResponse.previousPeriod.transactionErrorRate)?.x ?? NaN - ).toISOString() - ).toMatchInline(`"2021-08-03T07:05:10.000Z"`); + new Date(first(errorRateResponse.previousPeriod.timeseries)?.x ?? NaN).toISOString() + ).toMatchInline(`"2021-08-03T07:05:00.000Z"`); }); it('has the correct end date', () => { expectSnapshot( - new Date( - last(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN - ).toISOString() - ).toMatchInline(`"2021-08-03T07:20:10.000Z"`); + new Date(last(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() + ).toMatchInline(`"2021-08-03T07:20:00.000Z"`); expectSnapshot( - new Date( - last(errorRateResponse.previousPeriod.transactionErrorRate)?.x ?? NaN - ).toISOString() - ).toMatchInline(`"2021-08-03T07:20:10.000Z"`); + new Date(last(errorRateResponse.previousPeriod.timeseries)?.x ?? NaN).toISOString() + ).toMatchInline(`"2021-08-03T07:20:00.000Z"`); }); it('has the correct number of buckets', () => { - expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate.length).toMatchInline( - `91` - ); - expectSnapshot( - errorRateResponse.previousPeriod.transactionErrorRate.length - ).toMatchInline(`91`); + expectSnapshot(errorRateResponse.currentPeriod.timeseries.length).toMatchInline(`16`); + expectSnapshot(errorRateResponse.previousPeriod.timeseries.length).toMatchInline(`16`); }); it('has the correct calculation for average', () => { expectSnapshot(errorRateResponse.currentPeriod.average).toMatchInline( - `0.102040816326531` + `0.0792079207920792` ); expectSnapshot(errorRateResponse.previousPeriod.average).toMatchInline( - `0.0852713178294574` + `0.0894308943089431` ); }); it('has the correct error rate', () => { - expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate).toMatch(); - expectSnapshot(errorRateResponse.previousPeriod.transactionErrorRate).toMatch(); + expectSnapshot(errorRateResponse.currentPeriod.timeseries).toMatch(); + expectSnapshot(errorRateResponse.previousPeriod.timeseries).toMatch(); }); it('matches x-axis on current period and previous period', () => { - expect(errorRateResponse.currentPeriod.transactionErrorRate.map(({ x }) => x)).to.be.eql( - errorRateResponse.previousPeriod.transactionErrorRate.map(({ x }) => x) + expect(errorRateResponse.currentPeriod.timeseries.map(({ x }) => x)).to.be.eql( + errorRateResponse.previousPeriod.timeseries.map(({ x }) => x) ); }); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts index d021e59ecff6ca..beaff7647868a5 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -113,7 +113,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); const latencyChartReturn = response.body as LatencyChartReturnType; expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); - expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(31); }); }); @@ -138,7 +138,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); const latencyChartReturn = response.body as LatencyChartReturnType; expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); - expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(31); }); }); @@ -165,10 +165,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); expectSnapshot(latencyChartReturn.currentPeriod.overallAvgDuration).toMatchInline( - `53147.5747663551` + `53906.6603773585` ); - expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(31); }); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts index fe4058004691b8..e877afc0700506 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts @@ -76,175 +76,181 @@ export default function ApiTest({ getService }: FtrProviderContext) { } ); - registry.when('data is loaded', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { - describe('transactions groups detailed stats', () => { - const GO_PROD_RATE = 75; - const GO_PROD_ERROR_RATE = 25; - before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); - - const transactionName = 'GET /api/product/list'; - - await synthtraceEsClient.index([ - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction(transactionName) - .timestamp(timestamp) - .duration(1000) - .success() - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_ERROR_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction(transactionName) - .duration(1000) - .timestamp(timestamp) - .failure() - .serialize() - ), - ]); - }); + registry.when( + 'data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('transactions groups detailed stats', () => { + const GO_PROD_RATE = 75; + const GO_PROD_ERROR_RATE = 25; + before(async () => { + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( + 'instance-a' + ); - after(() => synthtraceEsClient.clean()); + const transactionName = 'GET /api/product/list'; - describe('without comparisons', () => { - let transactionsStatistics: TransactionsGroupsDetailedStatistics; - let metricsStatistics: TransactionsGroupsDetailedStatistics; - before(async () => { - [metricsStatistics, transactionsStatistics] = await Promise.all([ - callApi({ query: { kuery: 'processor.event : "metric"' } }), - callApi({ query: { kuery: 'processor.event : "transaction"' } }), + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionName) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_ERROR_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionName) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), ]); }); - it('returns some transactions data', () => { - expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); - }); + after(() => synthtraceEsClient.clean()); - it('returns some metrics data', () => { - expect(isEmpty(metricsStatistics.currentPeriod)).to.be.equal(false); - }); + describe('without comparisons', () => { + let transactionsStatistics: TransactionsGroupsDetailedStatistics; + let metricsStatistics: TransactionsGroupsDetailedStatistics; + before(async () => { + [metricsStatistics, transactionsStatistics] = await Promise.all([ + callApi({ query: { kuery: 'processor.event : "metric"' } }), + callApi({ query: { kuery: 'processor.event : "transaction"' } }), + ]); + }); - it('has same latency mean value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; - const transactionsLatencyMean = meanBy(transactionsCurrentPeriod.latency, 'y'); - const metricsLatencyMean = meanBy(metricsCurrentPeriod.latency, 'y'); - [transactionsLatencyMean, metricsLatencyMean].forEach((value) => - expect(value).to.be.equal(1000000) - ); - }); + it('returns some transactions data', () => { + expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); + }); - it('has same error rate mean value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + it('returns some metrics data', () => { + expect(isEmpty(metricsStatistics.currentPeriod)).to.be.equal(false); + }); - const transactionsErrorRateMean = meanBy(transactionsCurrentPeriod.errorRate, 'y'); - const metricsErrorRateMean = meanBy(metricsCurrentPeriod.errorRate, 'y'); - [transactionsErrorRateMean, metricsErrorRateMean].forEach((value) => - expect(asPercent(value, 1)).to.be.equal(`${GO_PROD_ERROR_RATE}%`) - ); - }); + it('has same latency mean value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + const transactionsLatencyMean = meanBy(transactionsCurrentPeriod.latency, 'y'); + const metricsLatencyMean = meanBy(metricsCurrentPeriod.latency, 'y'); + [transactionsLatencyMean, metricsLatencyMean].forEach((value) => + expect(value).to.be.equal(1000000) + ); + }); - it('has same throughput mean value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; - const transactionsThroughputMean = roundNumber( - meanBy(transactionsCurrentPeriod.throughput, 'y') - ); - const metricsThroughputMean = roundNumber(meanBy(metricsCurrentPeriod.throughput, 'y')); - [transactionsThroughputMean, metricsThroughputMean].forEach((value) => - expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_PROD_ERROR_RATE)) - ); - }); + it('has same error rate mean value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; - it('has same impact value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + const transactionsErrorRateMean = meanBy(transactionsCurrentPeriod.errorRate, 'y'); + const metricsErrorRateMean = meanBy(metricsCurrentPeriod.errorRate, 'y'); + [transactionsErrorRateMean, metricsErrorRateMean].forEach((value) => + expect(asPercent(value, 1)).to.be.equal(`${GO_PROD_ERROR_RATE}%`) + ); + }); - const transactionsImpact = transactionsCurrentPeriod.impact; - const metricsImpact = metricsCurrentPeriod.impact; - [transactionsImpact, metricsImpact].forEach((value) => expect(value).to.be.equal(100)); - }); - }); + it('has same throughput mean value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + const transactionsThroughputMean = roundNumber( + meanBy(transactionsCurrentPeriod.throughput, 'y') + ); + const metricsThroughputMean = roundNumber(meanBy(metricsCurrentPeriod.throughput, 'y')); + [transactionsThroughputMean, metricsThroughputMean].forEach((value) => + expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_PROD_ERROR_RATE)) + ); + }); - describe('with comparisons', () => { - let transactionsStatistics: TransactionsGroupsDetailedStatistics; - before(async () => { - transactionsStatistics = await callApi({ - query: { - start: moment(end).subtract(7, 'minutes').toISOString(), - end: new Date(end).toISOString(), - comparisonStart: new Date(start).toISOString(), - comparisonEnd: moment(start).add(7, 'minutes').toISOString(), - }, + it('has same impact value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + + const transactionsImpact = transactionsCurrentPeriod.impact; + const metricsImpact = metricsCurrentPeriod.impact; + [transactionsImpact, metricsImpact].forEach((value) => expect(value).to.be.equal(100)); }); }); - it('returns some data for both periods', () => { - expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); - expect(isEmpty(transactionsStatistics.previousPeriod)).to.be.equal(false); - }); + describe('with comparisons', () => { + let transactionsStatistics: TransactionsGroupsDetailedStatistics; + before(async () => { + transactionsStatistics = await callApi({ + query: { + start: moment(end).subtract(7, 'minutes').toISOString(), + end: new Date(end).toISOString(), + comparisonStart: new Date(start).toISOString(), + comparisonEnd: moment(start).add(7, 'minutes').toISOString(), + }, + }); + }); - it('has same start time for both periods', () => { - const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; - const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; - [ - [currentPeriod.latency, previousPeriod.latency], - [currentPeriod.errorRate, previousPeriod.errorRate], - [currentPeriod.throughput, previousPeriod.throughput], - ].forEach(([currentTimeseries, previousTimeseries]) => { - const firstCurrentPeriodDate = new Date( - first(currentTimeseries)?.x ?? NaN - ).toISOString(); - const firstPreviousPeriodDate = new Date( - first(previousPeriod.latency)?.x ?? NaN - ).toISOString(); - - expect(firstCurrentPeriodDate).to.equal(firstPreviousPeriodDate); + it('returns some data for both periods', () => { + expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); + expect(isEmpty(transactionsStatistics.previousPeriod)).to.be.equal(false); }); - }); - it('has same end time for both periods', () => { - const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; - const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; - [ - [currentPeriod.latency, previousPeriod.latency], - [currentPeriod.errorRate, previousPeriod.errorRate], - [currentPeriod.throughput, previousPeriod.throughput], - ].forEach(([currentTimeseries, previousTimeseries]) => { - const lastCurrentPeriodDate = new Date(last(currentTimeseries)?.x ?? NaN).toISOString(); - const lastPreviousPeriodDate = new Date( - last(previousPeriod.latency)?.x ?? NaN - ).toISOString(); - - expect(lastCurrentPeriodDate).to.equal(lastPreviousPeriodDate); + + it('has same start time for both periods', () => { + const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; + const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; + [ + [currentPeriod.latency, previousPeriod.latency], + [currentPeriod.errorRate, previousPeriod.errorRate], + [currentPeriod.throughput, previousPeriod.throughput], + ].forEach(([currentTimeseries, previousTimeseries]) => { + const firstCurrentPeriodDate = new Date( + first(currentTimeseries)?.x ?? NaN + ).toISOString(); + const firstPreviousPeriodDate = new Date( + first(previousPeriod.latency)?.x ?? NaN + ).toISOString(); + + expect(firstCurrentPeriodDate).to.equal(firstPreviousPeriodDate); + }); }); - }); + it('has same end time for both periods', () => { + const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; + const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; + [ + [currentPeriod.latency, previousPeriod.latency], + [currentPeriod.errorRate, previousPeriod.errorRate], + [currentPeriod.throughput, previousPeriod.throughput], + ].forEach(([currentTimeseries, previousTimeseries]) => { + const lastCurrentPeriodDate = new Date( + last(currentTimeseries)?.x ?? NaN + ).toISOString(); + const lastPreviousPeriodDate = new Date( + last(previousPeriod.latency)?.x ?? NaN + ).toISOString(); - it('returns same number of buckets for both periods', () => { - const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; - const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; - [ - [currentPeriod.latency, previousPeriod.latency], - [currentPeriod.errorRate, previousPeriod.errorRate], - [currentPeriod.throughput, previousPeriod.throughput], - ].forEach(([currentTimeseries, previousTimeseries]) => { - expect(currentTimeseries.length).to.equal(previousTimeseries.length); + expect(lastCurrentPeriodDate).to.equal(lastPreviousPeriodDate); + }); + }); + + it('returns same number of buckets for both periods', () => { + const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; + const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; + [ + [currentPeriod.latency, previousPeriod.latency], + [currentPeriod.errorRate, previousPeriod.errorRate], + [currentPeriod.throughput, previousPeriod.throughput], + ].forEach(([currentTimeseries, previousTimeseries]) => { + expect(currentTimeseries.length).to.equal(previousTimeseries.length); + }); }); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts index f95d153b1c96ee..7664d28271e14b 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts @@ -98,18 +98,18 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expectSnapshot(impacts).toMatchInline(` Array [ - 98.5616469236242, - 0.088146942911198, - 0.208815627929649, - 0.189536811278812, - 0.110293217369968, - 0.191163512620049, - 0.0899742946381385, - 0.341831477754056, - 0.0411384477014597, - 0.0652338973356331, - 0.109023796562458, - 0.00319505027438735, + 98.4867713293593, + 0.0910992862692518, + 0.217172932411727, + 0.197499651612207, + 0.117088451625813, + 0.203168003440319, + 0.0956764857936742, + 0.353287132108131, + 0.043688393280619, + 0.0754467823979389, + 0.115710953190738, + 0.00339059851027124, ] `); @@ -120,9 +120,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(pick(firstItem, 'name', 'latency', 'throughput', 'errorRate', 'impact')) .toMatchInline(` Object { - "errorRate": 0.1, - "impact": 98.5616469236242, - "latency": 1925546.54, + "errorRate": 0.08, + "impact": 98.4867713293593, + "latency": 1816019.48, "name": "DispatcherServlet#doGet", "throughput": 1.66666666666667, } @@ -150,7 +150,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { response.body as TransactionsGroupsPrimaryStatistics; const firstItem = transctionsGroupsPrimaryStatistics.transactionGroups[0]; - expectSnapshot(firstItem.latency).toMatchInline(`66836803`); + expectSnapshot(firstItem.latency).toMatchInline(`66846719`); }); } ); diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts index d42d0c9328e30a..559ada3f891bc2 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts @@ -67,6 +67,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); + const log = getService('log'); describe('patch_cases', () => { afterEach(async () => { @@ -792,12 +793,12 @@ export default ({ getService }: FtrProviderContext): void => { describe('detections rule', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); }); @@ -805,10 +806,10 @@ export default ({ getService }: FtrProviderContext): void => { const rule = getRuleForSignalTesting(['auditbeat-*']); const postedCase = await createCase(supertest, postCaseReq); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -865,10 +866,10 @@ export default ({ getService }: FtrProviderContext): void => { settings: { syncAlerts: false }, }); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -918,10 +919,10 @@ export default ({ getService }: FtrProviderContext): void => { settings: { syncAlerts: false }, }); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -987,10 +988,10 @@ export default ({ getService }: FtrProviderContext): void => { const rule = getRuleForSignalTesting(['auditbeat-*']); const postedCase = await createCase(supertest, postCaseReq); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/comments/post_comment.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/comments/post_comment.ts index 78a163224f0470..6c8acb3bbc3b39 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/comments/post_comment.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/comments/post_comment.ts @@ -69,6 +69,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); + const log = getService('log'); describe('post_comment', () => { afterEach(async () => { @@ -339,12 +340,12 @@ export default ({ getService }: FtrProviderContext): void => { describe('alerts', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); }); @@ -366,10 +367,10 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -421,10 +422,10 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/connectors/case.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/connectors/case.ts index 41d8cc12aaaabe..7d5c8ec2c4f142 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/connectors/case.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/connectors/case.ts @@ -31,6 +31,7 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('case_connector', () => { let createdActionId = ''; @@ -708,21 +709,21 @@ export default ({ getService }: FtrProviderContext): void => { describe('adding alerts using a connector', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); }); it('should add a comment of type alert', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; const { body: createdAction } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts index 468991ea14c141..8db3d9797b6df7 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts @@ -23,32 +23,37 @@ import { export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); + const log = getService('log'); describe('add_prepackaged_rules', () => { describe('creating prepackaged rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest); + await deleteAllAlerts(supertest, log); await deleteAllTimelines(es); }); it('should create the prepackaged rules and return a count greater than zero, rules_updated to be zero, and contain the correct keys', async () => { let responseBody: unknown; - await waitFor(async () => { - const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send(); - if (status === 200) { - responseBody = body; - } - return status === 200; - }, DETECTION_ENGINE_PREPACKAGED_URL); + await waitFor( + async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, + DETECTION_ENGINE_PREPACKAGED_URL, + log + ); const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; expect(prepackagedRules.rules_installed).to.be.greaterThan(0); @@ -62,29 +67,37 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should be possible to call the API twice and the second time the number of rules installed should be zero as well as timeline', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. // This is to reduce flakiness where it can for a short period of time try to install the same rule twice. - await waitFor(async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) - .set('kbn-xsrf', 'true') - .expect(200); - return body.rules_not_installed === 0; - }, `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`); + await waitFor( + async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.rules_not_installed === 0; + }, + `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, + log + ); let responseBody: unknown; - await waitFor(async () => { - const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send(); - if (status === 200) { - responseBody = body; - } - return status === 200; - }, DETECTION_ENGINE_PREPACKAGED_URL); + await waitFor( + async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, + DETECTION_ENGINE_PREPACKAGED_URL, + log + ); const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; expect(prepackagedRules.rules_installed).to.eql(0); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index b20e70497291ad..fa78fdd5fe04eb 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -27,6 +27,7 @@ import { export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); + const log = getService('log'); describe('create_rules', () => { describe('creating rules', () => { @@ -39,12 +40,12 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts index 340358be514138..b54e1432f5463b 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts @@ -25,6 +25,7 @@ import { export default ({ getService }: FtrProviderContext): void => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); + const log = getService('log'); describe('create_rules_bulk', () => { describe('creating rules in bulk', () => { @@ -37,12 +38,12 @@ export default ({ getService }: FtrProviderContext): void => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts index 3750a8eed394b0..e5f828d0f862d4 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts @@ -25,20 +25,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_rules', () => { describe('deleting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should delete a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // delete the rule by its rule_id const { body } = await supertest @@ -51,7 +52,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); // delete that rule by its auto-generated rule_id const { body } = await supertest @@ -64,7 +65,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRule()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); // delete that rule by its auto-generated id const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts index e978f871b55c25..b7517697ad2a94 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts @@ -25,20 +25,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_rules_bulk', () => { describe('deleting rules bulk using DELETE', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should delete a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); // delete the rule in bulk const { body } = await supertest @@ -52,7 +53,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); // delete that rule by its rule_id const { body } = await supertest @@ -66,7 +67,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRule()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); // delete that rule by its id const { body } = await supertest @@ -116,7 +117,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id but give an error if the second rule does not exist', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) @@ -141,16 +142,16 @@ export default ({ getService }: FtrProviderContext): void => { // This is a repeat of the tests above but just using POST instead of DELETE describe('deleting rules bulk using POST', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should delete a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); // delete the rule in bulk const { body } = await supertest @@ -164,7 +165,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); // delete that rule by its rule_id const { body } = await supertest @@ -178,7 +179,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRule()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); // delete that rule by its id const { body } = await supertest @@ -228,7 +229,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id but give an error if the second rule does not exist', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts index 03b1beffa79931..913f93d24c16e8 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts @@ -23,20 +23,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('export_rules', () => { describe('exporting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should set the response content types to be expected', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -48,7 +49,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should export a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -64,7 +65,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should export a exported count with a single rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -78,6 +79,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(bodySplitAndParsed).to.eql({ exported_exception_list_count: 0, exported_exception_list_item_count: 0, + exported_count: 1, exported_rules_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], @@ -89,8 +91,8 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should export exactly two rules given two rules', async () => { - await createRule(supertest, getSimpleRule('rule-1')); - await createRule(supertest, getSimpleRule('rule-2')); + await createRule(supertest, log, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-2')); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts index 30e1b3eef714a1..5d33e85d2d598e 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts @@ -24,15 +24,16 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('find_rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should return an empty find body correctly if no rules are loaded', async () => { @@ -51,7 +52,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return a single rule when a single rule is loaded from a find with defaults added', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); // query the single rule from _find const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts index 73eb57d5397f5b..82a793619483e5 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts @@ -24,6 +24,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); + const log = getService('log'); describe('find_statuses', () => { before(async () => { @@ -35,13 +36,13 @@ export default ({ getService }: FtrProviderContext): void => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllRulesStatuses(es); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllRulesStatuses(es, log); }); it('should return an empty find statuses body correctly if no statuses are loaded', async () => { @@ -55,9 +56,9 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); + const resBody = await createRule(supertest, log, getSimpleRule('rule-1', true)); - await waitForRuleSuccessOrStatus(supertest, resBody.id); + await waitForRuleSuccessOrStatus(supertest, log, resBody.id); // query the single rule from _find const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts index 6b226dae9cb11d..6764ed27a43e77 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts @@ -24,16 +24,17 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const log = getService('log'); describe('get_prepackaged_rules_status', () => { describe('getting prepackaged rules status', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts index 095bc1a02c217e..88541329fa3148 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts @@ -23,16 +23,17 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('import_rules', () => { describe('importing rules with an index', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should set the response content types to be expected', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts index 49de588118b9ae..20f5d3567ad230 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts @@ -21,16 +21,17 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const log = getService('log'); describe('install_prepackaged_timelines', () => { describe('creating prepackaged rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await deleteAllTimelines(es); }); @@ -67,13 +68,17 @@ export default ({ getService }: FtrProviderContext): void => { it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { await supertest.put(TIMELINE_PREPACKAGED_URL).set('kbn-xsrf', 'true').send().expect(200); - await waitFor(async () => { - const { body } = await supertest - .get(`${TIMELINE_PREPACKAGED_URL}/_status`) - .set('kbn-xsrf', 'true') - .expect(200); - return body.timelines_not_installed === 0; - }, `${TIMELINE_PREPACKAGED_URL}/_status`); + await waitFor( + async () => { + const { body } = await supertest + .get(`${TIMELINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.timelines_not_installed === 0; + }, + `${TIMELINE_PREPACKAGED_URL}/_status`, + log + ); const { body } = await supertest .put(TIMELINE_PREPACKAGED_URL) diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts index d200814bfb3d1e..fc0072de05f5ef 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts @@ -32,6 +32,7 @@ import { RACAlert } from '../../../../plugins/security_solution/server/lib/detec export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe.skip('open_close_signals', () => { describe('tests with auditbeat data', () => { @@ -44,30 +45,30 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await deleteAllAlerts(supertest); - await createSignalsIndex(supertest); + await deleteAllAlerts(supertest, log); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to execute and get 10 signals', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); it('should be have set the signals in an open state initially', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const everySignalOpen = signalsOpen.hits.hits.every( (hit) => hit._source?.[ALERT_WORKFLOW_STATUS] === 'open' ); @@ -76,10 +77,10 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to get a count of 10 closed signals when closing 10', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -101,10 +102,10 @@ export default ({ getService }: FtrProviderContext) => { it('should be able close 10 signals immediately and they all should be closed', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts index 45ef1c88506b51..34d0369f72c030 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts @@ -24,20 +24,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('patch_rules', () => { describe('patch rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should patch a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -54,7 +55,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a "403 forbidden" using a rule_id of type "machine learning"', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's type to machine learning const { body } = await supertest @@ -73,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { // create a simple rule const rule = getSimpleRule('rule-1'); delete rule.rule_id; - const createRuleBody = await createRule(supertest, rule); + const createRuleBody = await createRule(supertest, log, rule); // patch a simple rule's name const { body } = await supertest @@ -90,7 +91,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -107,7 +108,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change the version of a rule when it patches only enabled', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false const { body } = await supertest @@ -124,7 +125,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it patches enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false and another property const { body } = await supertest @@ -143,7 +144,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change other properties when it does patches', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's timeline_title await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts index 74d3bc8dd68d32..0be23c1d8a2891 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts @@ -24,20 +24,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('patch_rules_bulk', () => { describe('patch rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should patch a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -54,8 +55,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch two rule properties of name using the two rules rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); - await createRule(supertest, getSimpleRule('rule-2')); + await createRule(supertest, log, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-2')); // patch both rule names const { body } = await supertest @@ -82,7 +83,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch a single rule property of name using an id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRule('rule-1')); + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -99,8 +100,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch two rule properties of name using the two rules id', async () => { - const createRule1 = await createRule(supertest, getSimpleRule('rule-1')); - const createRule2 = await createRule(supertest, getSimpleRule('rule-2')); + const createRule1 = await createRule(supertest, log, getSimpleRule('rule-1')); + const createRule2 = await createRule(supertest, log, getSimpleRule('rule-2')); // patch both rule names const { body } = await supertest @@ -127,7 +128,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -144,7 +145,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change the version of a rule when it patches only enabled', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false const { body } = await supertest @@ -161,7 +162,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it patches enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false and another property const { body } = await supertest @@ -180,7 +181,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change other properties when it does patches', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's timeline_title await supertest @@ -240,7 +241,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch one rule property and give an error about a second fake rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch one rule name and give a fake id for the second const { body } = await supertest @@ -270,7 +271,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch one rule property and give an error about a second fake id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch one rule name and give a fake id for the second const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts b/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts index 635000a6dd5d5b..1c0c222c38bebb 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/query_signals.ts @@ -18,6 +18,7 @@ import { getSignalStatus, createSignalsIndex, deleteSignalsIndex } from '../../u export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('query_signals_route and find_alerts_route', () => { describe('validation checks', () => { @@ -39,7 +40,7 @@ export default ({ getService }: FtrProviderContext) => { }); it.skip('should not give errors when querying and the signals index does exist and is empty', async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); const { body } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') @@ -58,18 +59,18 @@ export default ({ getService }: FtrProviderContext) => { }, }); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); }); describe('backwards compatibility', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/endpoint/resolver/signals'); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/endpoint/resolver/signals'); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); it('should be able to filter old signals on host.os.name.caseless using runtime field', async () => { @@ -125,7 +126,7 @@ export default ({ getService }: FtrProviderContext) => { }); it.skip('should not give errors when querying and the signals index does exist and is empty', async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); const { body } = await supertest .post(ALERTS_AS_DATA_FIND_URL) .set('kbn-xsrf', 'true') @@ -144,11 +145,11 @@ export default ({ getService }: FtrProviderContext) => { }, }); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); it('should not give errors when executing security solution histogram aggs', async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); await supertest .post(ALERTS_AS_DATA_FIND_URL) .set('kbn-xsrf', 'true') @@ -209,7 +210,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts index 66a8459d0984cf..b874f19f30d422 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts @@ -25,20 +25,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('read_rules', () => { describe('reading rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to read a single rule using rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) @@ -51,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to read a single rule using id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRule()); + const createRuleBody = await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) @@ -64,7 +65,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to read a single rule with an auto-generated rule_id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const createRuleBody = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${createRuleBody.rule_id}`) diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rac_alerts.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rac_alerts.ts index e89ff48f9de109..18207f92580596 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rac_alerts.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rac_alerts.ts @@ -29,6 +29,7 @@ import { RACAlert } from '../../../../plugins/security_solution/server/lib/detec export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('open_close_signals', () => { describe('tests with auditbeat data', () => { @@ -39,29 +40,29 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); }); beforeEach(async () => { - await deleteAllAlerts(supertest); - await createSignalsIndex(supertest); + await deleteAllAlerts(supertest, log); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to execute and get 10 signals', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); it('should be have set the signals in an open state initially', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const everySignalOpen = signalsOpen.hits.hits.every( (hit) => hit._source?.[ALERT_WORKFLOW_STATUS] === 'open' ); @@ -70,10 +71,10 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to get a count of 10 closed signals when closing 10', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -95,10 +96,10 @@ export default ({ getService }: FtrProviderContext) => { it('should be able close 10 signals immediately and they all should be closed', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -124,10 +125,10 @@ export default ({ getService }: FtrProviderContext) => { it('should be able mark 10 signals as acknowledged immediately and they all should be acknowledged', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of acknowledged. There is no reason to use a waitUntil here diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts index 6532c3ad2be073..66baade16bd3cf 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts @@ -26,20 +26,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_rules', () => { describe('update rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should update a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -61,7 +62,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a 403 forbidden if it is a machine learning job', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's type to try to be a machine learning job type const updatedRule = getSimpleMlRuleUpdate('rule-1'); @@ -84,7 +85,7 @@ export default ({ getService }: FtrProviderContext) => { it('should update a single rule property of name using an auto-generated rule_id', async () => { const rule = getSimpleRule('rule-1'); delete rule.rule_id; - const createRuleBody = await createRule(supertest, rule); + const createRuleBody = await createRule(supertest, log, rule); // update a simple rule's name const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -106,7 +107,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -128,7 +129,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it updates enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's enabled to false and another property const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -151,7 +152,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.timeline_title = 'some title'; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts index 7612aafb5bb607..46e34869a8e039 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts @@ -25,20 +25,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_rules_bulk', () => { describe('update rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should update a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.name = 'some other name'; @@ -58,7 +59,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update two rule properties of name using the two rules rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // create a second simple rule await supertest @@ -95,7 +96,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update a single rule property of name using an id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRule('rule-1')); + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -117,8 +118,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update two rule properties of name using the two rules id', async () => { - const createRule1 = await createRule(supertest, getSimpleRule('rule-1')); - const createRule2 = await createRule(supertest, getSimpleRule('rule-2')); + const createRule1 = await createRule(supertest, log, getSimpleRule('rule-1')); + const createRule2 = await createRule(supertest, log, getSimpleRule('rule-2')); // update both rule names const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -152,7 +153,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -174,7 +175,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it updates enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's enabled to false and another property const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -197,7 +198,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's timeline_title const ruleUpdate = getSimpleRuleUpdate('rule-1'); @@ -270,7 +271,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update one rule property and give an error about a second fake rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.name = 'some other name'; @@ -305,7 +306,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update one rule property and give an error about a second fake id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update one rule name and give a fake id for the second const rule1 = getSimpleRuleUpdate(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts index 3278bf902fae41..cf298758390601 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts @@ -26,6 +26,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('add_actions', () => { describe('adding actions', () => { @@ -38,12 +39,12 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to create a new webhook action and attach it to a rule', async () => { @@ -54,7 +55,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id)); + const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id)); const bodyToCompare = removeServerGeneratedProperties(rule); expect(bodyToCompare).to.eql( getSimpleRuleOutputWithWebHookAction(`${bodyToCompare?.actions?.[0].id}`) @@ -69,8 +70,12 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id, true)); - await waitForRuleSuccessOrStatus(supertest, rule.id); + const rule = await createRule( + supertest, + log, + getRuleWithWebHookAction(hookAction.id, true) + ); + await waitForRuleSuccessOrStatus(supertest, log, rule.id); // expected result for status should be 'succeeded' const { body } = await supertest @@ -95,8 +100,8 @@ export default ({ getService }: FtrProviderContext) => { meta: {}, }; - const rule = await createRule(supertest, ruleWithAction); - await waitForRuleSuccessOrStatus(supertest, rule.id); + const rule = await createRule(supertest, log, ruleWithAction); + await waitForRuleSuccessOrStatus(supertest, log, rule.id); // expected result for status should be 'succeeded' const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts index 625cad531a1813..b935a38753e1f9 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts @@ -23,31 +23,36 @@ import { export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); + const log = getService('log'); describe('add_prepackaged_rules', () => { describe('creating prepackaged rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await deleteAllTimelines(es); }); it('should create the prepackaged rules and return a count greater than zero, rules_updated to be zero, and contain the correct keys', async () => { let responseBody: unknown; - await waitFor(async () => { - const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send(); - if (status === 200) { - responseBody = body; - } - return status === 200; - }, DETECTION_ENGINE_PREPACKAGED_URL); + await waitFor( + async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, + DETECTION_ENGINE_PREPACKAGED_URL, + log + ); const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; expect(prepackagedRules.rules_installed).to.be.greaterThan(0); @@ -61,29 +66,37 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should be possible to call the API twice and the second time the number of rules installed should be zero as well as timeline', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. // This is to reduce flakiness where it can for a short period of time try to install the same rule twice. - await waitFor(async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) - .set('kbn-xsrf', 'true') - .expect(200); - return body.rules_not_installed === 0; - }, `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`); + await waitFor( + async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.rules_not_installed === 0; + }, + `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, + log + ); let responseBody: unknown; - await waitFor(async () => { - const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send(); - if (status === 200) { - responseBody = body; - } - return status === 200; - }, DETECTION_ENGINE_PREPACKAGED_URL); + await waitFor( + async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, + DETECTION_ENGINE_PREPACKAGED_URL, + log + ); const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; expect(prepackagedRules.rules_installed).to.eql(0); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/alerts/alerts_compatibility.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/alerts/alerts_compatibility.ts index 09913388a06174..a9942fc86566b4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/alerts/alerts_compatibility.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/alerts/alerts_compatibility.ts @@ -26,6 +26,7 @@ import { ThreatEcs } from '../../../../../plugins/security_solution/common/ecs/t export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); + const log = getService('log'); describe('Alerts Compatibility', function () { describe('CTI', () => { @@ -43,14 +44,14 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.load( 'x-pack/test/functional/es_archives/security_solution/legacy_cti_signals' ); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { await esArchiver.unload( 'x-pack/test/functional/es_archives/security_solution/legacy_cti_signals' ); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); it('allows querying of legacy enriched signals by threat.indicator', async () => { @@ -103,14 +104,23 @@ export default ({ getService }: FtrProviderContext) => { expect(indices.length).to.eql(1); expect(indices[0].is_outdated).to.eql(true); - const [migration] = await startSignalsMigration({ indices: [indices[0].index], supertest }); - await waitFor(async () => { - const [{ completed }] = await finalizeSignalsMigration({ - migrationIds: [migration.migration_id], - supertest, - }); - return completed === true; - }, `polling finalize_migration until complete`); + const [migration] = await startSignalsMigration({ + indices: [indices[0].index], + supertest, + log, + }); + await waitFor( + async () => { + const [{ completed }] = await finalizeSignalsMigration({ + migrationIds: [migration.migration_id], + supertest, + log, + }); + return completed === true; + }, + `polling finalize_migration until complete`, + log + ); const { body: { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts index 3c033d2077c54e..c2616d1155c405 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts @@ -23,6 +23,8 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); + interface HostAlias { name: string; } @@ -37,20 +39,20 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should keep the original alias value such as "host_alias" from a source index when the value is indexed', async () => { const rule = getRuleForSignalTesting(['host_alias']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits .map((signal) => (signal._source?.host_alias as HostAlias).name) .sort(); @@ -59,10 +61,10 @@ export default ({ getService }: FtrProviderContext) => { it('should copy alias data from a source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['host_alias']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits .map((signal) => (signal._source?.host as HostAlias).name) .sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts index 58b5f98ff0c0d1..4e7cccd85d8281 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts @@ -26,26 +26,27 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); describe('check_privileges', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alias'); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/alias'); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); beforeEach(async () => { - await deleteAllAlerts(supertest); + await deleteAllAlerts(supertest, log); }); afterEach(async () => { - await deleteAllAlerts(supertest); + await deleteAllAlerts(supertest, log); }); describe('should set status to partial failure when user has no access', () => { @@ -63,7 +64,7 @@ export default ({ getService }: FtrProviderContext) => { user: ROLES.detections_admin, pass: 'changeme', }); - await waitForRuleSuccessOrStatus(supertest, id, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) .set('kbn-xsrf', 'true') @@ -89,7 +90,7 @@ export default ({ getService }: FtrProviderContext) => { user: ROLES.detections_admin, pass: 'changeme', }); - await waitForRuleSuccessOrStatus(supertest, id, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_endpoint_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_endpoint_exceptions.ts index 6c6fcc366782af..76848db5d55e82 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_endpoint_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_endpoint_exceptions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ToolingLog } from '@kbn/dev-utils'; import expect from '@kbn/expect'; import type SuperTest from 'supertest'; @@ -42,9 +43,10 @@ interface Host { */ export const getHostHits = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, id: string ): Promise => { - const signalsOpen = await getSignalsById(supertest, id); + const signalsOpen = await getSignalsById(supertest, log, id); return signalsOpen.hits.hits .map((hit) => hit._source?.host as Host) .sort((a, b) => { @@ -69,6 +71,7 @@ export const getHostHits = async ( export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for endpoints', () => { before(async () => { @@ -86,24 +89,24 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('no exceptions set', () => { it('should find all the "hosts" from a "agent" index when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['agent']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const hits = await getHostHits(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -122,10 +125,10 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the "hosts" from a "endpoint_without_host_type" index when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['endpoint_without_host_type']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const hits = await getHostHits(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { name: 'Linux' }, @@ -149,6 +152,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -165,9 +169,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { name: 'Linux' }, @@ -185,6 +189,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -201,9 +206,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { name: 'Linux' }, @@ -221,6 +226,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -248,9 +254,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { name: 'Linux' }, @@ -265,6 +271,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -292,9 +299,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { name: 'Linux' }, @@ -311,6 +318,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -327,9 +335,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -347,6 +355,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -363,9 +372,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -383,6 +392,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -410,9 +420,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -427,6 +437,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -454,9 +465,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -473,6 +484,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -489,9 +501,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 6, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 6, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -518,6 +530,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -534,9 +547,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 6, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 6, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -563,6 +576,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -590,9 +604,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -613,6 +627,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -640,9 +655,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -666,6 +681,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [ [ @@ -691,9 +707,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'macos' }, @@ -705,6 +721,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [ [ @@ -730,9 +747,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'macos' }, @@ -746,6 +763,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -762,9 +780,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -782,6 +800,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -798,9 +817,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -815,6 +834,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -831,9 +851,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, @@ -848,6 +868,7 @@ export default ({ getService }: FtrProviderContext) => { const rule = getRuleForSignalTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, + log, rule, [], [ @@ -864,9 +885,9 @@ export default ({ getService }: FtrProviderContext) => { }, ] ); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const hits = await getHostHits(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const hits = await getHostHits(supertest, log, id); expect(hits).to.eql([ { os: { type: 'linux' }, 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 1882f488e5a179..0dfc753be402c4 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 @@ -60,6 +60,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); + const log = getService('log'); const es = getService('es'); describe('create_rules_with_exceptions', () => { @@ -73,13 +74,13 @@ export default ({ getService }: FtrProviderContext) => { describe('creating rules with exceptions', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); }); describe('elastic admin', () => { @@ -104,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { ], }; - const rule = await createRule(supertest, ruleWithException); + const rule = await createRule(supertest, log, ruleWithException); const expected: Partial = { ...getSimpleRuleOutput(), exceptions_list: [ @@ -142,8 +143,8 @@ export default ({ getService }: FtrProviderContext) => { ], }; - const rule = await createRule(supertest, ruleWithException); - await waitForRuleSuccessOrStatus(supertest, rule.id); + const rule = await createRule(supertest, log, ruleWithException); + await waitForRuleSuccessOrStatus(supertest, log, rule.id); const bodyToCompare = removeServerGeneratedProperties(rule); const expected: Partial = { @@ -162,12 +163,16 @@ export default ({ getService }: FtrProviderContext) => { }); it('should allow removing an exception list from an immutable rule through patch', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one exceptions_list // remove the exceptions list as a user is allowed to remove it from an immutable rule @@ -179,23 +184,29 @@ export default ({ getService }: FtrProviderContext) => { const immutableRuleSecondTime = await getRule( supertest, + log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306' ); expect(immutableRuleSecondTime.exceptions_list.length).to.eql(0); }); it('should allow adding a second exception list to an immutable rule through patch', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); const { id, list_id, namespace_type, type } = await createExceptionList( supertest, + log, getCreateExceptionListMinimalSchemaMock() ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one // add a second exceptions list as a user is allowed to add a second list to an immutable rule @@ -218,6 +229,7 @@ export default ({ getService }: FtrProviderContext) => { const immutableRuleSecondTime = await getRule( supertest, + log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306' ); @@ -225,12 +237,16 @@ export default ({ getService }: FtrProviderContext) => { }); it('should override any updates to pre-packaged rules if the user removes the exception list through the API but the new version of a rule has an exception list again', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one await supertest @@ -239,10 +255,11 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: '9a1a2dae-0b5f-4c3d-8305-a268d404c306', exceptions_list: [] }) .expect(200); - await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); - await installPrePackagedRules(supertest); + await downgradeImmutableRule(es, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + await installPrePackagedRules(supertest, log); const immutableRuleSecondTime = await getRule( supertest, + log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306' ); @@ -252,17 +269,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should merge back an exceptions_list if it was removed from the immutable rule through PATCH', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); const { id, list_id, namespace_type, type } = await createExceptionList( supertest, + log, getCreateExceptionListMinimalSchemaMock() ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one // remove the exception list and only have a single list that is not an endpoint_list @@ -282,10 +304,11 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); - await installPrePackagedRules(supertest); + await downgradeImmutableRule(es, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + await installPrePackagedRules(supertest, log); const immutableRuleSecondTime = await getRule( supertest, + log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306' ); @@ -301,19 +324,24 @@ export default ({ getService }: FtrProviderContext) => { }); it('should NOT add an extra exceptions_list that already exists on a rule during an upgrade', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one - await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); - await installPrePackagedRules(supertest); + await downgradeImmutableRule(es, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + await installPrePackagedRules(supertest, log); const immutableRuleSecondTime = await getRule( supertest, + log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306' ); @@ -325,17 +353,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should NOT allow updates to pre-packaged rules to overwrite existing exception based rules when the user adds an additional exception list', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); const { id, list_id, namespace_type, type } = await createExceptionList( supertest, + log, getCreateExceptionListMinimalSchemaMock() ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); // add a second exceptions list as a user is allowed to add a second list to an immutable rule await supertest @@ -355,10 +388,11 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); - await installPrePackagedRules(supertest); + await downgradeImmutableRule(es, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + await installPrePackagedRules(supertest, log); const immutableRuleSecondTime = await getRule( supertest, + log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306' ); @@ -375,18 +409,23 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not remove any exceptions added to a pre-packaged/immutable rule during an update if that rule has no existing exception lists', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // Create a new exception list const { id, list_id, namespace_type, type } = await createExceptionList( supertest, + log, getCreateExceptionListMinimalSchemaMock() ); // Rule id of "eb079c62-4481-4d6e-9643-3ca499df7aaa" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json // since this rule does not have existing exceptions_list that we are going to use for tests - const immutableRule = await getRule(supertest, 'eb079c62-4481-4d6e-9643-3ca499df7aaa'); + const immutableRule = await getRule( + supertest, + log, + 'eb079c62-4481-4d6e-9643-3ca499df7aaa' + ); expect(immutableRule.exceptions_list.length).eql(0); // make sure we have no exceptions_list // add a second exceptions list as a user is allowed to add a second list to an immutable rule @@ -406,10 +445,11 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - await downgradeImmutableRule(es, 'eb079c62-4481-4d6e-9643-3ca499df7aaa'); - await installPrePackagedRules(supertest); + await downgradeImmutableRule(es, log, 'eb079c62-4481-4d6e-9643-3ca499df7aaa'); + await installPrePackagedRules(supertest, log); const immutableRuleSecondTime = await getRule( supertest, + log, 'eb079c62-4481-4d6e-9643-3ca499df7aaa' ); @@ -424,17 +464,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change the immutable tags when adding a second exception list to an immutable rule through patch', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); const { id, list_id, namespace_type, type } = await createExceptionList( supertest, + log, getCreateExceptionListMinimalSchemaMock() ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one // add a second exceptions list as a user is allowed to add a second list to an immutable rule @@ -457,6 +502,7 @@ export default ({ getService }: FtrProviderContext) => { const body = await findImmutableRuleById( supertest, + log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306' ); expect(body.data.length).to.eql(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad. @@ -468,17 +514,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change count of prepacked rules when adding a second exception list to an immutable rule through patch. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); const { id, list_id, namespace_type, type } = await createExceptionList( supertest, + log, getCreateExceptionListMinimalSchemaMock() ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getRule( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one // add a second exceptions list as a user is allowed to add a second list to an immutable rule @@ -499,7 +550,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - const status = await getPrePackagedRulesStatus(supertest); + const status = await getPrePackagedRulesStatus(supertest, log); expect(status.rules_not_installed).to.eql(0); }); }); @@ -544,18 +595,19 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); }); it('should be able to execute against an exception list that does not include valid entries and get back 10 signals', async () => { const { id, list_id, namespace_type, type } = await createExceptionList( supertest, + log, getCreateExceptionListMinimalSchemaMock() ); @@ -570,7 +622,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], }; - await createExceptionListItem(supertest, exceptionListItem); + await createExceptionListItem(supertest, log, exceptionListItem); const ruleWithException: CreateRulesSchema = { name: 'Simple Rule Query', @@ -592,10 +644,10 @@ export default ({ getService }: FtrProviderContext) => { }, ], }; - const { id: createdId } = await createRule(supertest, ruleWithException); - await waitForRuleSuccessOrStatus(supertest, createdId); - await waitForSignalsToBePresent(supertest, 10, [createdId]); - const signalsOpen = await getSignalsByIds(supertest, [createdId]); + const { id: createdId } = await createRule(supertest, log, ruleWithException); + await waitForRuleSuccessOrStatus(supertest, log, createdId); + await waitForSignalsToBePresent(supertest, log, 10, [createdId]); + const signalsOpen = await getSignalsByIds(supertest, log, [createdId]); expect(signalsOpen.hits.hits.length).equal(10); }); @@ -612,7 +664,7 @@ export default ({ getService }: FtrProviderContext) => { from: '1900-01-01T00:00:00.000Z', query: 'host.name: "suricata-sensor-amsterdam"', }; - const createdRule = await createRuleWithExceptionEntries(supertest, rule, [ + const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'host.name', // This matches the query above which will exclude everything @@ -622,7 +674,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -631,7 +683,7 @@ export default ({ getService }: FtrProviderContext) => { ...getEqlRuleForSignalTesting(['auditbeat-*']), query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', }; - const createdRule = await createRuleWithExceptionEntries(supertest, rule, [ + const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'host.id', @@ -641,7 +693,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -653,7 +705,7 @@ export default ({ getService }: FtrProviderContext) => { value: 700, }, }; - const createdRule = await createRuleWithExceptionEntries(supertest, rule, [ + const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'host.id', @@ -663,7 +715,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -696,7 +748,7 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const createdRule = await createRuleWithExceptionEntries(supertest, rule, [ + const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'source.ip', @@ -706,21 +758,21 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); describe('rules with value list exceptions', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('generates no signals when a value list exception is added for a query rule', async () => { const valueListId = 'value-list-id'; - await importFile(supertest, 'keyword', ['suricata-sensor-amsterdam'], valueListId); + await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); const rule: QueryCreateSchema = { name: 'Simple Rule Query', description: 'Simple Rule Query', @@ -733,7 +785,7 @@ export default ({ getService }: FtrProviderContext) => { from: '1900-01-01T00:00:00.000Z', query: 'host.name: "suricata-sensor-amsterdam"', }; - const createdRule = await createRuleWithExceptionEntries(supertest, rule, [ + const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'host.name', @@ -746,13 +798,13 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); it('generates no signals when a value list exception is added for a threat match rule', async () => { const valueListId = 'value-list-id'; - await importFile(supertest, 'keyword', ['zeek-sensor-amsterdam'], valueListId); + await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); const rule: ThreatMatchCreateSchema = { description: 'Detecting root and admin users', name: 'Query with a rule id', @@ -781,7 +833,7 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const createdRule = await createRuleWithExceptionEntries(supertest, rule, [ + const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'host.name', @@ -794,7 +846,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts new file mode 100644 index 00000000000000..fabc964692c7d7 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts @@ -0,0 +1,90 @@ +/* + * 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 { + DEFAULT_ALERTS_INDEX, + DETECTION_ENGINE_INDEX_URL, +} from '../../../../plugins/security_solution/common/constants'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteSignalsIndex } from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('create_index', () => { + afterEach(async () => { + await deleteSignalsIndex(supertest, log); + }); + + describe('elastic admin', () => { + describe('with another index that shares index alias', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/signals/index_alias_clash'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/signals/index_alias_clash'); + }); + + it.skip('should report that signals index does not exist', async () => { + const { body } = await supertest.get(DETECTION_ENGINE_INDEX_URL).send().expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should return 200 for create_index', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + expect(body).to.eql({ acknowledged: true }); + }); + }); + + describe('with an outdated signals index', () => { + beforeEach(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/endpoint/resolver/signals'); + }); + + afterEach(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/endpoint/resolver/signals'); + }); + + it('should report that signals index is outdated', async () => { + const { body } = await supertest.get(DETECTION_ENGINE_INDEX_URL).send().expect(200); + expect(body).to.eql({ + index_mapping_outdated: true, + name: `${DEFAULT_ALERTS_INDEX}-default`, + }); + }); + + it('should return 200 for create_index and add field aliases', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + expect(body).to.eql({ acknowledged: true }); + + const mappings = await es.indices.get({ + index: '.siem-signals-default-000001', + }); + // Make sure that aliases_version has been updated on the existing index + expect(mappings['.siem-signals-default-000001'].mappings?._meta?.aliases_version).to.eql( + 1 + ); + }); + }); + }); + }); +}; 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 6cfc21306d0a6b..57f4875b6c8643 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 @@ -37,13 +37,15 @@ import { ALERT_ANCESTORS, ALERT_DEPTH, ALERT_ORIGINAL_TIME, -} from '../../../../plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/field_names'; +} from '../../../../plugins/security_solution/common/field_maps/field_names'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); + const log = getService('log'); + const siemModule = 'siem_auditbeat'; const mlJobId = 'linux_anomalous_network_activity_ecs'; const testRule: MachineLearningCreateSchema = { @@ -102,12 +104,12 @@ export default ({ getService }: FtrProviderContext) => { }); afterEach(async () => { - await deleteAllAlerts(supertest); + await deleteAllAlerts(supertest, log); }); it('should create 1 alert from ML rule when record meets anomaly_threshold', async () => { - const createdRule = await createRule(supertest, testRule); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const createdRule = await createRule(supertest, log, testRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).eql(1); const signal = signalsOpen.hits.hits[0]; if (!signal._source) { @@ -208,17 +210,17 @@ export default ({ getService }: FtrProviderContext) => { ...testRule, anomaly_threshold: 20, }; - const createdRule = await createRule(supertest, rule); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const createdRule = await createRule(supertest, log, rule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).eql(7); }); describe('with non-value list exception', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('generates no signals when an exception is added for an ML rule', async () => { - const createdRule = await createRuleWithExceptionEntries(supertest, testRule, [ + const createdRule = await createRuleWithExceptionEntries(supertest, log, testRule, [ [ { field: 'host.name', @@ -228,25 +230,25 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); }); describe('with value list exception', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); - await deleteAllExceptions(supertest); + await deleteListsIndex(supertest, log); + await deleteAllExceptions(supertest, log); }); it('generates no signals when a value list exception is added for an ML rule', async () => { const valueListId = 'value-list-id'; - await importFile(supertest, 'keyword', ['mothra'], valueListId); - const createdRule = await createRuleWithExceptionEntries(supertest, testRule, [ + await importFile(supertest, log, 'keyword', ['mothra'], valueListId); + const createdRule = await createRuleWithExceptionEntries(supertest, log, testRule, [ [ { field: 'host.name', @@ -259,7 +261,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).equal(0); }); }); 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 b43339261aae4d..d7bba5fa5dbe50 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,6 +44,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); describe('create_rules', () => { describe('creating rules', () => { @@ -56,12 +57,12 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); describe('elastic admin', () => { @@ -103,7 +104,7 @@ export default ({ getService }: FtrProviderContext) => { .send(simpleRule) .expect(200); - await waitForRuleSuccessOrStatus(supertest, body.id); + await waitForRuleSuccessOrStatus(supertest, log, body.id); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) @@ -123,7 +124,7 @@ export default ({ getService }: FtrProviderContext) => { .send(simpleRule) .expect(200); - await waitForRuleSuccessOrStatus(supertest, body.id, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, body.id, 'partial failure'); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) @@ -145,7 +146,7 @@ export default ({ getService }: FtrProviderContext) => { .send(simpleRule) .expect(200); - await waitForRuleSuccessOrStatus(supertest, body.id, 'succeeded'); + await waitForRuleSuccessOrStatus(supertest, log, body.id, 'succeeded'); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) @@ -289,7 +290,7 @@ export default ({ getService }: FtrProviderContext) => { describe('missing timestamps', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); // to edit these files run the following script // cd $HOME/kibana/x-pack && nvm use && node ../scripts/es_archiver edit security_solution/timestamp_override await esArchiver.load( @@ -297,8 +298,8 @@ export default ({ getService }: FtrProviderContext) => { ); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload( 'x-pack/test/functional/es_archives/security_solution/timestamp_override' ); @@ -316,8 +317,8 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyId = body.id; - await waitForAlertToComplete(supertest, bodyId); - await waitForRuleSuccessOrStatus(supertest, bodyId, 'partial failure'); + await waitForAlertToComplete(supertest, log, bodyId); + await waitForRuleSuccessOrStatus(supertest, log, bodyId, 'partial failure'); await sleep(5000); const { body: statusBody } = await supertest @@ -348,9 +349,9 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyId = body.id; - await waitForRuleSuccessOrStatus(supertest, bodyId, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, bodyId, 'partial failure'); await sleep(5000); - await waitForSignalsToBePresent(supertest, 2, [bodyId]); + await waitForSignalsToBePresent(supertest, log, 2, [bodyId]); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index 3719a3c000e002..e12f9b7fe78258 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('create_rules_bulk', () => { describe('creating rules in bulk', () => { @@ -42,12 +43,12 @@ export default ({ getService }: FtrProviderContext): void => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should create a single rule with a rule_id', async () => { @@ -88,7 +89,7 @@ export default ({ getService }: FtrProviderContext): void => { .send([simpleRule]) .expect(200); - await waitForRuleSuccessOrStatus(supertest, body[0].id); + await waitForRuleSuccessOrStatus(supertest, log, body[0].id); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts index 78f117f3385af7..60cb8a40179055 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_signals_migrations.ts @@ -42,6 +42,7 @@ export default ({ getService }: FtrProviderContext): void => { const kbnClient = getService('kibanaServer'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); describe('Creating signals migrations', () => { let createdMigrations: CreateResponse[]; @@ -57,7 +58,7 @@ export default ({ getService }: FtrProviderContext): void => { outdatedSignalsIndexName = getIndexNameFromLoad( await esArchiver.load('x-pack/test/functional/es_archives/signals/outdated_signals_index') ); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { @@ -76,7 +77,7 @@ export default ({ getService }: FtrProviderContext): void => { kbnClient, ids: createdMigrations.filter((m) => m?.migration_id).map((m) => m.migration_id), }); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); it('returns the information necessary to finalize the migration', async () => { @@ -110,7 +111,7 @@ export default ({ getService }: FtrProviderContext): void => { createResponses.forEach((response) => expect(response.migration_id).to.be.a('string')); const [{ migration_index: newIndex }] = createResponses; - await waitForIndexToPopulate(es, newIndex); + await waitForIndexToPopulate(es, log, newIndex); const migrationResults = await es.search<{ signal: Signal }>({ index: newIndex }); expect(migrationResults.hits.hits).length(1); 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 dcfdfb7bbd9bcf..3d095992788095 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 @@ -41,6 +41,7 @@ import { import { getCreateThreatMatchRulesSchemaMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock'; import { getThreatMatchingSchemaPartialMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks'; import { ENRICHMENT_TYPES } from '../../../../plugins/security_solution/common/cti/constants'; +import { Ancestor } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; import { ALERT_ANCESTORS, ALERT_DEPTH, @@ -48,8 +49,7 @@ import { ALERT_ORIGINAL_EVENT_CATEGORY, ALERT_ORIGINAL_EVENT_MODULE, ALERT_ORIGINAL_TIME, -} from '../../../../plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/field_names'; -import { Ancestor } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; +} from '../../../../plugins/security_solution/common/field_maps/field_names'; const format = (value: unknown): string => JSON.stringify(value, null, 2); @@ -67,6 +67,7 @@ const assertContains = (subject: unknown[], expected: unknown[]) => export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); + const log = getService('log'); /** * Specific api integration tests for threat matching rule type @@ -82,16 +83,20 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should create a single rule with a rule_id', async () => { - const ruleResponse = await createRule(supertest, getCreateThreatMatchRulesSchemaMock()); + const ruleResponse = await createRule( + supertest, + log, + getCreateThreatMatchRulesSchemaMock() + ); const bodyToCompare = removeServerGeneratedProperties(ruleResponse); expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock()); }); @@ -99,10 +104,11 @@ export default ({ getService }: FtrProviderContext) => { it('should create a single rule with a rule_id and validate it ran successfully', async () => { const ruleResponse = await createRule( supertest, + log, getCreateThreatMatchRulesSchemaMock('rule-1', true) ); - await waitForRuleSuccessOrStatus(supertest, ruleResponse.id, 'succeeded'); + await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id, 'succeeded'); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) @@ -126,13 +132,13 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await deleteAllAlerts(supertest); - await createSignalsIndex(supertest); + await deleteAllAlerts(supertest, log); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to execute and get 10 signals when doing a specific query', async () => { @@ -164,10 +170,10 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const createdRule = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, createdRule.id); - await waitForSignalsToBePresent(supertest, 10, [createdRule.id]); - const signalsOpen = await getSignalsByIds(supertest, [createdRule.id]); + const createdRule = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, createdRule.id); + await waitForSignalsToBePresent(supertest, log, 10, [createdRule.id]); + const signalsOpen = await getSignalsByIds(supertest, log, [createdRule.id]); expect(signalsOpen.hits.hits.length).equal(10); const fullSource = signalsOpen.hits.hits.find( (signal) => @@ -368,9 +374,9 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const ruleResponse = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); + const ruleResponse = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); + const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -407,9 +413,9 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const ruleResponse = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); + const ruleResponse = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); + const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -446,9 +452,9 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const ruleResponse = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); + const ruleResponse = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); + const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -485,8 +491,8 @@ export default ({ getService }: FtrProviderContext) => { items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id, 'failed'); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id, 'failed'); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) @@ -537,10 +543,10 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).equal(2); const { hits } = signalsOpen.hits; @@ -626,10 +632,10 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).equal(1); const { hits } = signalsOpen.hits; @@ -713,10 +719,10 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).equal(1); const { hits } = signalsOpen.hits; @@ -827,10 +833,10 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).equal(2); const { hits } = signalsOpen.hits; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts index 3750a8eed394b0..e5f828d0f862d4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts @@ -25,20 +25,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_rules', () => { describe('deleting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should delete a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // delete the rule by its rule_id const { body } = await supertest @@ -51,7 +52,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); // delete that rule by its auto-generated rule_id const { body } = await supertest @@ -64,7 +65,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRule()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); // delete that rule by its auto-generated id const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts index e978f871b55c25..b7517697ad2a94 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts @@ -25,20 +25,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_rules_bulk', () => { describe('deleting rules bulk using DELETE', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should delete a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); // delete the rule in bulk const { body } = await supertest @@ -52,7 +53,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); // delete that rule by its rule_id const { body } = await supertest @@ -66,7 +67,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRule()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); // delete that rule by its id const { body } = await supertest @@ -116,7 +117,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id but give an error if the second rule does not exist', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) @@ -141,16 +142,16 @@ export default ({ getService }: FtrProviderContext): void => { // This is a repeat of the tests above but just using POST instead of DELETE describe('deleting rules bulk using POST', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should delete a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); // delete the rule in bulk const { body } = await supertest @@ -164,7 +165,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); // delete that rule by its rule_id const { body } = await supertest @@ -178,7 +179,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated id', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRule()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); // delete that rule by its id const { body } = await supertest @@ -228,7 +229,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete a single rule using an auto generated rule_id but give an error if the second rule does not exist', async () => { - const bodyWithCreatedRule = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts index 00d6607cba9635..569f364dcea376 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts @@ -34,6 +34,7 @@ export default ({ getService }: FtrProviderContext): void => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); describe('deleting signals migrations', () => { let outdatedSignalsIndexName: string; @@ -45,7 +46,7 @@ export default ({ getService }: FtrProviderContext): void => { await esArchiver.load('x-pack/test/functional/es_archives/signals/outdated_signals_index') ); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); ({ body: { @@ -57,24 +58,28 @@ export default ({ getService }: FtrProviderContext): void => { .send({ index: [outdatedSignalsIndexName] }) .expect(200)); - await waitFor(async () => { - ({ - body: { - migrations: [finalizedMigration], - }, - } = await supertest - .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ migration_ids: [createdMigration.migration_id] }) - .expect(200)); - - return finalizedMigration.completed ?? false; - }, `polling finalize_migration until all complete`); + await waitFor( + async () => { + ({ + body: { + migrations: [finalizedMigration], + }, + } = await supertest + .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) + .set('kbn-xsrf', 'true') + .send({ migration_ids: [createdMigration.migration_id] }) + .expect(200)); + + return finalizedMigration.completed ?? false; + }, + `polling finalize_migration until all complete`, + log + ); }); afterEach(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/signals/outdated_signals_index'); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); it('returns the deleted migration SavedObjects', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts index 742bbf7285e95a..5ab47a91ef6848 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type date', () => { before(async () => { @@ -41,24 +42,24 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the dates from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-01T05:08:53.000Z', @@ -70,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 1 single date if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -80,9 +81,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-02T05:08:53.000Z', @@ -93,7 +94,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 dates if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -111,16 +112,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); it('should filter 3 dates if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -146,16 +147,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); }); it('should filter 4 dates if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -189,8 +190,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); @@ -199,7 +200,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -209,15 +210,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -227,16 +228,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); }); it('will return 0 results if we exclude two dates', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -254,8 +255,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); @@ -264,7 +265,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single date if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -274,9 +275,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-02T05:08:53.000Z', @@ -287,7 +288,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 dates if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -297,16 +298,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); it('should filter 3 dates if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -320,16 +321,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); }); it('should filter 4 dates if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -344,8 +345,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); @@ -354,7 +355,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -364,15 +365,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -382,9 +383,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); @@ -393,7 +394,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against date', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -402,8 +403,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); @@ -412,7 +413,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against date', async () => { const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -421,9 +422,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-01T05:08:53.000Z', @@ -436,9 +437,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 3 results if we have a list that includes 1 date', async () => { - await importFile(supertest, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); + await importFile(supertest, log, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -451,9 +452,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-02T05:08:53.000Z', @@ -465,12 +466,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 2 results if we have a list that includes 2 dates', async () => { await importFile( supertest, + log, 'date', ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -483,9 +485,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-02T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); @@ -493,6 +495,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 0 results if we have a list that includes all dates', async () => { await importFile( supertest, + log, 'date', [ '2020-10-01T05:08:53.000Z', @@ -503,7 +506,7 @@ export default ({ getService }: FtrProviderContext) => { 'list_items.txt' ); const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -516,8 +519,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); @@ -525,9 +528,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 date', async () => { - await importFile(supertest, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); + await importFile(supertest, log, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -540,9 +543,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); }); @@ -550,12 +553,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 2 results if we have a list that excludes 2 dates', async () => { await importFile( supertest, + log, 'date', ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -568,9 +572,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z']); }); @@ -578,6 +582,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have a list that excludes all dates', async () => { await importFile( supertest, + log, 'date', [ '2020-10-01T05:08:53.000Z', @@ -588,7 +593,7 @@ export default ({ getService }: FtrProviderContext) => { 'list_items.txt' ); const rule = getRuleForSignalTesting(['date']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'date', @@ -601,9 +606,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-01T05:08:53.000Z', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts index a2639ea5224475..63b26c91073cde 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type double', () => { before(async () => { @@ -45,31 +46,31 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the double from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); it('should filter 1 single double if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -79,16 +80,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 double if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -106,16 +107,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 double if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -141,16 +142,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 double if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -184,8 +185,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); @@ -194,7 +195,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -204,15 +205,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -222,16 +223,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0']); }); it('will return 0 results if we exclude two double', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -249,8 +250,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); @@ -259,7 +260,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single double if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -269,16 +270,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 double if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -288,16 +289,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 double if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -307,16 +308,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 double if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -326,8 +327,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); @@ -336,7 +337,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -346,15 +347,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -364,9 +365,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.3']); }); @@ -375,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against double', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -384,8 +385,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); @@ -394,7 +395,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against double', async () => { const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -403,9 +404,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); @@ -414,9 +415,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { describe('working against double values in the data set', () => { it('will return 3 results if we have a list that includes 1 double', async () => { - await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -429,17 +430,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 double', async () => { - await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -452,17 +453,23 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.3']); }); it('will return 0 results if we have a list that includes all double', async () => { - await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile( + supertest, + log, + 'double', + ['1.0', '1.1', '1.2', '1.3'], + 'list_items.txt' + ); const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -475,8 +482,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); @@ -484,9 +491,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 double', async () => { - await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -499,17 +506,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 double', async () => { - await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -522,17 +529,23 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.3']); }); it('will return 0 results if we have a list that includes all double', async () => { - await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile( + supertest, + log, + 'double', + ['1.0', '1.1', '1.2', '1.3'], + 'list_items.txt' + ); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -545,19 +558,19 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); it('will return 1 result if we have a list which contains the double range of 1.0-1.2', async () => { - await importFile(supertest, 'double_range', ['1.0-1.2'], 'list_items.txt', [ + await importFile(supertest, log, 'double_range', ['1.0-1.2'], 'list_items.txt', [ '1.0', '1.2', ]); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -570,9 +583,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.3']); }); @@ -582,9 +595,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { describe('working against double values in the data set', () => { it('will return 1 result if we have a list that excludes 1 double', async () => { - await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -597,17 +610,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 double', async () => { - await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -620,17 +633,23 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.2']); }); it('will return 4 results if we have a list that excludes all double', async () => { - await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile( + supertest, + log, + 'double', + ['1.0', '1.1', '1.2', '1.3'], + 'list_items.txt' + ); const rule = getRuleForSignalTesting(['double']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -643,9 +662,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); @@ -653,9 +672,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 double', async () => { - await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -668,17 +687,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 double', async () => { - await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -691,17 +710,23 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.2']); }); it('will return 4 results if we have a list that excludes all double', async () => { - await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile( + supertest, + log, + 'double', + ['1.0', '1.1', '1.2', '1.3'], + 'list_items.txt' + ); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -714,20 +739,20 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); it('will return 3 results if we have a list which contains the double range of 1.0-1.2', async () => { - await importFile(supertest, 'double_range', ['1.0-1.2'], 'list_items.txt', [ + await importFile(supertest, log, 'double_range', ['1.0-1.2'], 'list_items.txt', [ '1.0', '1.2', ]); const rule = getRuleForSignalTesting(['double_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'double', @@ -740,9 +765,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2']); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts index 288e2353166ee5..840837186a4269 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type float', () => { before(async () => { @@ -43,31 +44,31 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the float from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); it('should filter 1 single float if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -77,16 +78,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 float if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -104,16 +105,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 float if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -139,16 +140,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 float if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -182,8 +183,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); @@ -192,7 +193,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -202,15 +203,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -220,16 +221,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0']); }); it('will return 0 results if we exclude two float', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -247,8 +248,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); @@ -257,7 +258,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single float if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -267,16 +268,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 float if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -286,16 +287,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 float if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -305,16 +306,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 float if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -324,8 +325,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); @@ -334,7 +335,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -344,15 +345,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -362,9 +363,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.3']); }); @@ -373,7 +374,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against float', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -382,8 +383,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); @@ -392,7 +393,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against float', async () => { const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -401,9 +402,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); @@ -412,9 +413,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { describe('working against float values in the data set', () => { it('will return 3 results if we have a list that includes 1 float', async () => { - await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -427,17 +428,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 float', async () => { - await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -450,17 +451,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.3']); }); it('will return 0 results if we have a list that includes all float', async () => { - await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -473,8 +474,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); @@ -482,9 +483,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 float', async () => { - await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -497,17 +498,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 float', async () => { - await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -520,17 +521,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.3']); }); it('will return 0 results if we have a list that includes all float', async () => { - await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -543,16 +544,19 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); it('will return 1 result if we have a list which contains the float range of 1.0-1.2', async () => { - await importFile(supertest, 'float_range', ['1.0-1.2'], 'list_items.txt', ['1.0', '1.2']); + await importFile(supertest, log, 'float_range', ['1.0-1.2'], 'list_items.txt', [ + '1.0', + '1.2', + ]); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -565,9 +569,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.3']); }); @@ -577,9 +581,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { describe('working against float values in the data set', () => { it('will return 1 result if we have a list that excludes 1 float', async () => { - await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -592,17 +596,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 float', async () => { - await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -615,17 +619,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.2']); }); it('will return 4 results if we have a list that excludes all float', async () => { - await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -638,9 +642,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); @@ -648,9 +652,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 float', async () => { - await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -663,17 +667,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 float', async () => { - await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -686,17 +690,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.2']); }); it('will return 4 results if we have a list that excludes all float', async () => { - await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -709,17 +713,20 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); it('will return 3 results if we have a list which contains the float range of 1.0-1.2', async () => { - await importFile(supertest, 'float_range', ['1.0-1.2'], 'list_items.txt', ['1.0', '1.2']); + await importFile(supertest, log, 'float_range', ['1.0-1.2'], 'list_items.txt', [ + '1.0', + '1.2', + ]); const rule = getRuleForSignalTesting(['float_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'float', @@ -732,9 +739,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2']); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts index 459034cd06569d..f7a2951790c561 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type integer', () => { before(async () => { @@ -45,31 +46,31 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the integer from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); it('should filter 1 single integer if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -79,16 +80,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 integer if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -106,16 +107,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 integer if all 3 are as exceptions', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -141,16 +142,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 integer if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -184,8 +185,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); @@ -194,7 +195,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -204,15 +205,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -222,16 +223,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1']); }); it('will return 0 results if we exclude two integer', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -249,8 +250,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); @@ -259,7 +260,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single integer if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -269,16 +270,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 integer if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -288,16 +289,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 integer if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -307,16 +308,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 integer if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -326,8 +327,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); @@ -336,7 +337,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -346,15 +347,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -364,9 +365,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '4']); }); @@ -375,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against integer', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -384,8 +385,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); @@ -394,7 +395,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against integer', async () => { const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -403,9 +404,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); @@ -414,9 +415,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { describe('working against integer values in the data set', () => { it('will return 3 results if we have a list that includes 1 integer', async () => { - await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -429,17 +430,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 integer', async () => { - await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -452,17 +453,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all integer', async () => { - await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -475,8 +476,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); @@ -484,9 +485,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 integer', async () => { - await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -499,17 +500,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 integer', async () => { - await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -522,17 +523,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all integer', async () => { - await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -545,16 +546,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); it('will return 1 result if we have a list which contains the integer range of 1-3', async () => { - await importFile(supertest, 'integer_range', ['1-3'], 'list_items.txt', ['1', '2']); + await importFile(supertest, log, 'integer_range', ['1-3'], 'list_items.txt', ['1', '2']); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -567,9 +568,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['4']); }); @@ -579,9 +580,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { describe('working against integer values in the data set', () => { it('will return 1 result if we have a list that excludes 1 integer', async () => { - await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -594,17 +595,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 integer', async () => { - await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -617,17 +618,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all integer', async () => { - await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -640,9 +641,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); @@ -650,9 +651,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 integer', async () => { - await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -665,17 +666,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 integer', async () => { - await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -688,17 +689,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all integer', async () => { - await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -711,17 +712,21 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); it('will return 3 results if we have a list which contains the integer range of 1-3', async () => { - await importFile(supertest, 'integer_range', ['1-3'], 'list_items.txt', ['1', '2', '3']); + await importFile(supertest, log, 'integer_range', ['1-3'], 'list_items.txt', [ + '1', + '2', + '3', + ]); const rule = getRuleForSignalTesting(['integer_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'integer', @@ -734,9 +739,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3']); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts index 2497bf096550a3..8e79b933be126d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type ip', () => { before(async () => { @@ -41,31 +42,31 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the ips from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('should filter 1 single ip if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -75,16 +76,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('should filter 2 ips if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -102,16 +103,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -137,16 +138,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); it('should filter 4 ips if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -180,15 +181,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('should filter a CIDR range of "127.0.0.1/30"', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -198,9 +199,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); @@ -209,7 +210,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -219,15 +220,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -237,16 +238,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1']); }); it('will return 0 results if we exclude two ips', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -264,8 +265,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); @@ -274,7 +275,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single ip if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -284,16 +285,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('should filter 2 ips if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -303,16 +304,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -322,16 +323,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); it('should filter 4 ips if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -341,8 +342,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); @@ -351,7 +352,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -361,15 +362,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -379,9 +380,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.4']); }); @@ -390,7 +391,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against ip', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -399,8 +400,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); @@ -409,7 +410,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against ip', async () => { const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -418,9 +419,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); }); @@ -428,9 +429,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 3 results if we have a list that includes 1 ip', async () => { - await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -443,17 +444,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('will return 2 results if we have a list that includes 2 ips', async () => { - await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -466,9 +467,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.4']); }); @@ -476,12 +477,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 0 results if we have a list that includes all ips', async () => { await importFile( supertest, + log, 'ip', ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -494,20 +496,20 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return 1 result if we have a list which contains the CIDR range of "127.0.0.1/30"', async () => { - await importFile(supertest, 'ip_range', ['127.0.0.1/30'], 'list_items.txt', [ + await importFile(supertest, log, 'ip_range', ['127.0.0.1/30'], 'list_items.txt', [ '127.0.0.1', '127.0.0.2', '127.0.0.3', ]); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -520,21 +522,21 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); it('will return 1 result if we have a list which contains the range syntax of "127.0.0.1-127.0.0.3"', async () => { - await importFile(supertest, 'ip_range', ['127.0.0.1-127.0.0.3'], 'list_items.txt', [ + await importFile(supertest, log, 'ip_range', ['127.0.0.1-127.0.0.3'], 'list_items.txt', [ '127.0.0.1', '127.0.0.2', '127.0.0.3', ]); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -547,9 +549,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); @@ -557,13 +559,14 @@ export default ({ getService }: FtrProviderContext) => { it('will return 1 result if we have a list which contains the range mixed syntax of "127.0.0.1/32,127.0.0.2-127.0.0.3"', async () => { await importFile( supertest, + log, 'ip_range', ['127.0.0.1/32', '127.0.0.2-127.0.0.3'], 'list_items.txt', ['127.0.0.1', '127.0.0.2', '127.0.0.3'] ); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -576,9 +579,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); @@ -586,9 +589,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 ip', async () => { - await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -601,17 +604,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1']); }); it('will return 2 results if we have a list that excludes 2 ips', async () => { - await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -624,9 +627,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.3']); }); @@ -634,12 +637,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have a list that excludes all ips', async () => { await importFile( supertest, + log, 'ip', ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -652,21 +656,21 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('will return 3 results if we have a list which contains the CIDR range of "127.0.0.1/30"', async () => { - await importFile(supertest, 'ip_range', ['127.0.0.1/30'], 'list_items.txt', [ + await importFile(supertest, log, 'ip_range', ['127.0.0.1/30'], 'list_items.txt', [ '127.0.0.1', '127.0.0.2', '127.0.0.3', ]); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -679,21 +683,21 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3']); }); it('will return 3 results if we have a list which contains the range syntax of "127.0.0.1-127.0.0.3"', async () => { - await importFile(supertest, 'ip_range', ['127.0.0.1-127.0.0.3'], 'list_items.txt', [ + await importFile(supertest, log, 'ip_range', ['127.0.0.1-127.0.0.3'], 'list_items.txt', [ '127.0.0.1', '127.0.0.2', '127.0.0.3', ]); const rule = getRuleForSignalTesting(['ip']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -706,9 +710,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3']); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts index efff288fddac28..a057fe0cb80018 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type ip', () => { before(async () => { @@ -41,24 +42,24 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the ips from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], @@ -70,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 1 single ip if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -80,9 +81,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], @@ -93,7 +94,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 ips if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -111,16 +112,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -146,16 +147,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); it('should filter a CIDR range of "127.0.0.1/30"', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -165,9 +166,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], @@ -178,7 +179,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter a CIDR range of "127.0.0.4/31"', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -188,9 +189,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); @@ -199,7 +200,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -209,15 +210,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -227,16 +228,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']]); }); it('will return just 1 result we excluded 2 from the same array elements', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -252,16 +253,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']]); }); it('will return 0 results if we exclude two ips', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -279,8 +280,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); @@ -289,7 +290,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single ip if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -299,9 +300,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], @@ -312,7 +313,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 ips if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -322,16 +323,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -341,9 +342,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -352,7 +353,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -362,15 +363,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -380,9 +381,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], @@ -394,7 +395,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 1 empty result if matching against ip', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -403,9 +404,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -414,7 +415,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 3 results if matching against ip', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -423,9 +424,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], @@ -437,9 +438,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 3 results if we have a list that includes 1 ip', async () => { - await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -452,9 +453,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], @@ -464,9 +465,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 2 results if we have a list that includes 2 ips', async () => { - await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.5'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.5'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -479,9 +480,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); @@ -489,12 +490,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 1 result if we have a list that includes all ips', async () => { await importFile( supertest, + log, 'ip', ['127.0.0.1', '127.0.0.5', '127.0.0.8'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -507,9 +509,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -517,6 +519,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 2 results if we have a list which contains the CIDR ranges of "127.0.0.1/32, 127.0.0.2/31, 127.0.0.4/30"', async () => { await importFile( supertest, + log, 'ip_range', ['127.0.0.1/32', '127.0.0.2/31', '127.0.0.4/30'], 'list_items.txt', @@ -531,7 +534,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -544,15 +547,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); it('will return 2 results if we have a list which contains the range syntax of "127.0.0.1-127.0.0.7', async () => { - await importFile(supertest, 'ip_range', ['127.0.0.1-127.0.0.7'], 'list_items.txt', [ + await importFile(supertest, log, 'ip_range', ['127.0.0.1-127.0.0.7'], 'list_items.txt', [ '127.0.0.1', '127.0.0.2', '127.0.0.3', @@ -562,7 +565,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.7', ]); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -575,9 +578,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); @@ -585,9 +588,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 ip', async () => { - await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -600,17 +603,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']]); }); it('will return 2 results if we have a list that excludes 2 ips', async () => { - await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.5'], 'list_items.txt'); + await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.5'], 'list_items.txt'); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -623,9 +626,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], @@ -636,12 +639,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have a list that excludes all ips', async () => { await importFile( supertest, + log, 'ip', ['127.0.0.1', '127.0.0.5', '127.0.0.8'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -654,9 +658,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], @@ -668,6 +672,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have a list which contains the CIDR ranges of "127.0.0.1/32, 127.0.0.2/31, 127.0.0.4/30"', async () => { await importFile( supertest, + log, 'ip_range', ['127.0.0.1/32', '127.0.0.2/31', '127.0.0.4/30'], 'list_items.txt', @@ -682,7 +687,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -695,9 +700,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], @@ -706,7 +711,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 3 results if we have a list which contains the range syntax of "127.0.0.1-127.0.0.7"', async () => { - await importFile(supertest, 'ip_range', ['127.0.0.1-127.0.0.7'], 'list_items.txt', [ + await importFile(supertest, log, 'ip_range', ['127.0.0.1-127.0.0.7'], 'list_items.txt', [ '127.0.0.1', '127.0.0.2', '127.0.0.3', @@ -716,7 +721,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.7', ]); const rule = getRuleForSignalTesting(['ip_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'ip', @@ -729,9 +734,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts index 8a184025b00e29..1e004c259e4418 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type keyword', () => { before(async () => { @@ -41,31 +42,31 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('should filter 1 single keyword if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -75,16 +76,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 keyword if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -102,16 +103,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -137,16 +138,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 keyword if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -180,8 +181,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); @@ -190,7 +191,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -200,15 +201,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -218,16 +219,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word one']); }); it('will return 0 results if we exclude two keyword', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -245,8 +246,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); @@ -255,7 +256,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single keyword if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -265,16 +266,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 keyword if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -284,16 +285,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -303,16 +304,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 keyword if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -322,8 +323,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); @@ -332,7 +333,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -342,15 +343,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -360,9 +361,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one']); }); @@ -371,7 +372,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against keyword', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -380,8 +381,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); @@ -390,7 +391,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against keyword', async () => { const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -399,9 +400,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); @@ -409,10 +410,10 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 4 results if we have two lists with an AND contradiction keyword === "word one" AND keyword === "word two"', async () => { - await importFile(supertest, 'keyword', ['word one'], 'list_items_1.txt'); - await importFile(supertest, 'keyword', ['word two'], 'list_items_2.txt'); + await importFile(supertest, log, 'keyword', ['word one'], 'list_items_1.txt'); + await importFile(supertest, log, 'keyword', ['word two'], 'list_items_2.txt'); const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -434,17 +435,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('will return 3 results if we have a list that includes 1 keyword', async () => { - await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -457,17 +458,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('will return 2 results if we have a list that includes 2 keyword', async () => { - await importFile(supertest, 'keyword', ['word one', 'word three'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one', 'word three'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -480,9 +481,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word two']); }); @@ -490,12 +491,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 0 results if we have a list that includes all keyword', async () => { await importFile( supertest, + log, 'keyword', ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -508,8 +510,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); @@ -517,9 +519,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 keyword', async () => { - await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -532,17 +534,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word one']); }); it('will return 2 results if we have a list that excludes 2 keyword', async () => { - await importFile(supertest, 'keyword', ['word one', 'word three'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one', 'word three'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -555,9 +557,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word one', 'word three']); }); @@ -565,12 +567,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have a list that excludes all keyword', async () => { await importFile( supertest, + log, 'keyword', ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['keyword']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -583,9 +586,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts index ee2f3e287cd663..b43cc818ec01d3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type keyword', () => { before(async () => { @@ -43,24 +44,24 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], @@ -72,7 +73,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 1 single keyword if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -82,9 +83,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], @@ -95,7 +96,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 keyword if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -113,16 +114,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -148,9 +149,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -159,7 +160,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -169,15 +170,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -187,16 +188,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 0 results if we exclude two keyword', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -214,8 +215,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); @@ -224,7 +225,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single keyword if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -234,9 +235,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], @@ -247,7 +248,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 keyword if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -257,16 +258,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -276,9 +277,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -287,7 +288,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -297,15 +298,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -315,9 +316,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], @@ -329,7 +330,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 1 results if matching against keyword for the empty array', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -338,9 +339,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -349,7 +350,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 3 results if matching against keyword', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -358,9 +359,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], @@ -372,10 +373,10 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 4 results if we have two lists with an AND contradiction keyword === "word one" AND keyword === "word five"', async () => { - await importFile(supertest, 'keyword', ['word one'], 'list_items_1.txt'); - await importFile(supertest, 'keyword', ['word five'], 'list_items_2.txt'); + await importFile(supertest, log, 'keyword', ['word one'], 'list_items_1.txt'); + await importFile(supertest, log, 'keyword', ['word five'], 'list_items_2.txt'); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -397,9 +398,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], @@ -410,10 +411,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 3 results if we have two lists with an AND keyword === "word one" AND keyword === "word two" since we have an array', async () => { - await importFile(supertest, 'keyword', ['word one'], 'list_items_1.txt'); - await importFile(supertest, 'keyword', ['word two'], 'list_items_2.txt'); + await importFile(supertest, log, 'keyword', ['word one'], 'list_items_1.txt'); + await importFile(supertest, log, 'keyword', ['word two'], 'list_items_2.txt'); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -435,9 +436,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], @@ -447,9 +448,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 3 results if we have a list that includes 1 keyword', async () => { - await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -462,9 +463,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], @@ -474,9 +475,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 2 results if we have a list that includes 2 keyword', async () => { - await importFile(supertest, 'keyword', ['word one', 'word six'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one', 'word six'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -489,9 +490,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); @@ -499,12 +500,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return only the empty array for results if we have a list that includes all keyword', async () => { await importFile( supertest, + log, 'keyword', ['word one', 'word five', 'word eight'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -517,9 +519,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -527,9 +529,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 keyword', async () => { - await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -542,17 +544,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 1 result if we have a list that excludes 1 keyword but repeat 2 elements from the array in the list', async () => { - await importFile(supertest, 'keyword', ['word one', 'word two'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one', 'word two'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -565,17 +567,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 2 results if we have a list that excludes 2 keyword', async () => { - await importFile(supertest, 'keyword', ['word one', 'word five'], 'list_items.txt'); + await importFile(supertest, log, 'keyword', ['word one', 'word five'], 'list_items.txt'); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -588,9 +590,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], @@ -601,12 +603,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have a list that excludes 3 items', async () => { await importFile( supertest, + log, 'keyword', ['word one', 'word six', 'word ten'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['keyword_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'keyword', @@ -619,9 +622,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts index f5bf3b627bc2ff..36a2ff1e19cbf2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type long', () => { before(async () => { @@ -43,31 +44,31 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the long from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); it('should filter 1 single long if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -77,16 +78,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 long if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -104,16 +105,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 long if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -139,16 +140,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 long if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -182,8 +183,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); @@ -192,7 +193,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -202,15 +203,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -220,16 +221,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1']); }); it('will return 0 results if we exclude two long', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -247,8 +248,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); @@ -257,7 +258,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single long if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -267,16 +268,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 long if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -286,16 +287,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 long if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -305,16 +306,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 long if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -324,8 +325,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); @@ -334,7 +335,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -344,15 +345,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -362,9 +363,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '4']); }); @@ -373,7 +374,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against long', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -382,8 +383,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); @@ -392,7 +393,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against long', async () => { const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -401,9 +402,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); @@ -412,9 +413,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { describe('working against long values in the data set', () => { it('will return 3 results if we have a list that includes 1 long', async () => { - await importFile(supertest, 'long', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -427,17 +428,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 long', async () => { - await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -450,17 +451,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all long', async () => { - await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -473,8 +474,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); @@ -482,9 +483,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 long', async () => { - await importFile(supertest, 'long', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -497,17 +498,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 long', async () => { - await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -520,17 +521,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all long', async () => { - await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -543,16 +544,20 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); it('will return 1 result if we have a list which contains the long range of 1-3', async () => { - await importFile(supertest, 'long_range', ['1-3'], 'list_items.txt', ['1', '2', '3']); + await importFile(supertest, log, 'long_range', ['1-3'], 'list_items.txt', [ + '1', + '2', + '3', + ]); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -565,9 +570,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['4']); }); @@ -577,9 +582,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { describe('working against long values in the data set', () => { it('will return 1 result if we have a list that excludes 1 long', async () => { - await importFile(supertest, 'long', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -592,17 +597,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 long', async () => { - await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -615,17 +620,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all long', async () => { - await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -638,9 +643,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); @@ -648,9 +653,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 long', async () => { - await importFile(supertest, 'long', ['1'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -663,17 +668,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 long', async () => { - await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -686,17 +691,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all long', async () => { - await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -709,17 +714,21 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); it('will return 3 results if we have a list which contains the long range of 1-3', async () => { - await importFile(supertest, 'long_range', ['1-3'], 'list_items.txt', ['1', '2', '3']); + await importFile(supertest, log, 'long_range', ['1-3'], 'list_items.txt', [ + '1', + '2', + '3', + ]); const rule = getRuleForSignalTesting(['long_as_string']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'long', @@ -732,9 +741,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3']); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts index ff2f6806540470..6a0eebb4161f5c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts @@ -31,6 +31,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type text', () => { before(async () => { @@ -44,31 +45,31 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the text from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('should filter 1 single text if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -78,16 +79,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 text if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -105,16 +106,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 text if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -140,16 +141,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 text if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -183,15 +184,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('should filter 1 single text using a single word', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -201,16 +202,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter all words using a common piece of text', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -220,15 +221,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('should filter 1 single text with punctuation added', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -238,9 +239,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); @@ -249,7 +250,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -259,15 +260,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -277,16 +278,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); it('will return 0 results if we exclude two text', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -304,15 +305,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('should filter 1 single text using a single word', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -322,16 +323,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); it('should filter all words using a common piece of text', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -341,16 +342,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('should filter 1 single text with punctuation added', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -360,9 +361,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); @@ -371,7 +372,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single text if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -381,16 +382,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 text if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -400,16 +401,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 text if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -419,16 +420,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 text if all are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -438,8 +439,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); @@ -448,7 +449,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -458,15 +459,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -476,9 +477,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one']); }); @@ -487,7 +488,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 0 results if matching against text', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -496,8 +497,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); @@ -506,7 +507,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 4 results if matching against text', async () => { const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -515,9 +516,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); @@ -526,9 +527,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { describe('working against text values without spaces', () => { it('will return 3 results if we have a list that includes 1 text', async () => { - await importFile(supertest, 'text', ['one'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_no_spaces']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -541,17 +542,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['four', 'three', 'two']); }); it('will return 2 results if we have a list that includes 2 text', async () => { - await importFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['one', 'three'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_no_spaces']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -564,9 +565,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['four', 'two']); }); @@ -574,12 +575,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 0 results if we have a list that includes all text', async () => { await importTextFile( supertest, + log, 'text', ['one', 'two', 'three', 'four'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['text_no_spaces']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -592,8 +594,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); @@ -601,9 +603,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against text values with spaces', () => { it('will return 3 results if we have a list that includes 1 text', async () => { - await importTextFile(supertest, 'text', ['word one'], 'list_items.txt'); + await importTextFile(supertest, log, 'text', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -616,9 +618,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); @@ -626,12 +628,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have a list that includes 1 text with additional wording', async () => { await importTextFile( supertest, + log, 'text', ['word one additional wording'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -644,17 +647,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('will return 2 results if we have a list that includes 2 text', async () => { - await importFile(supertest, 'text', ['word one', 'word three'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['word one', 'word three'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -667,9 +670,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word two']); }); @@ -677,12 +680,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 0 results if we have a list that includes all text', async () => { await importTextFile( supertest, + log, 'text', ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -695,8 +699,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); @@ -706,9 +710,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { describe('working against text values without spaces', () => { it('will return 1 result if we have a list that excludes 1 text', async () => { - await importTextFile(supertest, 'text', ['one'], 'list_items.txt'); + await importTextFile(supertest, log, 'text', ['one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_no_spaces']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -721,17 +725,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['one']); }); it('will return 2 results if we have a list that excludes 2 text', async () => { - await importTextFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + await importTextFile(supertest, log, 'text', ['one', 'three'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_no_spaces']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -744,9 +748,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['one', 'three']); }); @@ -754,12 +758,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have a list that excludes all text', async () => { await importTextFile( supertest, + log, 'text', ['one', 'two', 'three', 'four'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['text_no_spaces']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -772,9 +777,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['four', 'one', 'three', 'two']); }); @@ -782,9 +787,9 @@ export default ({ getService }: FtrProviderContext) => { describe('working against text values with spaces', () => { it('will return 1 result if we have a list that excludes 1 text', async () => { - await importTextFile(supertest, 'text', ['word one'], 'list_items.txt'); + await importTextFile(supertest, log, 'text', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -797,9 +802,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); @@ -807,12 +812,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 1 result if we have a list that excludes 1 text with additional wording', async () => { await importTextFile( supertest, + log, 'text', ['word one additional wording'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -825,17 +831,23 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); it('will return 2 results if we have a list that excludes 2 text', async () => { - await importTextFile(supertest, 'text', ['word one', 'word three'], 'list_items.txt'); + await importTextFile( + supertest, + log, + 'text', + ['word one', 'word three'], + 'list_items.txt' + ); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -848,9 +860,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one', 'word three']); }); @@ -858,12 +870,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have a list that excludes all text', async () => { await importTextFile( supertest, + log, 'text', ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['text']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -876,9 +889,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts index 6d251246671329..f079cc1e0fac12 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule exception operators for data type text', () => { before(async () => { @@ -41,24 +42,24 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); - await createListsIndex(supertest); + await createSignalsIndex(supertest, log); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllExceptions(supertest); - await deleteListsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllExceptions(supertest, log); + await deleteListsIndex(supertest, log); }); describe('"is" operator', () => { it('should find all the text from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], @@ -70,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 1 single text if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -80,9 +81,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], @@ -93,7 +94,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 text if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -111,16 +112,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 text if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -146,9 +147,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -157,7 +158,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -167,15 +168,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -185,16 +186,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 0 results if we exclude two text', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -212,8 +213,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); @@ -222,7 +223,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single text if it is set as an exception', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -232,9 +233,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], @@ -245,7 +246,7 @@ export default ({ getService }: FtrProviderContext) => { it('should filter 2 text if both are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -255,16 +256,16 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 text if all 3 are set as exceptions', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -274,9 +275,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -285,7 +286,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -295,15 +296,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -313,9 +314,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], @@ -327,7 +328,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 1 results if matching against text for the empty array', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -336,9 +337,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -347,7 +348,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"does not exist" operator', () => { it('will return 3 results if matching against text', async () => { const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -356,9 +357,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], @@ -370,10 +371,10 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 4 results if we have two lists with an AND contradiction text === "word one" AND text === "word five"', async () => { - await importFile(supertest, 'text', ['word one'], 'list_items_1.txt'); - await importFile(supertest, 'text', ['word five'], 'list_items_2.txt'); + await importFile(supertest, log, 'text', ['word one'], 'list_items_1.txt'); + await importFile(supertest, log, 'text', ['word five'], 'list_items_2.txt'); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -395,9 +396,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], @@ -408,10 +409,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 3 results if we have two lists with an AND text === "word one" AND text === "word two" since we have an array', async () => { - await importFile(supertest, 'text', ['word one'], 'list_items_1.txt'); - await importFile(supertest, 'text', ['word two'], 'list_items_2.txt'); + await importFile(supertest, log, 'text', ['word one'], 'list_items_1.txt'); + await importFile(supertest, log, 'text', ['word two'], 'list_items_2.txt'); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -433,9 +434,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], @@ -445,9 +446,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 3 results if we have a list that includes 1 text', async () => { - await importFile(supertest, 'text', ['word one'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -460,9 +461,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], @@ -472,9 +473,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 2 results if we have a list that includes 2 text', async () => { - await importFile(supertest, 'text', ['word one', 'word six'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['word one', 'word six'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -487,9 +488,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); @@ -497,12 +498,13 @@ export default ({ getService }: FtrProviderContext) => { it('will return only the empty array for results if we have a list that includes all text', async () => { await importFile( supertest, + log, 'text', ['word one', 'word five', 'word eight'], 'list_items.txt' ); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -515,9 +517,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); }); @@ -525,9 +527,9 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 text', async () => { - await importFile(supertest, 'text', ['word one'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['word one'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -540,17 +542,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 1 result if we have a list that excludes 1 text but repeat 2 elements from the array in the list', async () => { - await importFile(supertest, 'text', ['word one', 'word two'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['word one', 'word two'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -563,17 +565,17 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 2 results if we have a list that excludes 2 text', async () => { - await importFile(supertest, 'text', ['word one', 'word five'], 'list_items.txt'); + await importFile(supertest, log, 'text', ['word one', 'word five'], 'list_items.txt'); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -586,9 +588,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], @@ -597,9 +599,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('will return 3 results if we have a list that excludes 3 items', async () => { - await importFile(supertest, 'text', ['word one', 'word six', 'word ten'], 'list_items.txt'); + await importFile( + supertest, + log, + 'text', + ['word one', 'word six', 'word ten'], + 'list_items.txt' + ); const rule = getRuleForSignalTesting(['text_as_array']); - const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { field: 'text', @@ -612,9 +620,9 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsById(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts index 03b1beffa79931..7ea1985e8d7df6 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts @@ -17,26 +17,28 @@ import { deleteSignalsIndex, getSimpleRule, getSimpleRuleOutput, + getWebHookAction, removeServerGeneratedProperties, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('export_rules', () => { describe('exporting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should set the response content types to be expected', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -48,7 +50,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should export a single rule with a rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -64,7 +66,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should export a exported count with a single rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -78,6 +80,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(bodySplitAndParsed).to.eql({ exported_exception_list_count: 0, exported_exception_list_item_count: 0, + exported_count: 1, exported_rules_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], @@ -89,8 +92,8 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should export exactly two rules given two rules', async () => { - await createRule(supertest, getSimpleRule('rule-1')); - await createRule(supertest, getSimpleRule('rule-2')); + await createRule(supertest, log, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-2')); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -109,6 +112,411 @@ export default ({ getService }: FtrProviderContext): void => { getSimpleRuleOutput('rule-1'), ]); }); + + it('should export multiple actions attached to 1 rule', async () => { + // 1st action + const { body: hookAction1 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // 2nd action + const { body: hookAction2 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action1 = { + group: 'default', + id: hookAction1.id, + action_type_id: hookAction1.actionTypeId, + params: {}, + }; + const action2 = { + group: 'default', + id: hookAction2.id, + action_type_id: hookAction2.actionTypeId, + params: {}, + }; + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action1, action2], + }; + + await createRule(supertest, log, rule1); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .send() + .expect(200) + .parse(binaryToString); + + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + + const outputRule1: ReturnType = { + ...getSimpleRuleOutput('rule-1'), + actions: [action1, action2], + throttle: 'rule', + }; + expect(firstRule).to.eql(outputRule1); + }); + + it('should export actions attached to 2 rules', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + + const rule2: ReturnType = { + ...getSimpleRule('rule-2'), + actions: [action], + }; + + await createRule(supertest, log, rule1); + await createRule(supertest, log, rule2); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .send() + .expect(200) + .parse(binaryToString); + + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + const secondRule = removeServerGeneratedProperties(secondRuleParsed); + + const outputRule1: ReturnType = { + ...getSimpleRuleOutput('rule-2'), + actions: [action], + throttle: 'rule', + }; + const outputRule2: ReturnType = { + ...getSimpleRuleOutput('rule-1'), + actions: [action], + throttle: 'rule', + }; + expect(firstRule).to.eql(outputRule1); + expect(secondRule).to.eql(outputRule2); + }); + + /** + * Tests the legacy actions to ensure we can export legacy notifications + * @deprecated Once the legacy notification system is removed, remove this test too. + */ + describe('legacy_notification_system', () => { + it('should be able to export 1 legacy action on 1 rule', async () => { + // create an action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule without actions + const rule = await createRule(supertest, log, getSimpleRule('rule-1')); + + // attach the legacy notification + await supertest + .post(`/internal/api/detection/legacy/notifications?alert_id=${rule.id}`) + .set('kbn-xsrf', 'true') + .send({ + name: 'Legacy notification with one action', + interval: '1h', + actions: [ + { + id: hookAction.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction.actionTypeId, + }, + ], + }) + .expect(200); + + // export the rule + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .send() + .expect(200) + .parse(binaryToString); + + const outputRule1: ReturnType = { + ...getSimpleRuleOutput('rule-1'), + actions: [ + { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ], + throttle: '1h', + }; + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + + expect(firstRule).to.eql(outputRule1); + }); + + it('should be able to export 2 legacy actions on 1 rule', async () => { + // create 1st action/connector + const { body: hookAction1 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create 2nd action/connector + const { body: hookAction2 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule without actions + const rule = await createRule(supertest, log, getSimpleRule('rule-1')); + + // attach the legacy notification with actions + await supertest + .post(`/internal/api/detection/legacy/notifications?alert_id=${rule.id}`) + .set('kbn-xsrf', 'true') + .send({ + name: 'Legacy notification with one action', + interval: '1h', + actions: [ + { + id: hookAction1.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction1.actionTypeId, + }, + { + id: hookAction2.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction2.actionTypeId, + }, + ], + }) + .expect(200); + + // export the rule + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .send() + .expect(200) + .parse(binaryToString); + + const outputRule1: ReturnType = { + ...getSimpleRuleOutput('rule-1'), + actions: [ + { + group: 'default', + id: hookAction1.id, + action_type_id: hookAction1.actionTypeId, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + { + group: 'default', + id: hookAction2.id, + action_type_id: hookAction2.actionTypeId, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ], + throttle: '1h', + }; + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + + expect(firstRule).to.eql(outputRule1); + }); + + it('should be able to export 2 legacy actions on 2 rules', async () => { + // create 1st action/connector + const { body: hookAction1 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create 2nd action/connector + const { body: hookAction2 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create 2 rules without actions + const rule1 = await createRule(supertest, log, getSimpleRule('rule-1')); + const rule2 = await createRule(supertest, log, getSimpleRule('rule-2')); + + // attach the legacy notification with actions to the first rule + await supertest + .post(`/internal/api/detection/legacy/notifications?alert_id=${rule1.id}`) + .set('kbn-xsrf', 'true') + .send({ + name: 'Legacy notification with one action', + interval: '1h', + actions: [ + { + id: hookAction1.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction1.actionTypeId, + }, + { + id: hookAction2.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction2.actionTypeId, + }, + ], + }) + .expect(200); + + // attach the legacy notification with actions to the 2nd rule + await supertest + .post(`/internal/api/detection/legacy/notifications?alert_id=${rule2.id}`) + .set('kbn-xsrf', 'true') + .send({ + name: 'Legacy notification with one action', + interval: '1h', + actions: [ + { + id: hookAction1.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction1.actionTypeId, + }, + { + id: hookAction2.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction2.actionTypeId, + }, + ], + }) + .expect(200); + + // export the rule + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .send() + .expect(200) + .parse(binaryToString); + + const outputRule1: ReturnType = { + ...getSimpleRuleOutput('rule-1'), + actions: [ + { + group: 'default', + id: hookAction1.id, + action_type_id: hookAction1.actionTypeId, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + { + group: 'default', + id: hookAction2.id, + action_type_id: hookAction2.actionTypeId, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ], + throttle: '1h', + }; + + const outputRule2: ReturnType = { + ...getSimpleRuleOutput('rule-2'), + actions: [ + { + group: 'default', + id: hookAction1.id, + action_type_id: hookAction1.actionTypeId, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + { + group: 'default', + id: hookAction2.id, + action_type_id: hookAction2.actionTypeId, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ], + throttle: '1h', + }; + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + const secondRule = removeServerGeneratedProperties(secondRuleParsed); + + expect(firstRule).to.eql(outputRule2); + expect(secondRule).to.eql(outputRule1); + }); + }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts index 06961fe8fca58e..df06647c2a8b58 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts @@ -46,6 +46,7 @@ export default ({ getService }: FtrProviderContext): void => { const kbnClient = getService('kibanaServer'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); describe('Finalizing signals migrations', () => { let legacySignalsIndexName: string; @@ -61,7 +62,7 @@ export default ({ getService }: FtrProviderContext): void => { outdatedSignalsIndexName = getIndexNameFromLoad( await esArchiver.load('x-pack/test/functional/es_archives/signals/outdated_signals_index') ); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); ({ body: { indices: createdMigrations }, @@ -88,7 +89,7 @@ export default ({ getService }: FtrProviderContext): void => { kbnClient, ids: createdMigrations.filter((m) => m?.migration_id).map((m) => m.migration_id), }); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); it('replaces the original index alias with the migrated one', async () => { @@ -103,31 +104,39 @@ export default ({ getService }: FtrProviderContext): void => { expect(indicesBefore).to.contain(createdMigration.index); expect(indicesBefore).not.to.contain(createdMigration.migration_index); - await waitFor(async () => { - const { - body: { - migrations: [{ completed }], - }, - } = await supertest - .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ migration_ids: [createdMigration.migration_id] }) - .expect(200); - - return completed === true; - }, `polling finalize_migration until complete`); + await waitFor( + async () => { + const { + body: { + migrations: [{ completed }], + }, + } = await supertest + .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) + .set('kbn-xsrf', 'true') + .send({ migration_ids: [createdMigration.migration_id] }) + .expect(200); + + return completed === true; + }, + `polling finalize_migration until complete`, + log + ); let statusAfter: StatusResponse[] = []; - await waitFor(async () => { - ({ - body: { indices: statusAfter }, - } = await supertest - .get(DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL) - .query({ from: '2020-10-10' }) - .set('kbn-xsrf', 'true') - .expect(200)); - return statusAfter.some((s) => !s.is_outdated); - }, `polling finalize_migration until complete`); + await waitFor( + async () => { + ({ + body: { indices: statusAfter }, + } = await supertest + .get(DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL) + .query({ from: '2020-10-10' }) + .set('kbn-xsrf', 'true') + .expect(200)); + return statusAfter.some((s) => !s.is_outdated); + }, + `polling finalize_migration until complete`, + log + ); const indicesAfter = statusAfter.map((s) => s.index); @@ -145,17 +154,21 @@ export default ({ getService }: FtrProviderContext): void => { createdMigrations = [...createdMigrations, ...body.indices]; let finalizeResponse: FinalizeResponse[]; - await waitFor(async () => { - ({ - body: { migrations: finalizeResponse }, - } = await supertest - .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ migration_ids: createdMigrations.map((m) => m.migration_id) }) - .expect(200)); - - return finalizeResponse.every((index) => index.completed); - }, `polling finalize_migration until all complete`); + await waitFor( + async () => { + ({ + body: { migrations: finalizeResponse }, + } = await supertest + .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) + .set('kbn-xsrf', 'true') + .send({ migration_ids: createdMigrations.map((m) => m.migration_id) }) + .expect(200)); + + return finalizeResponse.every((index) => index.completed); + }, + `polling finalize_migration until all complete`, + log + ); const { body: bodyAfter } = await supertest .get(DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL) @@ -171,19 +184,23 @@ export default ({ getService }: FtrProviderContext): void => { }); it.skip('deletes the underlying migration task', async () => { - await waitFor(async () => { - const { - body: { - migrations: [{ completed }], - }, - } = await supertest - .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ migration_ids: [createdMigration.migration_id] }) - .expect(200); - - return completed; - }, `polling finalize_migration until complete`); + await waitFor( + async () => { + const { + body: { + migrations: [{ completed }], + }, + } = await supertest + .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) + .set('kbn-xsrf', 'true') + .send({ migration_ids: [createdMigration.migration_id] }) + .expect(200); + + return completed; + }, + `polling finalize_migration until complete`, + log + ); // const [{ taskId }] = await getMigration({ id: migration.migration_id }); // expect(taskId.length).greaterThan(0); @@ -192,19 +209,23 @@ export default ({ getService }: FtrProviderContext): void => { }); it('subsequent attempts at finalization are idempotent', async () => { - await waitFor(async () => { - const { - body: { - migrations: [{ completed }], - }, - } = await supertest - .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ migration_ids: [createdMigration.migration_id] }) - .expect(200); - - return completed; - }, `polling finalize_migration until complete`); + await waitFor( + async () => { + const { + body: { + migrations: [{ completed }], + }, + } = await supertest + .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) + .set('kbn-xsrf', 'true') + .send({ migration_ids: [createdMigration.migration_id] }) + .expect(200); + + return completed; + }, + `polling finalize_migration until complete`, + log + ); const { body } = await supertest .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts index 30e1b3eef714a1..a56403ce54252b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts @@ -18,21 +18,23 @@ import { getComplexRuleOutput, getSimpleRule, getSimpleRuleOutput, + getWebHookAction, removeServerGeneratedProperties, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('find_rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should return an empty find body correctly if no rules are loaded', async () => { @@ -51,7 +53,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return a single rule when a single rule is loaded from a find with defaults added', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); // query the single rule from _find const { body } = await supertest @@ -92,5 +94,164 @@ export default ({ getService }: FtrProviderContext): void => { total: 1, }); }); + + it('should find a single rule with a execute immediately action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + await createRule(supertest, log, rule); + + // query the single rule from _find + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + const ruleWithActions: ReturnType = { + ...getSimpleRuleOutput(), + actions: [action], + throttle: 'rule', + }; + + body.data = [removeServerGeneratedProperties(body.data[0])]; + expect(body).to.eql({ + data: [ruleWithActions], + page: 1, + perPage: 20, + total: 1, + }); + }); + + it('should be able to find a scheduled action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + throttle: '1h', // <-- throttle makes this a scheduled action + actions: [action], + }; + await createRule(supertest, log, rule); + + // query the single rule from _find + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + const ruleWithActions: ReturnType = { + ...getSimpleRuleOutput(), + actions: [action], + throttle: '1h', // <-- throttle makes this a scheduled action + }; + + body.data = [removeServerGeneratedProperties(body.data[0])]; + expect(body).to.eql({ + data: [ruleWithActions], + page: 1, + perPage: 20, + total: 1, + }); + }); + + /** + * Tests the legacy actions to ensure we can export legacy notifications + * @deprecated Once the legacy notification system is removed, remove this test too. + */ + describe('legacy_notification_system', async () => { + it('should be able to a read a scheduled action correctly', async () => { + // create an connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule without actions + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); + + // attach the legacy notification + await supertest + .post(`/internal/api/detection/legacy/notifications?alert_id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .send({ + name: 'Legacy notification with one action', + interval: '1h', + actions: [ + { + id: hookAction.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction.actionTypeId, + }, + ], + }) + .expect(200); + + // query the single rule from _find + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + const ruleWithActions: ReturnType = { + ...getSimpleRuleOutput(), + actions: [ + { + id: hookAction.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + action_type_id: hookAction.actionTypeId, + }, + ], + throttle: '1h', + }; + + body.data = [removeServerGeneratedProperties(body.data[0])]; + expect(body).to.eql({ + data: [ruleWithActions], + page: 1, + perPage: 20, + total: 1, + }); + }); + }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts index c2dfed0c495db0..0b430d6ae2d921 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts @@ -24,6 +24,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('find_statuses', () => { before(async () => { @@ -35,13 +36,13 @@ export default ({ getService }: FtrProviderContext): void => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); - await deleteAllRulesStatuses(es); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + await deleteAllRulesStatuses(es, log); }); it('should return an empty find statuses body correctly if no statuses are loaded', async () => { @@ -74,9 +75,9 @@ export default ({ getService }: FtrProviderContext): void => { this pops up again elsewhere. */ it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); + const resBody = await createRule(supertest, log, getSimpleRule('rule-1', true)); - await waitForRuleSuccessOrStatus(supertest, resBody.id); + await waitForRuleSuccessOrStatus(supertest, log, resBody.id); // query the single rule from _find const { body } = await supertest 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 2977037a9523fd..81291b3d461185 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 @@ -46,15 +46,15 @@ import { waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../utils'; +import { Ancestor } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; import { ALERT_ANCESTORS, ALERT_DEPTH, - ALERT_GROUP_ID, + ALERT_ORIGINAL_TIME, ALERT_ORIGINAL_EVENT, ALERT_ORIGINAL_EVENT_CATEGORY, - ALERT_ORIGINAL_TIME, -} from '../../../../plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/field_names'; -import { Ancestor } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; + ALERT_GROUP_ID, +} from '../../../../plugins/security_solution/common/field_maps/field_names'; /** * Specific _id to use for some of the tests. If the archiver changes and you see errors @@ -67,16 +67,17 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); + const log = getService('log'); describe('Generating signals from source indexes', () => { beforeEach(async () => { - await deleteSignalsIndex(supertest); - await createSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); describe('Signals from audit beat are of the expected structure', () => { @@ -93,10 +94,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); }); @@ -106,10 +107,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['auditbeat-*']), max_signals: maxSignals, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, maxSignals, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id], maxSignals); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, maxSignals, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id], maxSignals); expect(signalsOpen.hits.hits.length).equal(maxSignals); }); @@ -118,10 +119,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits[0]._source![ALERT_RULE_RULE_ID]).eql(getSimpleRule().rule_id); }); @@ -130,10 +131,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signal = signalsOpen.hits.hits[0]._source!; expect(signal).eql({ @@ -165,10 +166,10 @@ export default ({ getService }: FtrProviderContext) => { query: `_id:${ID}`, saved_id: 'doesnt-exist', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signal = signalsOpen.hits.hits[0]._source!; expect(signal).eql({ ...signal, @@ -197,9 +198,9 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - const { id: createdId } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, createdId); - await waitForSignalsToBePresent(supertest, 1, [createdId]); + const { id: createdId } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, createdId); + await waitForSignalsToBePresent(supertest, log, 1, [createdId]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal const ruleForSignals: QueryCreateSchema = { @@ -207,12 +208,12 @@ export default ({ getService }: FtrProviderContext) => { rule_id: 'signal-on-signal', }; - const { id } = await createRule(supertest, ruleForSignals); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); + const { id } = await createRule(supertest, log, ruleForSignals); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); // Get our single signal on top of a signal - const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); + const signalsOpen = await getSignalsByRuleIds(supertest, log, ['signal-on-signal']); const signal = signalsOpen.hits.hits[0]._source!; expect(signal).eql({ @@ -249,10 +250,10 @@ export default ({ getService }: FtrProviderContext) => { ...getEqlRuleForSignalTesting(['auditbeat-*']), query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); expect(signals.hits.hits.length).eql(1); const fullSignal = signals.hits.hits[0]._source; if (!fullSignal) { @@ -350,10 +351,10 @@ export default ({ getService }: FtrProviderContext) => { it('generates up to max_signals for non-sequence EQL queries', async () => { const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 100, [id]); - const signals = await getSignalsByIds(supertest, [id], 1000); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 100, [id]); + const signals = await getSignalsByIds(supertest, log, [id], 1000); const filteredSignals = signals.hits.hits.filter( (signal) => signal._source?.[ALERT_DEPTH] === 1 ); @@ -366,10 +367,10 @@ export default ({ getService }: FtrProviderContext) => { query: 'config_change where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', event_category_override: 'auditd.message_type', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); expect(signals.hits.hits.length).eql(1); const fullSignal = signals.hits.hits[0]._source; if (!fullSignal) { @@ -438,10 +439,10 @@ export default ({ getService }: FtrProviderContext) => { ...getEqlRuleForSignalTesting(['auditbeat-*']), query: 'sequence by host.name [anomoly where true] [any where true]', // TODO: spelling }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signals = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signals = await getSignalsByIds(supertest, log, [id]); const buildingBlock = signals.hits.hits.find( (signal) => signal._source?.[ALERT_DEPTH] === 1 && @@ -586,10 +587,10 @@ export default ({ getService }: FtrProviderContext) => { ...getEqlRuleForSignalTesting(['auditbeat-*']), query: 'sequence by host.name [anomoly where true] [any where true]', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const sequenceSignal = signalsOpen.hits.hits.find( (signal) => signal._source?.[ALERT_DEPTH] === 2 ); @@ -672,13 +673,13 @@ export default ({ getService }: FtrProviderContext) => { ...getEqlRuleForSignalTesting(['auditbeat-*']), query: 'sequence by host.name [any where true] [any where true]', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); // For EQL rules, max_signals is the maximum number of detected sequences: each sequence has a building block // alert for each event in the sequence, so max_signals=100 results in 200 building blocks in addition to // 100 regular alerts - await waitForSignalsToBePresent(supertest, 300, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id], 1000); + await waitForSignalsToBePresent(supertest, log, 300, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id], 1000); expect(signalsOpen.hits.hits.length).eql(300); const shellSignals = signalsOpen.hits.hits.filter( (signal) => signal._source?.[ALERT_DEPTH] === 2 @@ -700,10 +701,10 @@ export default ({ getService }: FtrProviderContext) => { value: 700, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).eql(1); const fullSignal = signalsOpen.hits.hits[0]._source; if (!fullSignal) { @@ -748,10 +749,10 @@ export default ({ getService }: FtrProviderContext) => { value: 100, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).eql(2); }); @@ -764,10 +765,10 @@ export default ({ getService }: FtrProviderContext) => { value: 21, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).eql(1); }); @@ -785,8 +786,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const createdRule = await createRule(supertest, rule); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const createdRule = await createRule(supertest, log, rule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -804,8 +805,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const createdRule = await createRule(supertest, rule); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const createdRule = await createRule(supertest, log, rule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -823,8 +824,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const createdRule = await createRule(supertest, rule); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const createdRule = await createRule(supertest, log, rule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).eql(1); const fullSignal = signalsOpen.hits.hits[0]._source; if (!fullSignal) { @@ -875,8 +876,8 @@ export default ({ getService }: FtrProviderContext) => { value: 22, }, }; - const createdRule = await createRule(supertest, rule); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const createdRule = await createRule(supertest, log, rule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -888,8 +889,8 @@ export default ({ getService }: FtrProviderContext) => { value: 21, }, }; - const createdRule = await createRule(supertest, rule); - const signalsOpen = await getOpenSignals(supertest, es, createdRule); + const createdRule = await createRule(supertest, log, rule); + const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); expect(signalsOpen.hits.hits.length).eql(1); const fullSignal = signalsOpen.hits.hits[0]._source; if (!fullSignal) { @@ -955,10 +956,10 @@ export default ({ getService }: FtrProviderContext) => { }); const executeRuleAndGetSignals = async (rule: QueryCreateSchema) => { - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id]); const signals = signalsResponse.hits.hits.map((hit) => hit._source); const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); return signalsOrderedByEventId; @@ -1102,13 +1103,13 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await deleteSignalsIndex(supertest); - await createSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should generate signals with name_override field', async () => { @@ -1117,11 +1118,11 @@ export default ({ getService }: FtrProviderContext) => { rule_name_override: 'event.action', }; - const { id } = await createRule(supertest, rule); + const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id], 1); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id], 1); const signals = signalsResponse.hits.hits.map((hit) => hit._source); const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); const fullSignal = signalsOrderedByEventId[0]; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts index 6b226dae9cb11d..6764ed27a43e77 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts @@ -24,16 +24,17 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const log = getService('log'); describe('get_prepackaged_rules_status', () => { describe('getting prepackaged rules status', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts index 1f7d1054b706ee..a1400e865fa567 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts @@ -18,6 +18,7 @@ export default ({ getService }: FtrProviderContext): void => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); describe('Signals migration status', () => { let legacySignalsIndexName: string; @@ -25,12 +26,12 @@ export default ({ getService }: FtrProviderContext): void => { legacySignalsIndexName = getIndexNameFromLoad( await esArchiver.load('x-pack/test/functional/es_archives/signals/legacy_signals_index') ); - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/signals/legacy_signals_index'); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); it('returns no indexes if no signals exist in the specified range', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/ignore_fields.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/ignore_fields.ts index 409128523ea407..d7528fbf6e8f55 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/ignore_fields.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/ignore_fields.ts @@ -50,6 +50,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('ignore_fields', () => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ignore_fields'); @@ -60,21 +61,21 @@ export default ({ getService }: FtrProviderContext): void => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should ignore the field of "testing_ignored"', async () => { const rule = getEqlRuleForSignalTesting(['ignore_fields']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits .map((hit) => (hit._source as Ignore).testing_ignored) .sort(); @@ -86,10 +87,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should ignore the field of "testing_regex"', async () => { const rule = getEqlRuleForSignalTesting(['ignore_fields']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => (hit._source as Ignore).testing_regex).sort(); // Value should be "undefined for all records" @@ -99,10 +100,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should have the field of "normal_constant"', async () => { const rule = getEqlRuleForSignalTesting(['ignore_fields']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits .map((hit) => (hit._source as Ignore).normal_constant) .sort(); @@ -115,10 +116,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should ignore the field of "_ignored" when using EQL and index the data', async () => { const rule = getEqlRuleForSignalTesting(['ignore_fields']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => (hit._source as Ignore).small_field).sort(); // We just test a constant value to ensure this did not blow up on us and did index data. 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 654bd4d79b7c37..9a9d4f7758d077 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 @@ -16,6 +16,7 @@ import { getSimpleRule, getSimpleRuleAsNdjson, getSimpleRuleOutput, + getWebHookAction, removeServerGeneratedProperties, ruleToNdjson, } from '../../utils'; @@ -23,16 +24,17 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('import_rules', () => { describe('importing rules with an index', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should set the response content types to be expected', async () => { @@ -315,6 +317,165 @@ export default ({ getService }: FtrProviderContext): void => { getSimpleRuleOutput('rule-3'), ]); }); + + it('should give single connector error back if we have a single connector error message', async () => { + const simpleRule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [ + { + group: 'default', + id: '123', + action_type_id: '456', + params: {}, + }, + ], + }; + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', ruleToNdjson(simpleRule), 'rules.ndjson') + .expect(200); + + expect(body).to.eql({ + success: false, + success_count: 0, + errors: [ + { + rule_id: 'rule-1', + error: { + status_code: 404, + message: '1 connector is missing. Connector id missing is: 123', + }, + }, + ], + }); + }); + + it('should be able to import a rule with an action connector that exists', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + const simpleRule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [ + { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }; + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', ruleToNdjson(simpleRule), 'rules.ndjson') + .expect(200); + expect(body).to.eql({ success: true, success_count: 1, errors: [] }); + }); + + it('should be able to import 2 rules with action connectors that exist', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [ + { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }; + + const rule2: ReturnType = { + ...getSimpleRule('rule-2'), + actions: [ + { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }; + const rule1String = JSON.stringify(rule1); + const rule2String = JSON.stringify(rule2); + const buffer = Buffer.from(`${rule1String}\n${rule2String}\n`); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + + expect(body).to.eql({ success: true, success_count: 2, errors: [] }); + }); + + it('should be able to import 1 rule with an action connector that exists and get 1 other error back for a second rule that does not have the connector', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [ + { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }; + + const rule2: ReturnType = { + ...getSimpleRule('rule-2'), + actions: [ + { + group: 'default', + id: '123', // <-- This does not exist + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }; + const rule1String = JSON.stringify(rule1); + const rule2String = JSON.stringify(rule2); + const buffer = Buffer.from(`${rule1String}\n${rule2String}\n`); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + + expect(body).to.eql({ + success: false, + success_count: 1, + errors: [ + { + rule_id: 'rule-2', + error: { + status_code: 404, + message: '1 connector is missing. Connector id missing is: 123', + }, + }, + ], + }); + }); }); }); }; 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 31ecf6edb9bb25..db616baa0678ac 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 @@ -19,6 +19,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./update_actions')); loadTestFile(require.resolve('./add_prepackaged_rules')); loadTestFile(require.resolve('./check_privileges')); + loadTestFile(require.resolve('./create_index')); loadTestFile(require.resolve('./create_rules')); loadTestFile(require.resolve('./create_rules_bulk')); loadTestFile(require.resolve('./create_ml')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts index 152b8100f9aa9e..80e85fb491fc18 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts @@ -29,6 +29,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule detects against a keyword of event.dataset', () => { before(async () => { @@ -42,12 +43,12 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); describe('"kql" rule type', () => { @@ -56,10 +57,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['const_keyword']), query: 'event.dataset: "dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); expect(signalsOpen.hits.hits.length).to.eql(4); }); @@ -68,10 +69,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['const_keyword']), query: 'event.dataset: "dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort(); expect(hits).to.eql([ 'dataset_name_1', @@ -89,10 +90,10 @@ export default ({ getService }: FtrProviderContext) => { query: 'any where event.dataset=="dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); expect(signalsOpen.hits.hits.length).to.eql(4); }); @@ -102,10 +103,10 @@ export default ({ getService }: FtrProviderContext) => { query: 'any where event.dataset=="dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort(); expect(hits).to.eql([ 'dataset_name_1', @@ -125,10 +126,10 @@ export default ({ getService }: FtrProviderContext) => { value: 1, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits .map((hit) => hit._source?.threshold_result ?? null) .sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts index a3004d8942922d..bdfe496bd3fa65 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts @@ -30,6 +30,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule detects against a keyword of event.dataset', () => { before(async () => { @@ -41,12 +42,12 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); describe('"kql" rule type', () => { @@ -55,10 +56,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['keyword']), query: 'event.dataset: "dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort(); expect(hits).to.eql([ 'dataset_name_1', @@ -76,10 +77,10 @@ export default ({ getService }: FtrProviderContext) => { query: 'any where event.dataset=="dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort(); expect(hits).to.eql([ 'dataset_name_1', @@ -99,10 +100,10 @@ export default ({ getService }: FtrProviderContext) => { value: 1, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits .map((hit) => hit._source?.threshold_result ?? null) .sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts index f33ce52318579b..4076a34b6139b9 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts @@ -28,6 +28,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('Rule detects against a keyword and constant_keyword of event.dataset', () => { before(async () => { @@ -43,12 +44,12 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); describe('"kql" rule type', () => { @@ -57,10 +58,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['keyword', 'const_keyword']), query: 'event.dataset: "dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 8, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 8, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); expect(signalsOpen.hits.hits.length).to.eql(8); }); @@ -69,10 +70,10 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['keyword', 'const_keyword']), query: 'event.dataset: "dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 8, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 8, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort(); expect(hits).to.eql([ 'dataset_name_1', @@ -94,10 +95,10 @@ export default ({ getService }: FtrProviderContext) => { query: 'any where event.dataset=="dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 8, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 8, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); expect(signalsOpen.hits.hits.length).to.eql(8); }); @@ -107,10 +108,10 @@ export default ({ getService }: FtrProviderContext) => { query: 'any where event.dataset=="dataset_name_1"', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 8, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 8, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort(); expect(hits).to.eql([ 'dataset_name_1', @@ -138,10 +139,10 @@ export default ({ getService }: FtrProviderContext) => { value: 1, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits .map((hit) => hit._source?.threshold_result ?? null) .sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts index 49b8bc640cde31..f9a5a3283bd2fb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts @@ -36,6 +36,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const log = getService('log'); describe('open_close_signals', () => { describe.skip('validation checks', () => { @@ -53,7 +54,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not give errors when querying and the signals index does exist and is empty', async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); const { body } = await supertest .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) .set('kbn-xsrf', 'true') @@ -65,7 +66,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql(getSignalStatusEmptyResponse()); - await deleteSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); }); describe('tests with auditbeat data', () => { @@ -78,30 +79,30 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await deleteAllAlerts(supertest); - await createSignalsIndex(supertest); + await deleteAllAlerts(supertest, log); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to execute and get 10 signals', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); it('should be have set the signals in an open state initially', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const everySignalOpen = signalsOpen.hits.hits.every( (hit) => hit._source?.[ALERT_WORKFLOW_STATUS] === 'open' ); @@ -110,10 +111,10 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to get a count of 10 closed signals when closing 10', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 10, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -136,10 +137,10 @@ export default ({ getService }: FtrProviderContext) => { it('should be able close signals immediately and they all should be closed', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -166,11 +167,11 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to close signals with t1 analyst user', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); await createUserAndRole(getService, ROLES.t1_analyst); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // Try to set all of the signals to the state of closed. @@ -201,12 +202,12 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to close signals with soc_manager user', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 1, [id]); const userAndRole = ROLES.soc_manager; await createUserAndRole(getService, userAndRole); - const signalsOpen = await getSignalsByIds(supertest, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // Try to set all of the signals to the state of closed. diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts index d20eb0492bbc4a..7867fc2fd16e4d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts @@ -21,25 +21,27 @@ import { getSimpleMlRuleOutput, createRule, getSimpleMlRule, + createLegacyRuleAction, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('patch_rules', () => { describe('patch rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should patch a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -56,7 +58,7 @@ export default ({ getService }: FtrProviderContext) => { }); it("should patch a machine_learning rule's job ID if in a legacy format", async () => { - await createRule(supertest, getSimpleMlRule('rule-1')); + await createRule(supertest, log, getSimpleMlRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -72,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch a single rule property of name using a rule_id of type "machine learning"', async () => { - await createRule(supertest, getSimpleMlRule('rule-1')); + await createRule(supertest, log, getSimpleMlRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -91,7 +93,7 @@ export default ({ getService }: FtrProviderContext) => { it('should patch a single rule property of name using the auto-generated rule_id', async () => { const rule = getSimpleRule('rule-1'); delete rule.rule_id; - const createRuleBody = await createRule(supertest, rule); + const createRuleBody = await createRule(supertest, log, rule); // patch a simple rule's name const { body } = await supertest @@ -108,7 +110,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -125,7 +127,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change the version of a rule when it patches only enabled', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false const { body } = await supertest @@ -142,7 +144,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it patches enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false and another property const { body } = await supertest @@ -161,7 +163,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change other properties when it does patches', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's timeline_title await supertest @@ -187,6 +189,46 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare).to.eql(outputRule); }); + it('should return the rule with migrated actions after the enable patch', async () => { + const [connector, rule] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule('rule-1')), + ]); + await createLegacyRuleAction(supertest, rule.id, connector.body.id); + + // patch disable the rule + const patchResponse = await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send({ id: rule.id, enabled: false }) + .expect(200); + + const outputRule = getSimpleRuleOutput(); + outputRule.actions = [ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ]; + outputRule.throttle = '1m'; + const bodyToCompare = removeServerGeneratedProperties(patchResponse.body); + expect(bodyToCompare).to.eql(outputRule); + }); + it('should give a 404 if it is given a fake id', async () => { const { body } = await supertest .patch(DETECTION_ENGINE_RULES_URL) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts index 74d3bc8dd68d32..d3128c66704024 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts @@ -19,25 +19,27 @@ import { getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, createRule, + createLegacyRuleAction, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('patch_rules_bulk', () => { describe('patch rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should patch a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -54,8 +56,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch two rule properties of name using the two rules rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); - await createRule(supertest, getSimpleRule('rule-2')); + await createRule(supertest, log, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-2')); // patch both rule names const { body } = await supertest @@ -82,7 +84,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch a single rule property of name using an id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRule('rule-1')); + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -99,8 +101,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch two rule properties of name using the two rules id', async () => { - const createRule1 = await createRule(supertest, getSimpleRule('rule-1')); - const createRule2 = await createRule(supertest, getSimpleRule('rule-2')); + const createRule1 = await createRule(supertest, log, getSimpleRule('rule-1')); + const createRule2 = await createRule(supertest, log, getSimpleRule('rule-2')); // patch both rule names const { body } = await supertest @@ -126,8 +128,57 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare2).to.eql(outputRule2); }); + it('should bulk disable two rules and migrate their actions', async () => { + const [connector, rule1, rule2] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule('rule-1')), + createRule(supertest, log, getSimpleRule('rule-2')), + ]); + await Promise.all([ + createLegacyRuleAction(supertest, rule1.id, connector.body.id), + createLegacyRuleAction(supertest, rule2.id, connector.body.id), + ]); + // patch a simple rule's name + const { body } = await supertest + .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .set('kbn-xsrf', 'true') + .send([ + { id: rule1.id, enabled: false }, + { id: rule2.id, enabled: false }, + ]) + .expect(200); + + // @ts-expect-error + body.forEach((response) => { + const outputRule = getSimpleRuleOutput(response.rule_id, false); + outputRule.actions = [ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ]; + outputRule.throttle = '1m'; + const bodyToCompare = removeServerGeneratedProperties(response); + expect(bodyToCompare).to.eql(outputRule); + }); + }); + it('should patch a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's name const { body } = await supertest @@ -144,7 +195,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change the version of a rule when it patches only enabled', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false const { body } = await supertest @@ -161,7 +212,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it patches enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's enabled to false and another property const { body } = await supertest @@ -180,7 +231,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change other properties when it does patches', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's timeline_title await supertest @@ -240,7 +291,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch one rule property and give an error about a second fake rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // patch one rule name and give a fake id for the second const { body } = await supertest @@ -270,7 +321,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should patch one rule property and give an error about a second fake id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // patch one rule name and give a fake id for the second const { body } = await supertest 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 83166619b152dc..bb117b50d5aed0 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 @@ -27,19 +27,20 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('perform_bulk_action', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should export rules', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .post(DETECTION_ENGINE_RULES_BULK_ACTION) @@ -59,6 +60,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(exportDetails).to.eql({ exported_exception_list_count: 0, exported_exception_list_item_count: 0, + exported_count: 1, exported_rules_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], @@ -71,7 +73,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should delete rules', async () => { const ruleId = 'ruleId'; - await createRule(supertest, getSimpleRule(ruleId)); + await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await supertest .post(DETECTION_ENGINE_RULES_BULK_ACTION) @@ -89,7 +91,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should enable rules', async () => { const ruleId = 'ruleId'; - await createRule(supertest, getSimpleRule(ruleId)); + await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await supertest .post(DETECTION_ENGINE_RULES_BULK_ACTION) @@ -114,7 +116,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should disable rules', async () => { const ruleId = 'ruleId'; - await createRule(supertest, getSimpleRule(ruleId, true)); + await createRule(supertest, log, getSimpleRule(ruleId, true)); const { body } = await supertest .post(DETECTION_ENGINE_RULES_BULK_ACTION) @@ -137,7 +139,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should duplicate rules', async () => { const ruleId = 'ruleId'; - await createRule(supertest, getSimpleRule(ruleId)); + await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await supertest .post(DETECTION_ENGINE_RULES_BULK_ACTION) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts index 66a8459d0984cf..92cc8cf52f18f2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts @@ -18,6 +18,7 @@ import { getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, + getWebHookAction, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, } from '../../utils'; @@ -25,20 +26,21 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('read_rules', () => { describe('reading rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to read a single rule using rule_id', async () => { - await createRule(supertest, getSimpleRule()); + await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) @@ -51,7 +53,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to read a single rule using id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRule()); + const createRuleBody = await createRule(supertest, log, getSimpleRule()); const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) @@ -64,7 +66,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to read a single rule with an auto-generated rule_id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRuleWithoutRuleId()); + const createRuleBody = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${createRuleBody.rule_id}`) @@ -101,6 +103,146 @@ export default ({ getService }: FtrProviderContext) => { message: 'rule_id: "fake_id" not found', }); }); + + it('should be able to a read a execute immediately action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + const createRuleBody = await createRule(supertest, log, rule); + + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const ruleWithActions: ReturnType = { + ...getSimpleRuleOutput(), + actions: [action], + throttle: 'rule', + }; + expect(bodyToCompare).to.eql(ruleWithActions); + }); + + it('should be able to a read a scheduled action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + throttle: '1h', // <-- throttle makes this a scheduled action + actions: [action], + }; + + const createRuleBody = await createRule(supertest, log, rule); + + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const ruleWithActions: ReturnType = { + ...getSimpleRuleOutput(), + actions: [action], + throttle: '1h', // <-- throttle makes this a scheduled action + }; + expect(bodyToCompare).to.eql(ruleWithActions); + }); + + /** + * Tests the legacy actions to ensure we can export legacy notifications + * @deprecated Once the legacy notification system is removed, remove this test too. + */ + describe('legacy_notification_system', () => { + it('should be able to a read a scheduled action correctly', async () => { + // create an action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule without actions + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); + + // attach the legacy notification + await supertest + .post(`/internal/api/detection/legacy/notifications?alert_id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .send({ + name: 'Legacy notification with one action', + interval: '1h', + actions: [ + { + id: hookAction.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: hookAction.actionTypeId, + }, + ], + }) + .expect(200); + + // read the rule which should have the legacy actions attached + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const ruleWithActions: ReturnType = { + ...getSimpleRuleOutput(), + actions: [ + { + id: hookAction.id, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + action_type_id: hookAction.actionTypeId, + }, + ], + throttle: '1h', + }; + expect(bodyToCompare).to.eql(ruleWithActions); + }); + }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts index 440672e33d8ed3..a7333ef8716f2b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts @@ -18,19 +18,20 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const es = getService('es'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('resolve_read_rules', () => { describe('reading rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); await esArchiver.load( 'x-pack/test/functional/es_archives/security_solution/resolve_read_rules/7_14' ); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload( 'x-pack/test/functional/es_archives/security_solution/resolve_read_rules/7_14' ); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts index 528a99715c05c2..e6d1ef69f09139 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts @@ -23,6 +23,8 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); + interface Runtime { name: string; hostname: string; @@ -39,48 +41,50 @@ export default ({ getService }: FtrProviderContext) => { describe('Regular runtime field mappings', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should copy normal non-runtime data set from the source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['runtime']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); - const hits = signalsOpen.hits.hits.map((signal) => (signal._source?.host as Runtime).name); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); + const hits = signalsOpen.hits.hits + .map((signal) => (signal._source?.host as Runtime).name) + .sort(); expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']); }); it('should copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['runtime']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); - const hits = signalsOpen.hits.hits.map( - (signal) => (signal._source?.host as Runtime).hostname - ); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); + const hits = signalsOpen.hits.hits + .map((signal) => (signal._source?.host as Runtime).hostname) + .sort(); expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']); }); }); describe('Runtime field mappings that have conflicts within them', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); await esArchiver.load( 'x-pack/test/functional/es_archives/security_solution/runtime_conflicting_fields' ); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload( 'x-pack/test/functional/es_archives/security_solution/runtime_conflicting_fields' ); @@ -93,11 +97,20 @@ export default ({ getService }: FtrProviderContext) => { */ it('should NOT copy normal non-runtime data set from the source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['runtime_conflicting_fields']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); - const hits = signalsOpen.hits.hits.map((signal) => signal._source?.host); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); + const hits = signalsOpen.hits.hits + .map((signal) => signal._source?.host as Array<{ name: string }>) + .map((host) => { + // sort the inner array elements first + return host.sort((a, b) => a.name.localeCompare(b.name)); + }) + .sort((aArray, bArray) => { + // since these are all unique, using just the first element should give us stability + return aArray[0].name.localeCompare(bArray[0].name); + }); expect(hits).to.eql([ [ { @@ -141,10 +154,10 @@ export default ({ getService }: FtrProviderContext) => { */ it('should NOT copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['runtime_conflicting_fields']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 4, [id]); - const signalsOpen = await getSignalsById(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map( (signal) => (signal._source?.host as Runtime).hostname ); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/throttle.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/throttle.ts index f0fef839482ce4..ec12446f1f9df1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/throttle.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/throttle.ts @@ -29,6 +29,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); /** * @@ -46,12 +47,12 @@ export default ({ getService }: FtrProviderContext) => { describe('throttle', () => { describe('adding actions', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); describe('creating a rule', () => { @@ -63,7 +64,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id)); + const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id)); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${rule.id}`); @@ -76,7 +77,7 @@ export default ({ getService }: FtrProviderContext) => { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_NO_ACTIONS, }; - const rule = await createRule(supertest, ruleWithThrottle); + const rule = await createRule(supertest, log, ruleWithThrottle); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${rule.id}`); @@ -96,7 +97,7 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleWithWebHookAction(hookAction.id), throttle: NOTIFICATION_THROTTLE_NO_ACTIONS, }; - const rule = await createRule(supertest, ruleWithThrottle); + const rule = await createRule(supertest, log, ruleWithThrottle); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${rule.id}`); @@ -109,7 +110,7 @@ export default ({ getService }: FtrProviderContext) => { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_RULE, }; - const rule = await createRule(supertest, ruleWithThrottle); + const rule = await createRule(supertest, log, ruleWithThrottle); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${rule.id}`); @@ -123,7 +124,7 @@ export default ({ getService }: FtrProviderContext) => { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_RULE, }; - const rule = await createRule(supertest, ruleWithThrottle); + const rule = await createRule(supertest, log, ruleWithThrottle); expect(rule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS); }); @@ -139,7 +140,7 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleWithWebHookAction(hookAction.id), throttle: NOTIFICATION_THROTTLE_RULE, }; - const rule = await createRule(supertest, ruleWithThrottle); + const rule = await createRule(supertest, log, ruleWithThrottle); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${rule.id}`); @@ -152,7 +153,7 @@ export default ({ getService }: FtrProviderContext) => { ...getSimpleRule(), throttle: '1h', }; - const rule = await createRule(supertest, ruleWithThrottle); + const rule = await createRule(supertest, log, ruleWithThrottle); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${rule.id}`); @@ -172,7 +173,7 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleWithWebHookAction(hookAction.id), throttle: '1h', }; - const rule = await createRule(supertest, ruleWithThrottle); + const rule = await createRule(supertest, log, ruleWithThrottle); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${rule.id}`); @@ -190,8 +191,8 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id)); - const readRule = await getRule(supertest, rule.rule_id); + const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id)); + const readRule = await getRule(supertest, log, rule.rule_id); expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_RULE); }); @@ -200,8 +201,8 @@ export default ({ getService }: FtrProviderContext) => { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_NO_ACTIONS, }; - const rule = await createRule(supertest, ruleWithThrottle); - const readRule = await getRule(supertest, rule.rule_id); + const rule = await createRule(supertest, log, ruleWithThrottle); + const readRule = await getRule(supertest, log, rule.rule_id); expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS); }); @@ -211,8 +212,8 @@ export default ({ getService }: FtrProviderContext) => { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_RULE, }; - const rule = await createRule(supertest, ruleWithThrottle); - const readRule = await getRule(supertest, rule.rule_id); + const rule = await createRule(supertest, log, ruleWithThrottle); + const readRule = await getRule(supertest, log, rule.rule_id); expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS); }); @@ -224,13 +225,13 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id)); + const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id)); await supertest .post(`/api/alerting/rule/${rule.id}/_mute_all`) .set('kbn-xsrf', 'true') .send() .expect(204); - const readRule = await getRule(supertest, rule.rule_id); + const readRule = await getRule(supertest, log, rule.rule_id); expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS); }); }); @@ -245,9 +246,9 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const ruleWithWebHookAction = getRuleWithWebHookAction(hookAction.id); - await createRule(supertest, ruleWithWebHookAction); + await createRule(supertest, log, ruleWithWebHookAction); ruleWithWebHookAction.name = 'some other name'; - const updated = await updateRule(supertest, ruleWithWebHookAction); + const updated = await updateRule(supertest, log, ruleWithWebHookAction); expect(updated.throttle).to.eql(NOTIFICATION_THROTTLE_RULE); }); @@ -260,9 +261,9 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const ruleWithWebHookAction = getRuleWithWebHookAction(hookAction.id); - await createRule(supertest, ruleWithWebHookAction); + await createRule(supertest, log, ruleWithWebHookAction); ruleWithWebHookAction.name = 'some other name'; - const updated = await updateRule(supertest, ruleWithWebHookAction); + const updated = await updateRule(supertest, log, ruleWithWebHookAction); const { body: { mute_all: muteAll, notify_when: notifyWhen }, } = await supertest.get(`/api/alerting/rule/${updated.id}`); @@ -280,9 +281,9 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const ruleWithWebHookAction = getRuleWithWebHookAction(hookAction.id); - await createRule(supertest, ruleWithWebHookAction); + await createRule(supertest, log, ruleWithWebHookAction); ruleWithWebHookAction.actions = []; - const updated = await updateRule(supertest, ruleWithWebHookAction); + const updated = await updateRule(supertest, log, ruleWithWebHookAction); expect(updated.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS); }); }); @@ -297,14 +298,14 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const ruleWithWebHookAction = getRuleWithWebHookAction(hookAction.id); - const rule = await createRule(supertest, ruleWithWebHookAction); + const rule = await createRule(supertest, log, ruleWithWebHookAction); // patch a simple rule's name await supertest .patch(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') .send({ rule_id: rule.rule_id, name: 'some other name' }) .expect(200); - const readRule = await getRule(supertest, rule.rule_id); + const readRule = await getRule(supertest, log, rule.rule_id); expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_RULE); }); @@ -317,7 +318,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const ruleWithWebHookAction = getRuleWithWebHookAction(hookAction.id); - const rule = await createRule(supertest, ruleWithWebHookAction); + const rule = await createRule(supertest, log, ruleWithWebHookAction); // patch a simple rule's name await supertest .patch(DETECTION_ENGINE_RULES_URL) @@ -341,14 +342,14 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const ruleWithWebHookAction = getRuleWithWebHookAction(hookAction.id); - const rule = await createRule(supertest, ruleWithWebHookAction); + const rule = await createRule(supertest, log, ruleWithWebHookAction); // patch a simple rule's action await supertest .patch(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') .send({ rule_id: rule.rule_id, actions: [] }) .expect(200); - const readRule = await getRule(supertest, rule.rule_id); + const readRule = await getRule(supertest, log, rule.rule_id); expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts index f4b91cae364488..e8017591faf0d0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts @@ -11,7 +11,7 @@ import { EqlCreateSchema, QueryCreateSchema, } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; -import { ALERT_ORIGINAL_TIME } from '../../../../plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/field_names'; +import { ALERT_ORIGINAL_TIME } from '../../../../plugins/security_solution/common/field_maps/field_names'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { @@ -34,6 +34,7 @@ function sleep(ms: number) { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); /** * Tests around timestamps within signals such as the copying of timestamps correctly into @@ -43,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { describe('timestamp tests', () => { describe('Signals generated from events with a timestamp in seconds is converted correctly into the forced ISO8601 format when copying', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); await esArchiver.load( 'x-pack/test/functional/es_archives/security_solution/timestamp_in_seconds' ); @@ -53,8 +54,8 @@ export default ({ getService }: FtrProviderContext) => { }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload( 'x-pack/test/functional/es_archives/security_solution/timestamp_in_seconds' ); @@ -66,11 +67,11 @@ export default ({ getService }: FtrProviderContext) => { describe('KQL query', () => { it('should convert the @timestamp which is epoch_seconds into the correct ISO format', async () => { const rule = getRuleForSignalTesting(['timestamp_in_seconds']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); await sleep(5000); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const hits = signalsOpen.hits.hits .map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]) .sort(); @@ -82,11 +83,11 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['myfakeindex-5']), timestamp_override: 'event.ingested', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); await sleep(5000); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const hits = signalsOpen.hits.hits .map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]) .sort(); @@ -97,11 +98,11 @@ export default ({ getService }: FtrProviderContext) => { describe('EQL query', () => { it('should convert the @timestamp which is epoch_seconds into the correct ISO format for EQL', async () => { const rule = getEqlRuleForSignalTesting(['timestamp_in_seconds']); - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); await sleep(5000); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const hits = signalsOpen.hits.hits .map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]) .sort(); @@ -113,11 +114,11 @@ export default ({ getService }: FtrProviderContext) => { ...getEqlRuleForSignalTesting(['myfakeindex-5']), timestamp_override: 'event.ingested', }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); await sleep(5000); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, [id]); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, log, [id]); const hits = signalsOpen.hits.hits .map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]) .sort(); @@ -134,8 +135,8 @@ export default ({ getService }: FtrProviderContext) => { */ describe('Signals generated from events with timestamp override field', async () => { beforeEach(async () => { - await deleteSignalsIndex(supertest); - await createSignalsIndex(supertest); + await deleteSignalsIndex(supertest, log); + await createSignalsIndex(supertest, log); await esArchiver.load( 'x-pack/test/functional/es_archives/security_solution/timestamp_override_1' ); @@ -151,8 +152,8 @@ export default ({ getService }: FtrProviderContext) => { }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); await esArchiver.unload( 'x-pack/test/functional/es_archives/security_solution/timestamp_override_1' ); @@ -174,12 +175,12 @@ export default ({ getService }: FtrProviderContext) => { timestamp_override: 'event.ingested', }; - const { id } = await createRule(supertest, rule); + const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, id, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); await sleep(5000); - await waitForSignalsToBePresent(supertest, 3, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id], 3); + await waitForSignalsToBePresent(supertest, log, 3, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id], 3); const signals = signalsResponse.hits.hits.map((hit) => hit._source); const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); @@ -189,12 +190,12 @@ export default ({ getService }: FtrProviderContext) => { it('should generate 2 signals with @timestamp', async () => { const rule: QueryCreateSchema = getRuleForSignalTesting(['myfa*']); - const { id } = await createRule(supertest, rule); + const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, id, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); await sleep(5000); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id]); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id]); const signals = signalsResponse.hits.hits.map((hit) => hit._source); const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); @@ -206,12 +207,12 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['myfa*']), timestamp_override: 'event.fakeingestfield', }; - const { id } = await createRule(supertest, rule); + const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, id, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); await sleep(5000); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id, id]); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id, id]); const signals = signalsResponse.hits.hits.map((hit) => hit._source); const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); @@ -229,12 +230,12 @@ export default ({ getService }: FtrProviderContext) => { ...getRuleForSignalTesting(['myfakeindex-2']), timestamp_override: 'event.ingested', }; - const { id } = await createRule(supertest, rule); + const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, id); + await waitForRuleSuccessOrStatus(supertest, log, id); await sleep(5000); - await waitForSignalsToBePresent(supertest, 1, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id, id]); + await waitForSignalsToBePresent(supertest, log, 1, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id, id]); const hits = signalsResponse.hits.hits .map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]) .sort(); @@ -246,12 +247,12 @@ export default ({ getService }: FtrProviderContext) => { it('should generate 2 signals with @timestamp', async () => { const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['myfa*']); - const { id } = await createRule(supertest, rule); + const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, id, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); await sleep(5000); - await waitForSignalsToBePresent(supertest, 2, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id]); + await waitForSignalsToBePresent(supertest, log, 2, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id]); const signals = signalsResponse.hits.hits.map((hit) => hit._source); const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); @@ -270,12 +271,12 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); describe('KQL', () => { @@ -297,11 +298,11 @@ export default ({ getService }: FtrProviderContext) => { max_signals: 200, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id, 'partial failure'); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); await sleep(5000); - await waitForSignalsToBePresent(supertest, 200, [id]); - const signalsResponse = await getSignalsByIds(supertest, [id], 200); + await waitForSignalsToBePresent(supertest, log, 200, [id]); + const signalsResponse = await getSignalsByIds(supertest, log, [id], 200); const signals = signalsResponse.hits.hits.map((hit) => hit._source); expect(signals.length).equal(200); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts index 930486dab97bb7..43b982ff537d7f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts @@ -33,6 +33,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('update_actions', () => { describe('updating actions', () => { @@ -45,20 +46,20 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should be able to create a new webhook action and update a rule with the webhook action', async () => { - const hookAction = await createNewAction(supertest); + const hookAction = await createNewAction(supertest, log); const rule = getSimpleRule(); - await createRule(supertest, rule); + await createRule(supertest, log, rule); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, rule); - const updatedRule = await updateRule(supertest, ruleToUpdate); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); const bodyToCompare = removeServerGeneratedProperties(updatedRule); const expected = { @@ -69,12 +70,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to add a new webhook action and then remove the action from the rule again', async () => { - const hookAction = await createNewAction(supertest); + const hookAction = await createNewAction(supertest, log); const rule = getSimpleRule(); - await createRule(supertest, rule); + await createRule(supertest, log, rule); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, rule); - await updateRule(supertest, ruleToUpdate); - const ruleAfterActionRemoved = await updateRule(supertest, rule); + await updateRule(supertest, log, ruleToUpdate); + const ruleAfterActionRemoved = await updateRule(supertest, log, rule); const bodyToCompare = removeServerGeneratedProperties(ruleAfterActionRemoved); const expected = { ...getSimpleRuleOutput(), @@ -84,12 +85,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to create a new webhook action and attach it to a rule without a meta field and run it correctly', async () => { - const hookAction = await createNewAction(supertest); + const hookAction = await createNewAction(supertest, log); const rule = getSimpleRule(); - await createRule(supertest, rule); + await createRule(supertest, log, rule); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, true, rule); - const updatedRule = await updateRule(supertest, ruleToUpdate); - await waitForRuleSuccessOrStatus(supertest, updatedRule.id); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); + await waitForRuleSuccessOrStatus(supertest, log, updatedRule.id); // expected result for status should be 'succeeded' const { body } = await supertest @@ -101,15 +102,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { - const hookAction = await createNewAction(supertest); + const hookAction = await createNewAction(supertest, log); const rule = getSimpleRule(); - await createRule(supertest, rule); + await createRule(supertest, log, rule); const ruleToUpdate: CreateRulesSchema = { ...getRuleWithWebHookAction(hookAction.id, true, rule), meta: {}, // create a rule with the action attached and a meta field }; - const updatedRule = await updateRule(supertest, ruleToUpdate); - await waitForRuleSuccessOrStatus(supertest, updatedRule.id); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); + await waitForRuleSuccessOrStatus(supertest, log, updatedRule.id); // expected result for status should be 'succeeded' const { body } = await supertest @@ -121,14 +122,14 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to create a new webhook action and attach it to an immutable rule', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); - const hookAction = await createNewAction(supertest); + const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const hookAction = await createNewAction(supertest, log); const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); - const updatedRule = await updateRule(supertest, ruleToUpdate); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); const bodyToCompare = removeServerGeneratedProperties(updatedRule); const expected = { @@ -141,29 +142,33 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to create a new webhook action, attach it to an immutable rule and the count of prepackaged rules should not increase. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); - const hookAction = await createNewAction(supertest); + const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const hookAction = await createNewAction(supertest, log); const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); - await updateRule(supertest, ruleToUpdate); + await updateRule(supertest, log, ruleToUpdate); - const status = await getPrePackagedRulesStatus(supertest); + const status = await getPrePackagedRulesStatus(supertest, log); expect(status.rules_not_installed).to.eql(0); }); it('should be able to create a new webhook action, attach it to an immutable rule and the rule should stay immutable when searching against immutable tags', async () => { - await installPrePackagedRules(supertest); + await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json - const immutableRule = await getRule(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); - const hookAction = await createNewAction(supertest); + const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const hookAction = await createNewAction(supertest, log); const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); - await updateRule(supertest, ruleToUpdate); - const body = await findImmutableRuleById(supertest, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + await updateRule(supertest, log, ruleToUpdate); + const body = await findImmutableRuleById( + supertest, + log, + '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); expect(body.data.length).to.eql(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad. const bodyToCompare = removeServerGeneratedProperties(body.data[0]); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts index 5a4a04f71b3d55..d6f69095c43c4a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts @@ -23,25 +23,27 @@ import { getSimpleMlRuleUpdate, createRule, getSimpleRule, + createLegacyRuleAction, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_rules', () => { describe('update rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should update a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -63,7 +65,7 @@ export default ({ getService }: FtrProviderContext) => { }); it("should update a rule's machine learning job ID if given a legacy job ID format", async () => { - await createRule(supertest, getSimpleMlRule('rule-1')); + await createRule(supertest, log, getSimpleMlRule('rule-1')); // update rule's machine_learning_job_id const updatedRule = getSimpleMlRuleUpdate('rule-1'); @@ -85,7 +87,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update a single rule property of name using a rule_id with a machine learning job', async () => { - await createRule(supertest, getSimpleMlRule('rule-1')); + await createRule(supertest, log, getSimpleMlRule('rule-1')); // update a simple rule's name const updatedRule = getSimpleMlRuleUpdate('rule-1'); @@ -109,7 +111,7 @@ export default ({ getService }: FtrProviderContext) => { it('should update a single rule property of name using an auto-generated rule_id', async () => { const rule = getSimpleRule('rule-1'); delete rule.rule_id; - const createRuleBody = await createRule(supertest, rule); + const createRuleBody = await createRule(supertest, log, rule); // update a simple rule's name const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -130,8 +132,57 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare).to.eql(outputRule); }); + it('should update a single rule property of name using an auto-generated rule_id and migrate the actions', async () => { + const rule = getSimpleRule('rule-1'); + delete rule.rule_id; + const [connector, createRuleBody] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, rule), + ]); + await createLegacyRuleAction(supertest, createRuleBody.id, connector.body.id); + + // update a simple rule's name + const updatedRule = getSimpleRuleUpdate('rule-1'); + updatedRule.rule_id = createRuleBody.rule_id; + updatedRule.name = 'some other name'; + delete updatedRule.id; + + const { body } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(updatedRule) + .expect(200); + + const outputRule = getSimpleRuleOutputWithoutRuleId(); + outputRule.name = 'some other name'; + outputRule.version = 2; + outputRule.actions = [ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ]; + outputRule.throttle = '1m'; + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); + expect(bodyToCompare).to.eql(outputRule); + }); + it('should update a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -153,7 +204,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it updates enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's enabled to false and another property const updatedRule = getSimpleRuleUpdate('rule-1'); @@ -176,7 +227,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.timeline_title = 'some title'; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts index 7612aafb5bb607..217debca079dc4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts @@ -20,25 +20,27 @@ import { getSimpleRuleUpdate, createRule, getSimpleRule, + createLegacyRuleAction, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_rules_bulk', () => { describe('update rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest); + await createSignalsIndex(supertest, log); }); afterEach(async () => { - await deleteSignalsIndex(supertest); - await deleteAllAlerts(supertest); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); }); it('should update a single rule property of name using a rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.name = 'some other name'; @@ -58,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update two rule properties of name using the two rules rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // create a second simple rule await supertest @@ -94,8 +96,66 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare2).to.eql(outputRule2); }); + it('should update two rule properties of name using the two rules rule_id and migrate actions', async () => { + const [connector, rule1, rule2] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule('rule-1')), + createRule(supertest, log, getSimpleRule('rule-2')), + ]); + await Promise.all([ + createLegacyRuleAction(supertest, rule1.id, connector.body.id), + createLegacyRuleAction(supertest, rule2.id, connector.body.id), + ]); + + expect(rule1.actions).to.eql([]); + expect(rule2.actions).to.eql([]); + + const updatedRule1 = getSimpleRuleUpdate('rule-1'); + updatedRule1.name = 'some other name'; + + const updatedRule2 = getSimpleRuleUpdate('rule-2'); + updatedRule2.name = 'some other name'; + + // update both rule names + const { body } = await supertest + .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .set('kbn-xsrf', 'true') + .send([updatedRule1, updatedRule2]) + .expect(200); + + // @ts-expect-error + body.forEach((response) => { + const outputRule = getSimpleRuleOutput(response.rule_id); + outputRule.name = 'some other name'; + outputRule.version = 2; + outputRule.actions = [ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }, + ]; + outputRule.throttle = '1m'; + const bodyToCompare = removeServerGeneratedProperties(response); + expect(bodyToCompare).to.eql(outputRule); + }); + }); + it('should update a single rule property of name using an id', async () => { - const createRuleBody = await createRule(supertest, getSimpleRule('rule-1')); + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -117,8 +177,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update two rule properties of name using the two rules id', async () => { - const createRule1 = await createRule(supertest, getSimpleRule('rule-1')); - const createRule2 = await createRule(supertest, getSimpleRule('rule-2')); + const createRule1 = await createRule(supertest, log, getSimpleRule('rule-1')); + const createRule2 = await createRule(supertest, log, getSimpleRule('rule-2')); // update both rule names const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -152,7 +212,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update a single rule property of name using the auto-generated id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's name const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -174,7 +234,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change the version of a rule when it updates enabled and another property', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's enabled to false and another property const updatedRule1 = getSimpleRuleUpdate('rule-1'); @@ -197,7 +257,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's timeline_title const ruleUpdate = getSimpleRuleUpdate('rule-1'); @@ -270,7 +330,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update one rule property and give an error about a second fake rule_id', async () => { - await createRule(supertest, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-1')); const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.name = 'some other name'; @@ -305,7 +365,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update one rule property and give an error about a second fake id', async () => { - const createdBody = await createRule(supertest, getSimpleRule('rule-1')); + const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); // update one rule name and give a fake id for the second const rule1 = getSimpleRuleUpdate(); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 095c4f2cb59d54..cc915324a4a355 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -24,6 +24,7 @@ import type { ExceptionListSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { ToolingLog } from '@kbn/dev-utils'; import { PrePackagedRulesAndTimelinesStatusSchema } from '../../plugins/security_solution/common/detection_engine/schemas/response'; import { CreateRulesSchema, @@ -48,6 +49,7 @@ import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL, INTERNAL_IMMUTABLE_KEY, INTERNAL_RULE_ID_KEY, + UPDATE_OR_CREATE_LEGACY_ACTIONS, } from '../../plugins/security_solution/common/constants'; import { RACAlert } from '../../plugins/security_solution/server/lib/detection_engine/rule_types/types'; @@ -414,7 +416,8 @@ export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial = * @param supertest The supertest agent. */ export const deleteAllAlerts = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { await countDownTest( async () => { @@ -439,33 +442,42 @@ export const deleteAllAlerts = async ( return finalCheck.data.length === 0; }, 'deleteAllAlerts', + log, 50, 1000 ); }; -export const downgradeImmutableRule = async (es: Client, ruleId: string): Promise => { - return countDownES(async () => { - return es.updateByQuery( - { - index: '.kibana', - refresh: true, - wait_for_completion: true, - body: { - script: { - lang: 'painless', - source: 'ctx._source.alert.params.version--', - }, - query: { - term: { - 'alert.tags': `${INTERNAL_RULE_ID_KEY}:${ruleId}`, +export const downgradeImmutableRule = async ( + es: Client, + log: ToolingLog, + ruleId: string +): Promise => { + return countDownES( + async () => { + return es.updateByQuery( + { + index: '.kibana', + refresh: true, + wait_for_completion: true, + body: { + script: { + lang: 'painless', + source: 'ctx._source.alert.params.version--', + }, + query: { + term: { + 'alert.tags': `${INTERNAL_RULE_ID_KEY}:${ruleId}`, + }, }, }, }, - }, - { meta: true } - ); - }, 'downgradeImmutableRule'); + { meta: true } + ); + }, + 'downgradeImmutableRule', + log + ); }; /** @@ -486,20 +498,25 @@ export const deleteAllTimelines = async (es: Client): Promise => { * Remove all rules statuses from the .kibana index * This will retry 20 times before giving up and hopefully still not interfere with other tests * @param es The ElasticSearch handle + * @param log The tooling logger */ -export const deleteAllRulesStatuses = async (es: Client): Promise => { - return countDownES(async () => { - return es.deleteByQuery( - { - index: '.kibana', - q: 'type:siem-detection-engine-rule-status', - wait_for_completion: true, - refresh: true, - body: {}, - }, - { meta: true } - ); - }, 'deleteAllRulesStatuses'); +export const deleteAllRulesStatuses = async (es: Client, log: ToolingLog): Promise => { + return countDownES( + async () => { + return es.deleteByQuery( + { + index: '.kibana', + q: 'type:siem-detection-engine-rule-status', + wait_for_completion: true, + refresh: true, + body: {}, + }, + { meta: true } + ); + }, + 'deleteAllRulesStatuses', + log + ); }; /** @@ -508,25 +525,58 @@ export const deleteAllRulesStatuses = async (es: Client): Promise => { * @param supertest The supertest client library */ export const createSignalsIndex = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { - await countDownTest(async () => { - await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send(); - return true; - }, 'createSignalsIndex'); + await countDownTest( + async () => { + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send(); + return true; + }, + 'createSignalsIndex', + log + ); }; +export const createLegacyRuleAction = async ( + supertest: SuperTest.SuperTest, + alertId: string, + connectorId: string +): Promise => + supertest + .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}`) + .set('kbn-xsrf', 'true') + .query({ alert_id: alertId }) + .send({ + name: 'Legacy notification with one action', + interval: '1m', + actions: [ + { + id: connectorId, + group: 'default', + params: { + message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionTypeId: '.slack', + }, + ], + }); /** * Deletes the signals index for use inside of afterEach blocks of tests * @param supertest The supertest client library */ export const deleteSignalsIndex = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { - await countDownTest(async () => { - await supertest.delete(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send(); - return true; - }, 'deleteSignalsIndex'); + await countDownTest( + async () => { + await supertest.delete(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send(); + return true; + }, + 'deleteSignalsIndex', + log + ); }; /** @@ -785,16 +835,18 @@ export const getSimpleRuleOutputWithWebHookAction = (actionId: string): Partial< export const waitFor = async ( functionToTest: () => Promise, functionName: string, - maxTimeout: number = 20000, - timeoutWait: number = 10 + log: ToolingLog, + maxTimeout: number = 800000, + timeoutWait: number = 250 ): Promise => { let found = false; let numberOfTries = 0; - - while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) { + const maxTries = Math.floor(maxTimeout / timeoutWait); + while (!found && numberOfTries < maxTries) { if (await functionToTest()) { found = true; } else { + log.debug(`Try number ${numberOfTries} out of ${maxTries} for function ${functionName}`); numberOfTries++; } @@ -812,12 +864,14 @@ export const waitFor = async ( * reliant. * @param esFunction The function to test against * @param esFunctionName The name of the function to print if we encounter errors + * @param log The tooling logger * @param retryCount The number of times to retry before giving up (has default) * @param timeoutWait Time to wait before trying again (has default) */ export const countDownES = async ( esFunction: () => Promise, unknown>>, esFunctionName: string, + log: ToolingLog, retryCount: number = 20, timeoutWait = 250 ): Promise => { @@ -825,14 +879,14 @@ export const countDownES = async ( async () => { const result = await esFunction(); if (result.body.version_conflicts !== 0) { - // eslint-disable-next-line no-console - console.log(`Version conflicts for ${result.body.version_conflicts}`); + log.error(`Version conflicts for ${result.body.version_conflicts}`); return false; } else { return true; } }, esFunctionName, + log, retryCount, timeoutWait ); @@ -855,12 +909,14 @@ export const refreshIndex = async (es: Client, index?: string) => { * for testing resiliency. * @param functionToTest The function to test against * @param name The name of the function to print if we encounter errors + * @param log The tooling logger * @param retryCount The number of times to retry before giving up (has default) * @param timeoutWait Time to wait before trying again (has default) */ export const countDownTest = async ( functionToTest: () => Promise, name: string, + log: ToolingLog, retryCount: number = 20, timeoutWait = 250, ignoreThrow: boolean = false @@ -869,30 +925,27 @@ export const countDownTest = async ( try { const passed = await functionToTest(); if (!passed) { - // eslint-disable-next-line no-console - console.log(`Failure trying to ${name}, retries left are: ${retryCount - 1}`); + log.error(`Failure trying to ${name}, retries left are: ${retryCount - 1}`); // retry, counting down, and delay a bit before await new Promise((resolve) => setTimeout(resolve, timeoutWait)); - await countDownTest(functionToTest, name, retryCount - 1, timeoutWait, ignoreThrow); + await countDownTest(functionToTest, name, log, retryCount - 1, timeoutWait, ignoreThrow); } } catch (err) { if (ignoreThrow) { throw err; } else { - // eslint-disable-next-line no-console - console.log( - `Failure trying to ${name}, with exception message of:`, - err.message, - `retries left are: ${retryCount - 1}` + log.error( + `Failure trying to ${name}, with exception message of: ${ + err.message + }, retries left are: ${retryCount - 1}` ); // retry, counting down, and delay a bit before await new Promise((resolve) => setTimeout(resolve, timeoutWait)); - await countDownTest(functionToTest, name, retryCount - 1, timeoutWait, ignoreThrow); + await countDownTest(functionToTest, name, log, retryCount - 1, timeoutWait, ignoreThrow); } } } else { - // eslint-disable-next-line no-console - console.log(`Could not ${name}, no retries are left`); + log.error(`Could not ${name}, no retries are left`); } }; @@ -903,9 +956,11 @@ export const countDownTest = async ( * 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 */ export const createRule = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, rule: CreateRulesSchema ): Promise => { const response = await supertest @@ -914,13 +969,12 @@ export const createRule = async ( .send(rule); if (response.status === 409) { if (rule.rule_id != null) { - // eslint-disable-next-line no-console - console.log( - `When creating a rule found an unexpected conflict (409), will attempt a cleanup and one time re-try. This usually indicates a bad cleanup or race condition within the tests: ${JSON.stringify( + log.debug( + `Did not get an expected 200 "ok" when creating a rule (createRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( response.body - )}` + )}, status: ${JSON.stringify(response.status)}` ); - await deleteRule(supertest, rule.rule_id); + await deleteRule(supertest, log, rule.rule_id); const secondResponseTry = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') @@ -950,19 +1004,22 @@ export const createRule = async ( * Helper to cut down on the noise in some of the tests. Does a delete of a rule. * It does not check for a 200 "ok" on this. * @param supertest The supertest deps - * @param id The rule id to delete + * @param ruleId The rule id to delete + * @param log The tooling logger */ export const deleteRule = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, ruleId: string ): Promise => { const response = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) .set('kbn-xsrf', 'true'); if (response.status !== 200) { - // eslint-disable-next-line no-console - console.log( - 'Did not get an expected 200 "ok" when deleting the rule. CI issues could happen. Suspect this line if you are seeing CI issues.' + log.error( + `Did not get an expected 200 "ok" when deleting the rule (deleteRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` ); } @@ -995,14 +1052,21 @@ export const createRuleWithAuth = async ( */ export const updateRule = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, updatedRule: UpdateRulesSchema ): Promise => { - const { body } = await supertest + const response = await supertest .put(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(updatedRule) - .expect(200); - return body; + .send(updatedRule); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when updating a rule (updateRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; }; /** @@ -1010,13 +1074,22 @@ export const updateRule = async ( * creates a new action and expects a 200 and does not do any retries. * @param supertest The supertest deps */ -export const createNewAction = async (supertest: SuperTest.SuperTest) => { - const { body } = await supertest +export const createNewAction = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog +) => { + const response = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - return body; + .send(getWebHookAction()); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when creating a new action. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; }; /** @@ -1026,6 +1099,7 @@ export const createNewAction = async (supertest: SuperTest.SuperTest, + log: ToolingLog, ruleId: string ): Promise<{ page: number; @@ -1033,14 +1107,20 @@ export const findImmutableRuleById = async ( total: number; data: FullResponseSchema[]; }> => { - const { body } = await supertest + const response = await supertest .get( `${DETECTION_ENGINE_RULES_URL}/_find?filter=alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true" AND alert.attributes.tags: "${INTERNAL_RULE_ID_KEY}:${ruleId}"` ) .set('kbn-xsrf', 'true') - .send() - .expect(200); - return body; + .send(); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when finding an immutable rule by id (findImmutableRuleById). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; }; /** @@ -1049,24 +1129,34 @@ export const findImmutableRuleById = async ( * @param supertest The supertest deps */ export const getPrePackagedRulesStatus = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { - const { body } = await supertest + const response = await supertest .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) .set('kbn-xsrf', 'true') - .send() - .expect(200); - return body; + .send(); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting a pre-packaged rule status. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; }; /** * Helper to cut down on the noise in some of the tests. This checks for * an expected 200 still and does not try to any retries. Creates exception lists * @param supertest The supertest deps - * @param rule The rule to create + * @param exceptionList The exception list to create + * @param log The tooling logger */ export const createExceptionList = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, exceptionList: CreateExceptionListSchema ): Promise => { const response = await supertest @@ -1076,13 +1166,12 @@ export const createExceptionList = async ( if (response.status === 409) { if (exceptionList.list_id != null) { - // eslint-disable-next-line no-console - console.log( - `When creating an exception list found an unexpected conflict (409), will attempt a cleanup and one time re-try. This usually indicates a bad cleanup or race condition within the tests: ${JSON.stringify( + log.error( + `When creating an exception list found an unexpected conflict (409) creating an exception list (createExceptionList), will attempt a cleanup and one time re-try. This usually indicates a bad cleanup or race condition within the tests: ${JSON.stringify( response.body - )}` + )}, status: ${JSON.stringify(response.status)}` ); - await deleteExceptionList(supertest, exceptionList.list_id); + await deleteExceptionList(supertest, log, exceptionList.list_id); const secondResponseTry = await supertest .post(EXCEPTION_LIST_URL) .set('kbn-xsrf', 'true') @@ -1111,22 +1200,25 @@ export const createExceptionList = async ( }; /** - * Helper to cut down on the noise in some of the tests. Does a delete of a rule. + * Helper to cut down on the noise in some of the tests. Does a delete of an exception list. * It does not check for a 200 "ok" on this. * @param supertest The supertest deps - * @param id The rule id to delete + * @param listId The exception list to delete + * @param log The tooling logger */ export const deleteExceptionList = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, listId: string ): Promise => { const response = await supertest .delete(`${EXCEPTION_LIST_URL}?list_id=${listId}`) .set('kbn-xsrf', 'true'); if (response.status !== 200) { - // eslint-disable-next-line no-console - console.log( - 'Did not get an expected 200 "ok" when deleting an exception list. CI issues could happen. Suspect this line if you are seeing CI issues.' + log.error( + `Did not get an expected 200 "ok" when deleting an exception list (deleteExceptionList). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` ); } @@ -1137,18 +1229,27 @@ export const deleteExceptionList = async ( * Helper to cut down on the noise in some of the tests. This checks for * an expected 200 still and does not try to any retries. Creates exception lists * @param supertest The supertest deps - * @param rule The rule to create + * @param exceptionListItem The exception list item to create + * @param log The tooling logger */ export const createExceptionListItem = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, exceptionListItem: CreateExceptionListItemSchema ): Promise => { - const { body } = await supertest + const response = await supertest .post(EXCEPTION_LIST_ITEM_URL) .set('kbn-xsrf', 'true') - .send(exceptionListItem) - .expect(200); - return body; + .send(exceptionListItem); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when creating an exception list item (createExceptionListItem). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; }; /** @@ -1159,26 +1260,43 @@ export const createExceptionListItem = async ( */ export const getRule = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, ruleId: string ): Promise => { - const { body } = await supertest + const response = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) - .set('kbn-xsrf', 'true') - .expect(200); - return body; + .set('kbn-xsrf', 'true'); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting a rule (getRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; }; export const waitForAlertToComplete = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, id: string ): Promise => { - await waitFor(async () => { - const { body: alertBody } = await supertest - .get(`/api/alerts/alert/${id}/state`) - .set('kbn-xsrf', 'true') - .expect(200); - return alertBody.previousStartedAt != null; - }, 'waitForAlertToComplete'); + await waitFor( + async () => { + const response = await supertest.get(`/api/alerts/alert/${id}/state`).set('kbn-xsrf', 'true'); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when waiting for an alert to complete (waitForAlertToComplete). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body.previousStartedAt != null; + }, + 'waitForAlertToComplete', + log + ); }; /** @@ -1188,24 +1306,43 @@ export const waitForAlertToComplete = async ( */ export const waitForRuleSuccessOrStatus = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, id: string, status: 'succeeded' | 'failed' | 'partial failure' | 'warning' = 'succeeded' ): Promise => { - await waitFor(async () => { - try { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [id] }) - .expect(200); - return body[id]?.current_status?.status === status; - } catch (e) { - if ((e as Error).message.includes('got 503 "Service Unavailable"')) { - return false; + await waitFor( + async () => { + try { + const response = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .set('kbn-xsrf', 'true') + .send({ ids: [id] }); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when waiting for a rule success or status (waitForRuleSuccessOrStatus). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + if (response.body[id]?.current_status?.status !== status) { + log.debug( + `Did not get an expected status of ${status} while waiting for a rule success or status for rule id ${id} (waitForRuleSuccessOrStatus). Will continue retrying until status is found. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + + return response.body[id]?.current_status?.status === status; + } catch (e) { + if ((e as Error).message.includes('got 503 "Service Unavailable"')) { + return false; + } + throw e; } - throw e; - } - }, 'waitForRuleSuccessOrStatus'); + }, + 'waitForRuleSuccessOrStatus', + log + ); }; /** @@ -1216,15 +1353,17 @@ export const waitForRuleSuccessOrStatus = async ( */ export const waitForSignalsToBePresent = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, numberOfSignals = 1, signalIds: string[] ): Promise => { await waitFor( async () => { - const signalsOpen = await getSignalsByIds(supertest, signalIds, numberOfSignals); + const signalsOpen = await getSignalsByIds(supertest, log, signalIds, numberOfSignals); return signalsOpen.hits.hits.length >= numberOfSignals; }, 'waitForSignalsToBePresent', + log, 20000, 250 // Wait 250ms between tries ); @@ -1236,13 +1375,23 @@ export const waitForSignalsToBePresent = async ( */ export const getSignalsByRuleIds = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, ruleIds: string[] ): Promise> => { - const { body: signalsOpen }: { body: estypes.SearchResponse } = await supertest + const response = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalsRuleId(ruleIds)) - .expect(200); + .send(getQuerySignalsRuleId(ruleIds)); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting a signal by rule_id (getSignalsByRuleIds). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + + const { body: signalsOpen }: { body: estypes.SearchResponse } = response; return signalsOpen; }; @@ -1254,14 +1403,23 @@ export const getSignalsByRuleIds = async ( */ export const getSignalsByIds = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, ids: string[], size?: number ): Promise> => { - const { body: signalsOpen }: { body: estypes.SearchResponse } = await supertest + const response = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalsId(ids, size)) - .expect(200); + .send(getQuerySignalsId(ids, size)); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting a signal by id. CI issues could happen (getSignalsByIds). Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + const { body: signalsOpen }: { body: estypes.SearchResponse } = response; return signalsOpen; }; @@ -1272,26 +1430,48 @@ export const getSignalsByIds = async ( */ export const getSignalsById = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, id: string ): Promise> => { - const { body: signalsOpen }: { body: estypes.SearchResponse } = await supertest + const response = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalsId([id])) - .expect(200); + .send(getQuerySignalsId([id])); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting signals by id (getSignalsById). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + const { body: signalsOpen }: { body: estypes.SearchResponse } = response; return signalsOpen; }; export const installPrePackagedRules = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { - await countDownTest(async () => { - const { status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send(); - return status === 200; - }, 'installPrePackagedRules'); + await countDownTest( + async () => { + const { status, body } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status !== 200) { + log.debug( + `Did not get an expected 200 "ok" when installing pre-packaged rules (installPrePackagedRules) yet. Retrying until we get a 200 "ok". body: ${JSON.stringify( + body + )}, status: ${JSON.stringify(status)}` + ); + } + + return status === 200; + }, + 'installPrePackagedRules', + log + ); }; /** @@ -1303,6 +1483,7 @@ export const installPrePackagedRules = async ( */ export const createContainerWithEndpointEntries = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, endpointEntries: Array<{ entries: NonEmptyEntriesArray; osTypes: OsTypeArray | undefined; @@ -1315,7 +1496,7 @@ export const createContainerWithEndpointEntries = async ( // create the endpoint exception list container // eslint-disable-next-line @typescript-eslint/naming-convention - const { id, list_id, namespace_type, type } = await createExceptionList(supertest, { + const { id, list_id, namespace_type, type } = await createExceptionList(supertest, log, { description: 'endpoint description', list_id: 'endpoint_list', name: 'endpoint_list', @@ -1333,16 +1514,20 @@ export const createContainerWithEndpointEntries = async ( os_types: endpointEntry.osTypes, type: 'simple', }; - return createExceptionListItem(supertest, exceptionListItem); + return createExceptionListItem(supertest, log, exceptionListItem); }) ); // To reduce the odds of in-determinism and/or bugs we ensure we have // the same length of entries before continuing. - await waitFor(async () => { - const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); - return body.data.length === endpointEntries.length; - }, `within createContainerWithEndpointEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); + await waitFor( + async () => { + const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); + return body.data.length === endpointEntries.length; + }, + `within createContainerWithEndpointEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`, + log + ); return [ { @@ -1363,6 +1548,7 @@ export const createContainerWithEndpointEntries = async ( */ export const createContainerWithEntries = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, entries: NonEmptyEntriesArray[] ): Promise => { // If not given any endpoint entries, return without any @@ -1371,7 +1557,7 @@ export const createContainerWithEntries = async ( } // Create the rule exception list container // eslint-disable-next-line @typescript-eslint/naming-convention - const { id, list_id, namespace_type, type } = await createExceptionList(supertest, { + const { id, list_id, namespace_type, type } = await createExceptionList(supertest, log, { description: 'some description', list_id: 'some-list-id', name: 'some name', @@ -1388,16 +1574,20 @@ export const createContainerWithEntries = async ( type: 'simple', entries: entry, }; - return createExceptionListItem(supertest, exceptionListItem); + return createExceptionListItem(supertest, log, exceptionListItem); }) ); // To reduce the odds of in-determinism and/or bugs we ensure we have // the same length of entries before continuing. - await waitFor(async () => { - const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); - return body.data.length === entries.length; - }, `within createContainerWithEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); + await waitFor( + async () => { + const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); + return body.data.length === entries.length; + }, + `within createContainerWithEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`, + log + ); return [ { @@ -1421,6 +1611,7 @@ export const createContainerWithEntries = async ( */ export const createRuleWithExceptionEntries = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, rule: CreateRulesSchema, entries: NonEmptyEntriesArray[], endpointEntries?: Array<{ @@ -1428,9 +1619,10 @@ export const createRuleWithExceptionEntries = async ( osTypes: OsTypeArray | undefined; }> ): Promise => { - const maybeExceptionList = await createContainerWithEntries(supertest, entries); + const maybeExceptionList = await createContainerWithEntries(supertest, log, entries); const maybeEndpointList = await createContainerWithEndpointEntries( supertest, + log, endpointEntries ?? [] ); @@ -1443,13 +1635,19 @@ export const createRuleWithExceptionEntries = async ( enabled: false, exceptions_list: [...maybeExceptionList, ...maybeEndpointList], }; - const ruleResponse = await createRule(supertest, ruleWithException); - await supertest + const ruleResponse = await createRule(supertest, log, ruleWithException); + const response = await supertest .patch(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ rule_id: ruleResponse.rule_id, enabled: true }) - .expect(200); + .send({ rule_id: ruleResponse.rule_id, enabled: true }); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when patching a rule with exception entries (createRuleWithExceptionEntries). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } return ruleResponse; }; @@ -1469,11 +1667,19 @@ export const getIndexNameFromLoad = (loadResponse: Record): str * @param esClient elasticsearch {@link Client} * @param index name of the index to query */ -export const waitForIndexToPopulate = async (es: Client, index: string): Promise => { - await waitFor(async () => { - const response = await es.count({ index }); - return response.count > 0; - }, `waitForIndexToPopulate: ${index}`); +export const waitForIndexToPopulate = async ( + es: Client, + log: ToolingLog, + index: string +): Promise => { + await waitFor( + async () => { + const response = await es.count({ index }); + return response.count > 0; + }, + `waitForIndexToPopulate: ${index}`, + log + ); }; export const deleteMigrations = async ({ @@ -1502,18 +1708,27 @@ interface CreateMigrationResponse { export const startSignalsMigration = async ({ indices, supertest, + log, }: { supertest: SuperTest.SuperTest; + log: ToolingLog; indices: string[]; }): Promise => { - const { - body: { indices: created }, - }: { body: { indices: CreateMigrationResponse[] } } = await supertest + const response = await supertest .post(DETECTION_ENGINE_SIGNALS_MIGRATION_URL) .set('kbn-xsrf', 'true') - .send({ index: indices }) - .expect(200); + .send({ index: indices }); + const { + body: { indices: created }, + }: { body: { indices: CreateMigrationResponse[] } } = response; + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when starting a signals migration (startSignalsMigration). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } return created; }; @@ -1526,30 +1741,40 @@ interface FinalizeMigrationResponse { export const finalizeSignalsMigration = async ({ migrationIds, supertest, + log, }: { supertest: SuperTest.SuperTest; + log: ToolingLog; migrationIds: string[]; }): Promise => { - const { - body: { migrations }, - }: { body: { migrations: FinalizeMigrationResponse[] } } = await supertest + const response = await supertest .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) .set('kbn-xsrf', 'true') - .send({ migration_ids: migrationIds }) - .expect(200); + .send({ migration_ids: migrationIds }); + const { + body: { migrations }, + }: { body: { migrations: FinalizeMigrationResponse[] } } = response; + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when finalizing signals migration (finalizeSignalsMigration). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } return migrations; }; export const getOpenSignals = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, es: Client, rule: FullResponseSchema ) => { - await waitForRuleSuccessOrStatus(supertest, rule.id); + await waitForRuleSuccessOrStatus(supertest, log, rule.id); // Critically important that we wait for rule success AND refresh the write index in that order before we // assert that no signals were created. Otherwise, signals could be written but not available to query yet // when we search, causing tests that check that signals are NOT created to pass when they should fail. await refreshIndex(es, '.alerts-security.alerts-default*'); - return getSignalsByIds(supertest, [rule.id]); + return getSignalsByIds(supertest, log, [rule.id]); }; diff --git a/x-pack/test/examples/embedded_lens/embedded_example.ts b/x-pack/test/examples/embedded_lens/embedded_example.ts index 3a0891079f24e9..d11495f0450b4b 100644 --- a/x-pack/test/examples/embedded_lens/embedded_example.ts +++ b/x-pack/test/examples/embedded_lens/embedded_example.ts @@ -16,8 +16,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); async function checkData() { - const data = await elasticChart.getChartDebugData(); - expect(data!.bars![0].bars.length).to.eql(24); + await retry.try(async () => { + const data = await elasticChart.getChartDebugData(); + expect(data!.bars![0].bars.length).to.eql(24); + }); } describe('show and save', () => { diff --git a/x-pack/test/examples/embedded_lens/index.ts b/x-pack/test/examples/embedded_lens/index.ts index 3bd4ea31cc89bb..98ea7f80754a2d 100644 --- a/x-pack/test/examples/embedded_lens/index.ts +++ b/x-pack/test/examples/embedded_lens/index.ts @@ -15,14 +15,19 @@ export default function ({ getService, loadTestFile }: PluginFunctionalProviderC describe('embedded Lens examples', function () { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.load('x-pack/test/functional/es_archives/lens/basic'); // need at least one index pattern + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); // need at least one index pattern await kibanaServer.uiSettings.update({ defaultIndex: 'logstash-*', }); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); describe('', function () { diff --git a/x-pack/test/examples/search_examples/index.ts b/x-pack/test/examples/search_examples/index.ts index 41c4945ca45696..ac9e385d3d391d 100644 --- a/x-pack/test/examples/search_examples/index.ts +++ b/x-pack/test/examples/search_examples/index.ts @@ -10,17 +10,23 @@ import { PluginFunctionalProviderContext } from 'test/plugin_functional/services // eslint-disable-next-line import/no-default-export export default function ({ getService, loadTestFile }: PluginFunctionalProviderContext) { const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); describe('search examples', function () { this.tags('ciGroup13'); before(async () => { await esArchiver.emptyKibanaIndex(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); // need at least one index pattern + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); // need at least one index pattern }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); loadTestFile(require.resolve('./search_session_example')); diff --git a/x-pack/test/fleet_api_integration/apis/data_streams/list.ts b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts index 365eb716592d1a..2b0098a76ac1f0 100644 --- a/x-pack/test/fleet_api_integration/apis/data_streams/list.ts +++ b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts @@ -6,9 +6,14 @@ */ import expect from '@kbn/expect'; +import { keyBy } from 'lodash'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; +interface IndexResponse { + _id: string; + _index: string; +} export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); @@ -33,34 +38,51 @@ export default function (providerContext: FtrProviderContext) { }; const seedDataStreams = async () => { - await es.transport.request({ - method: 'POST', - path: `/${logsTemplateName}-default/_doc`, - body: { - '@timestamp': '2015-01-01', - logs_test_name: 'test', - data_stream: { - dataset: `${pkgName}.test_logs`, - namespace: 'default', - type: 'logs', + const responses = []; + responses.push( + await es.transport.request({ + method: 'POST', + path: `/${logsTemplateName}-default/_doc`, + body: { + '@timestamp': '2015-01-01', + logs_test_name: 'test', + data_stream: { + dataset: `${pkgName}.test_logs`, + namespace: 'default', + type: 'logs', + }, }, - }, - }); - await es.transport.request({ - method: 'POST', - path: `/${metricsTemplateName}-default/_doc`, - body: { - '@timestamp': '2015-01-01', - logs_test_name: 'test', - data_stream: { - dataset: `${pkgName}.test_metrics`, - namespace: 'default', - type: 'metrics', + }) + ); + responses.push( + await es.transport.request({ + method: 'POST', + path: `/${metricsTemplateName}-default/_doc`, + body: { + '@timestamp': '2015-01-01', + logs_test_name: 'test', + data_stream: { + dataset: `${pkgName}.test_metrics`, + namespace: 'default', + type: 'metrics', + }, }, - }, - }); + }) + ); + + return responses as IndexResponse[]; }; + const getSeedDocsFromResponse = async (indexResponses: IndexResponse[]) => + Promise.all( + indexResponses.map((indexResponse) => + es.transport.request({ + method: 'GET', + path: `/${indexResponse._index}/_doc/${indexResponse._id}`, + }) + ) + ); + const getDataStreams = async () => { return await supertest.get(`/api/fleet/data_streams`).set('kbn-xsrf', 'xxxx'); }; @@ -93,36 +115,58 @@ export default function (providerContext: FtrProviderContext) { expect(body).to.eql({ data_streams: [] }); }); - it('should return correct data stream information', async function () { + it('should return correct basic data stream information', async function () { await seedDataStreams(); - await retry.tryForTime(10000, async () => { - const { body } = await getDataStreams(); - return expect( - body.data_streams.map((dataStream: any) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { index, size_in_bytes, ...rest } = dataStream; - return rest; - }) - ).to.eql([ + // we can't compare the array directly as the order is unpredictable + const expectedStreamsByDataset = keyBy( + [ { - dataset: 'datastreams.test_logs', + dataset: 'datastreams.test_metrics', namespace: 'default', - type: 'logs', + type: 'metrics', package: 'datastreams', package_version: '0.1.0', - last_activity_ms: 1420070400000, dashboards: [], }, { - dataset: 'datastreams.test_metrics', + dataset: 'datastreams.test_logs', namespace: 'default', - type: 'metrics', + type: 'logs', package: 'datastreams', package_version: '0.1.0', - last_activity_ms: 1420070400000, dashboards: [], }, - ]); + ], + 'dataset' + ); + + await retry.tryForTime(10000, async () => { + const { body } = await getDataStreams(); + expect(body.data_streams.length).to.eql(2); + + body.data_streams.forEach((dataStream: any) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { index, size_in_bytes, last_activity_ms, ...coreFields } = dataStream; + expect(expectedStreamsByDataset[coreFields.dataset]).not.to.eql(undefined); + expect(coreFields).to.eql(expectedStreamsByDataset[coreFields.dataset]); + }); + }); + }); + + it('should use event.ingested instead of @timestamp for last_activity_ms', async function () { + const seedResponse = await seedDataStreams(); + const docs = await getSeedDocsFromResponse(seedResponse); + const docsByDataset: Record = keyBy(docs, '_source.data_stream.dataset'); + await retry.tryForTime(10000, async () => { + const { body } = await getDataStreams(); + expect(body.data_streams.length).to.eql(2); + body.data_streams.forEach((dataStream: any) => { + expect(docsByDataset[dataStream.dataset]).not.to.eql(undefined); + const expectedTimestamp = new Date( + docsByDataset[dataStream.dataset]?._source?.event?.ingested + ).getTime(); + expect(dataStream.last_activity_ms).to.eql(expectedTimestamp); + }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap index 8e06e623853152..98dc4c26307435 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap +++ b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap @@ -167,7 +167,6 @@ Object { "data_streams": Array [ Object { "dataset": "apache.access", - "ingest_pipeline": "default", "package": "apache", "path": "access", "release": "experimental", @@ -199,7 +198,6 @@ Object { }, Object { "dataset": "apache.status", - "ingest_pipeline": "default", "package": "apache", "path": "status", "release": "experimental", @@ -236,7 +234,6 @@ Object { }, Object { "dataset": "apache.error", - "ingest_pipeline": "default", "package": "apache", "path": "error", "release": "experimental", @@ -331,11 +328,11 @@ Object { "install_version": "0.1.4", "installed_es": Array [ Object { - "id": "logs-apache.access-0.1.4", + "id": "logs-apache.access-0.1.4-default", "type": "ingest_pipeline", }, Object { - "id": "logs-apache.error-0.1.4", + "id": "logs-apache.error-0.1.4-default", "type": "ingest_pipeline", }, Object { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts index 86928874f8a344..85f4cb6193d60d 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts @@ -74,7 +74,7 @@ export default function (providerContext: FtrProviderContext) { .type('application/gzip') .send(buf) .expect(200); - expect(res.body.response.length).to.be(29); + expect(res.body.response.length).to.be(27); }); it('should install a zip archive correctly and package info should return correctly after validation', async function () { @@ -85,7 +85,7 @@ export default function (providerContext: FtrProviderContext) { .type('application/zip') .send(buf) .expect(200); - expect(res.body.response.length).to.be(29); + expect(res.body.response.length).to.be(27); const packageInfoRes = await supertest .get(`/api/fleet/epm/packages/${testPkgKey}`) diff --git a/x-pack/test/fleet_api_integration/apis/epm/template.ts b/x-pack/test/fleet_api_integration/apis/epm/template.ts index 6f29eb794e7d06..d8856ec392218a 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/template.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/template.ts @@ -28,7 +28,7 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - // This test was inspired by https://github.com/elastic/kibana/blob/master/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js + // This test was inspired by https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js describe('EPM - template', async () => { beforeEach(async () => { appContextService.start({ 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 6a0d46a6053866..6817289d389f35 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 @@ -68,7 +68,7 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/package_policies`) .set('kbn-xsrf', 'xxxx') .send({ - name: 'filetest-1', + name: 'filetest', description: '', namespace: 'default', policy_id: hostedPolicy.id, @@ -85,7 +85,7 @@ export default function (providerContext: FtrProviderContext) { expect(responseWithoutForce.statusCode).to.be(400); expect(responseWithoutForce.message).to.contain( - 'Cannot add integrations to hosted agent policy' + 'Cannot update integrations of hosted agent policy' ); // try same request with `force: true` @@ -122,7 +122,7 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/package_policies`) .set('kbn-xsrf', 'xxxx') .send({ - name: 'filetest-1', + name: 'filetest-2', description: '', namespace: 'default', policy_id: agentPolicyId, @@ -276,5 +276,53 @@ export default function (providerContext: FtrProviderContext) { }) .expect(400); }); + + it('should return a 400 if there is a package policy with the same name on a different policy', async function () { + const { body: agentPolicyResponse } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Test policy 2', + namespace: 'default', + }); + const otherAgentPolicyId = agentPolicyResponse.item.id; + + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'same-name-test-2', + description: '', + namespace: 'default', + policy_id: otherAgentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(200); + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'same-name-test-2', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(400); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts index 315ca276c393ff..7d62ea3bf7ec34 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts @@ -158,7 +158,7 @@ export default function (providerContext: FtrProviderContext) { }); }); - it('should return a 500 if there is another package policy with the same name', async function () { + it('should return a 400 if there is another package policy with the same name', async function () { await supertest .put(`/api/fleet/package_policies/${packagePolicyId2}`) .set('kbn-xsrf', 'xxxx') @@ -176,7 +176,37 @@ export default function (providerContext: FtrProviderContext) { version: '0.1.0', }, }) - .expect(500); + .expect(400); + }); + + it('should return a 400 if there is another package policy with the same name on a different policy', async function () { + const { body: agentPolicyResponse } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Test policy 2', + namespace: 'default', + }); + const otherAgentPolicyId = agentPolicyResponse.item.id; + + await supertest + .put(`/api/fleet/package_policies/${packagePolicyId2}`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: 'updated_namespace', + policy_id: otherAgentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(400); }); it('should work with frozen input vars', async function () { diff --git a/x-pack/test/fleet_cypress/agent.ts b/x-pack/test/fleet_cypress/agent.ts new file mode 100644 index 00000000000000..e05a21c6a63e33 --- /dev/null +++ b/x-pack/test/fleet_cypress/agent.ts @@ -0,0 +1,114 @@ +/* + * 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 { ToolingLog } from '@kbn/dev-utils'; +import axios, { AxiosRequestConfig } from 'axios'; +import { ChildProcess, spawn } from 'child_process'; +import { getLatestVersion } from './artifact_manager'; +import { Manager } from './resource_manager'; + +interface AgentManagerParams { + user: string; + password: string; + kibanaUrl: string; + esHost: string; +} + +export class AgentManager extends Manager { + private params: AgentManagerParams; + private log: ToolingLog; + private agentProcess?: ChildProcess; + private requestOptions: AxiosRequestConfig; + constructor(params: AgentManagerParams, log: ToolingLog) { + super(); + this.log = log; + this.params = params; + this.requestOptions = { + headers: { + 'kbn-xsrf': 'kibana', + }, + auth: { + username: this.params.user, + password: this.params.password, + }, + }; + } + + public async setup() { + this.log.info('Running agent preconfig'); + return await axios.post( + `${this.params.kibanaUrl}/api/fleet/agents/setup`, + {}, + this.requestOptions + ); + } + + public async startAgent() { + this.log.info('Getting agent enrollment key'); + const { data: apiKeys } = await axios.get( + this.params.kibanaUrl + '/api/fleet/enrollment-api-keys', + this.requestOptions + ); + const policy = apiKeys.list[1]; + + this.log.info('Running the agent'); + + const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; + this.log.info(artifact); + + const args = [ + 'run', + '--add-host', + 'host.docker.internal:host-gateway', + '--env', + 'FLEET_ENROLL=1', + '--env', + `FLEET_URL=http://host.docker.internal:8220`, + '--env', + `FLEET_ENROLLMENT_TOKEN=${policy.api_key}`, + '--env', + 'FLEET_INSECURE=true', + '--rm', + artifact, + ]; + + this.agentProcess = spawn('docker', args, { stdio: 'inherit' }); + + // Wait til we see the agent is online + let done = false; + let retries = 0; + while (!done) { + await new Promise((r) => setTimeout(r, 5000)); + const { data: agents } = await axios.get( + `${this.params.kibanaUrl}/api/fleet/agents`, + this.requestOptions + ); + done = agents.list[0]?.status === 'online'; + if (++retries > 12) { + this.log.error('Giving up on enrolling the agent after a minute'); + throw new Error('Agent timed out while coming online'); + } + } + + return { policyId: policy.policy_id as string }; + } + + protected _cleanup() { + this.log.info('Cleaning up the agent process'); + if (this.agentProcess) { + if (!this.agentProcess.kill(9)) { + this.log.warning('Unable to kill agent process'); + } + + this.agentProcess.on('close', () => { + this.log.info('Agent process closed'); + }); + delete this.agentProcess; + } + return; + } +} diff --git a/x-pack/test/fleet_cypress/artifact_manager.ts b/x-pack/test/fleet_cypress/artifact_manager.ts new file mode 100644 index 00000000000000..aea0eb8bbec86c --- /dev/null +++ b/x-pack/test/fleet_cypress/artifact_manager.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; +import { last } from 'lodash'; + +export async function getLatestVersion(): Promise { + const response: any = await axios('https://artifacts-api.elastic.co/v1/versions'); + return last(response.data.versions as string[]) || '8.0.0-SNAPSHOT'; +} diff --git a/x-pack/test/fleet_cypress/cli_config.ts b/x-pack/test/fleet_cypress/cli_config.ts new file mode 100644 index 00000000000000..b8eb78e6a4abcb --- /dev/null +++ b/x-pack/test/fleet_cypress/cli_config.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. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +import { FleetCypressCliTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const cypressConfig = await readConfigFile(require.resolve('./config.ts')); + return { + ...cypressConfig.getAll(), + + testRunner: FleetCypressCliTestRunner, + }; +} diff --git a/x-pack/test/fleet_cypress/config.ts b/x-pack/test/fleet_cypress/config.ts new file mode 100644 index 00000000000000..14898f81aac129 --- /dev/null +++ b/x-pack/test/fleet_cypress/config.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +import { CA_CERT_PATH } from '@kbn/dev-utils'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonTestsConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const xpackFunctionalTestsConfig = await readConfigFile( + require.resolve('../functional/config.js') + ); + + return { + ...kibanaCommonTestsConfig.getAll(), + + esTestCluster: { + ...xpackFunctionalTestsConfig.get('esTestCluster'), + serverArgs: [ + ...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs'), + // define custom es server here + // API Keys is enabled at the top level + 'xpack.security.enabled=true', + 'http.host=0.0.0.0', + ], + }, + + kbnTestServer: { + ...xpackFunctionalTestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), + '--csp.strict=false', + // define custom kibana server args here + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + ], + }, + }; +} diff --git a/x-pack/test/fleet_cypress/fleet_server.ts b/x-pack/test/fleet_cypress/fleet_server.ts new file mode 100644 index 00000000000000..fe2b8c74592292 --- /dev/null +++ b/x-pack/test/fleet_cypress/fleet_server.ts @@ -0,0 +1,83 @@ +/* + * 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 { ChildProcess, spawn } from 'child_process'; +import { ToolingLog } from '@kbn/dev-utils'; +import axios from 'axios'; +import { Manager } from './resource_manager'; +import { getLatestVersion } from './artifact_manager'; + +export interface ElasticsearchConfig { + esHost: string; + user: string; + password: string; + port: string; +} + +export class FleetManager extends Manager { + private fleetProcess?: ChildProcess; + private esConfig: ElasticsearchConfig; + private log: ToolingLog; + constructor(esConfig: ElasticsearchConfig, log: ToolingLog) { + super(); + this.esConfig = esConfig; + this.log = log; + } + public async setup(): Promise { + this.log.info('Setting fleet up'); + return new Promise(async (res, rej) => { + try { + const response = await axios.post( + `${this.esConfig.esHost}/_security/service/elastic/fleet-server/credential/token` + ); + const serviceToken = response.data.token.value; + const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; + this.log.info(artifact); + + const host = 'host.docker.internal'; + + const args = [ + 'run', + '-p', + `8220:8220`, + '--add-host', + 'host.docker.internal:host-gateway', + '--env', + 'FLEET_SERVER_ENABLE=true', + '--env', + `FLEET_SERVER_ELASTICSEARCH_HOST=http://${host}:${this.esConfig.port}`, + '--env', + `FLEET_SERVER_SERVICE_TOKEN=${serviceToken}`, + '--rm', + artifact, + ]; + this.fleetProcess = spawn('docker', args, { + stdio: 'inherit', + }); + this.fleetProcess.on('error', rej); + setTimeout(res, 15000); + } catch (error) { + rej(error); + } + }); + } + + protected _cleanup() { + this.log.info('Removing old fleet config'); + if (this.fleetProcess) { + this.log.info('Closing fleet process'); + if (!this.fleetProcess.kill(9)) { + this.log.warning('Unable to kill fleet server process'); + } + + this.fleetProcess.on('close', () => { + this.log.info('Fleet server process closed'); + }); + delete this.fleetProcess; + } + } +} diff --git a/x-pack/test/fleet_cypress/ftr_provider_context.d.ts b/x-pack/test/fleet_cypress/ftr_provider_context.d.ts new file mode 100644 index 00000000000000..aa56557c09df82 --- /dev/null +++ b/x-pack/test/fleet_cypress/ftr_provider_context.d.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; + +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/fleet_cypress/resource_manager.ts b/x-pack/test/fleet_cypress/resource_manager.ts new file mode 100644 index 00000000000000..e892021155417d --- /dev/null +++ b/x-pack/test/fleet_cypress/resource_manager.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. + */ + +const CLEANUP_EVENTS = ['SIGINT', 'exit', 'uncaughtException', 'unhandledRejection']; +export class Manager { + private cleaned = false; + constructor() { + const cleanup = () => this.cleanup(); + CLEANUP_EVENTS.forEach((ev) => process.on(ev, cleanup)); + } + // This must be a synchronous method because it is used in the unhandledException and exit event handlers + public cleanup() { + // Since this can be called multiple places we proxy it with some protection + if (this._cleanup && !this.cleaned) { + this.cleaned = true; + this._cleanup(); + } + } + protected _cleanup?(): void; +} diff --git a/x-pack/test/fleet_cypress/runner.ts b/x-pack/test/fleet_cypress/runner.ts new file mode 100644 index 00000000000000..b49bfbdc091e24 --- /dev/null +++ b/x-pack/test/fleet_cypress/runner.ts @@ -0,0 +1,105 @@ +/* + * 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 { resolve } from 'path'; +import Url from 'url'; + +import { withProcRunner } from '@kbn/dev-utils'; + +import { FtrProviderContext } from './ftr_provider_context'; + +import { AgentManager } from './agent'; +import { FleetManager } from './fleet_server'; + +async function withFleetAgent( + { getService }: FtrProviderContext, + runner: (runnerEnv: Record) => Promise +) { + const log = getService('log'); + const config = getService('config'); + + const esHost = Url.format(config.get('servers.elasticsearch')); + const esConfig = { + user: config.get('servers.elasticsearch.username'), + password: config.get('servers.elasticsearch.password'), + esHost, + port: config.get('servers.elasticsearch.port'), + }; + const fleetManager = new FleetManager(esConfig, log); + + const agentManager = new AgentManager( + { + ...esConfig, + kibanaUrl: Url.format({ + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: config.get('servers.kibana.port'), + }), + }, + log + ); + + // Since the managers will create uncaughtException event handlers we need to exit manually + process.on('uncaughtException', (err) => { + // eslint-disable-next-line no-console + console.error('Encountered error; exiting after cleanup.', err); + process.exit(1); + }); + + await agentManager.setup(); + await fleetManager.setup(); + try { + await runner({}); + } finally { + fleetManager.cleanup(); + agentManager.cleanup(); + } +} + +export async function FleetCypressCliTestRunner(context: FtrProviderContext) { + await startFleetAgent(context, 'run'); +} + +export async function FleetCypressVisualTestRunner(context: FtrProviderContext) { + await startFleetAgent(context, 'open'); +} + +function startFleetAgent(context: FtrProviderContext, cypressCommand: string) { + const log = context.getService('log'); + const config = context.getService('config'); + return withFleetAgent(context, (runnerEnv) => + withProcRunner(log, async (procs) => { + await procs.run('cypress', { + cmd: 'yarn', + args: [`cypress:${cypressCommand}`], + cwd: resolve(__dirname, '../../plugins/fleet'), + env: { + FORCE_COLOR: '1', + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_baseUrl: Url.format(config.get('servers.kibana')), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_protocol: config.get('servers.kibana.protocol'), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_hostname: config.get('servers.kibana.hostname'), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_configport: config.get('servers.kibana.port'), + CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), + CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), + CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + CYPRESS_KIBANA_URL: Url.format({ + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: config.get('servers.kibana.port'), + }), + ...runnerEnv, + ...process.env, + }, + wait: true, + }); + }) + ); +} diff --git a/x-pack/test/fleet_cypress/services.ts b/x-pack/test/fleet_cypress/services.ts new file mode 100644 index 00000000000000..5e063134081ad2 --- /dev/null +++ b/x-pack/test/fleet_cypress/services.ts @@ -0,0 +1,8 @@ +/* + * 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 * from '../../../test/common/services'; diff --git a/x-pack/test/fleet_cypress/visual_config.ts b/x-pack/test/fleet_cypress/visual_config.ts new file mode 100644 index 00000000000000..1a343b52c11616 --- /dev/null +++ b/x-pack/test/fleet_cypress/visual_config.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. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +import { FleetCypressVisualTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const cypressConfig = await readConfigFile(require.resolve('./config.ts')); + return { + ...cypressConfig.getAll(), + + testRunner: FleetCypressVisualTestRunner, + }; +} diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts index ebe76ee52499b0..20c79d9142f096 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const security = getService('security'); const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']); @@ -18,14 +17,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const globalNav = getService('globalNav'); describe('security feature controls', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/empty_kibana'); - }); - describe('global advanced_settings all privileges', () => { before(async () => { await security.role.create('global_advanced_settings_all_role', { @@ -177,7 +168,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('does not show Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Discover']); + expect(navLinks).to.eql(['Discover']); }); it(`does not allow navigation to advanced settings; shows "not found" error`, async () => { diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts index 41a03b36d3c43b..0d1c525f569044 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts @@ -25,7 +25,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { // we need to load the following in every situation as deleting // a space deletes all of the associated saved objects - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); await spacesService.create({ id: 'custom_space', @@ -36,7 +35,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { after(async () => { await spacesService.delete('custom_space'); - await esArchiver.unload('x-pack/test/functional/es_archives/empty_kibana'); }); it('shows Management navlink', async () => { diff --git a/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts b/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts index 9e25ca1d79f9bf..9b418e5fd2b94a 100644 --- a/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts +++ b/x-pack/test/functional/apps/api_keys/feature_controls/api_keys_security.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const security = getService('security'); const PageObjects = getPageObjects(['common', 'settings', 'security']); const appsMenu = getService('appsMenu'); @@ -17,14 +16,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('security', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); await PageObjects.common.navigateToApp('home'); }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/empty_kibana'); - }); - describe('global all privileges (aka kibana_admin)', () => { before(async () => { await security.testUser.setRoles(['kibana_admin'], true); diff --git a/x-pack/test/functional/apps/api_keys/home_page.ts b/x-pack/test/functional/apps/api_keys/home_page.ts index be8f128359345f..59072475275850 100644 --- a/x-pack/test/functional/apps/api_keys/home_page.ts +++ b/x-pack/test/functional/apps/api_keys/home_page.ts @@ -5,6 +5,7 @@ * 2.0. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { @@ -13,6 +14,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const security = getService('security'); const testSubjects = getService('testSubjects'); const find = getService('find'); + const browser = getService('browser'); describe('Home page', function () { before(async () => { @@ -34,5 +36,90 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { log.debug('Checking for create API key call to action'); await find.existsByLinkText('Create API key'); }); + + describe('creates API key', function () { + before(async () => { + await security.testUser.setRoles(['kibana_admin']); + await security.testUser.setRoles(['test_api_keys']); + await pageObjects.common.navigateToApp('apiKeys'); + }); + + afterEach(async () => { + await pageObjects.apiKeys.deleteAllApiKeyOneByOne(); + }); + + it('when submitting form, close dialog and displays new api key', async () => { + const apiKeyName = 'Happy API Key'; + await pageObjects.apiKeys.clickOnPromptCreateApiKey(); + expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys/create'); + + await pageObjects.apiKeys.setApiKeyName(apiKeyName); + await pageObjects.apiKeys.submitOnCreateApiKey(); + const newApiKeyCreation = await pageObjects.apiKeys.getNewApiKeyCreation(); + + expect(await browser.getCurrentUrl()).to.not.contain( + 'app/management/security/api_keys/create' + ); + expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); + expect(await pageObjects.apiKeys.isApiKeyModalExists()).to.be(false); + expect(newApiKeyCreation).to.be(`Created API key '${apiKeyName}'`); + }); + + it('with optional expiration, redirects back and displays base64', async () => { + const apiKeyName = 'Happy expiration API key'; + await pageObjects.apiKeys.clickOnPromptCreateApiKey(); + expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys/create'); + + await pageObjects.apiKeys.setApiKeyName(apiKeyName); + await pageObjects.apiKeys.toggleCustomExpiration(); + await pageObjects.apiKeys.submitOnCreateApiKey(); + expect(await pageObjects.apiKeys.getErrorCallOutText()).to.be( + 'Enter a valid duration or disable this option.' + ); + + await pageObjects.apiKeys.setApiKeyCustomExpiration('12'); + await pageObjects.apiKeys.submitOnCreateApiKey(); + const newApiKeyCreation = await pageObjects.apiKeys.getNewApiKeyCreation(); + + expect(await browser.getCurrentUrl()).to.not.contain( + 'app/management/security/api_keys/create' + ); + expect(await browser.getCurrentUrl()).to.contain('app/management/security/api_keys'); + expect(await pageObjects.apiKeys.isApiKeyModalExists()).to.be(false); + expect(newApiKeyCreation).to.be(`Created API key '${apiKeyName}'`); + }); + }); + + describe('deletes API key(s)', function () { + before(async () => { + await security.testUser.setRoles(['kibana_admin']); + await security.testUser.setRoles(['test_api_keys']); + await pageObjects.common.navigateToApp('apiKeys'); + }); + + beforeEach(async () => { + await pageObjects.apiKeys.clickOnPromptCreateApiKey(); + await pageObjects.apiKeys.setApiKeyName('api key 1'); + await pageObjects.apiKeys.submitOnCreateApiKey(); + }); + + it('one by one', async () => { + await pageObjects.apiKeys.deleteAllApiKeyOneByOne(); + expect(await pageObjects.apiKeys.getApiKeysFirstPromptTitle()).to.be( + 'Create your first API key' + ); + }); + + it('by bulk', async () => { + await pageObjects.apiKeys.clickOnTableCreateApiKey(); + await pageObjects.apiKeys.setApiKeyName('api key 2'); + await pageObjects.apiKeys.submitOnCreateApiKey(); + + await pageObjects.apiKeys.bulkDeleteApiKeys(); + expect(await pageObjects.apiKeys.getApiKeysFirstPromptTitle()).to.be( + 'Create your first API key' + ); + }); + }); }); }; diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts index 6f6d29db1ef0ca..05e13bb04d91b5 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const security = getService('security'); const PageObjects = getPageObjects(['common', 'error', 'security']); const testSubjects = getService('testSubjects'); @@ -18,7 +17,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('security', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); // ensure we're logged out so we can login as the appropriate users await PageObjects.security.forceLogout(); }); diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts index 5a73f31c8427f8..983a3101b9e31e 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts @@ -67,7 +67,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Canvas']); + expect(navLinks).to.eql(['Canvas']); }); it(`landing page shows "Create new workpad" button`, async () => { @@ -142,7 +142,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Canvas']); + expect(navLinks).to.eql(['Canvas']); }); it(`landing page shows disabled "Create new workpad" button`, async () => { diff --git a/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts b/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts index b1d7c1194e7bc5..26efa4248850bf 100644 --- a/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts @@ -15,16 +15,26 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const dashboardPanelActions = getService('dashboardPanelActions'); + const kibanaServer = getService('kibanaServer'); describe('dashboard lens by value', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); + }); + it('can add a lens panel by value', async () => { await PageObjects.lens.createAndAddLensFromDashboard({}); const newPanelCount = await PageObjects.dashboard.getPanelCount(); diff --git a/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts b/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts index 043acf90bb8932..f8a90767b54ac9 100644 --- a/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts @@ -23,6 +23,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); const dashboardAddPanel = getService('dashboardAddPanel'); + const kibanaServer = getService('kibanaServer'); const LAYER_NAME = 'World Countries'; let mapCounter = 0; @@ -77,7 +78,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('dashboard maps by value', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); + await kibanaServer.savedObjects.clean({ + types: ['search', 'index-pattern', 'visualization', 'dashboard', 'tag', 'map'], + }); }); describe('adding a map by value', () => { diff --git a/x-pack/test/functional/apps/dashboard/dashboard_tagging.ts b/x-pack/test/functional/apps/dashboard/dashboard_tagging.ts index 707b3877a70b51..7a358e24a2b412 100644 --- a/x-pack/test/functional/apps/dashboard/dashboard_tagging.ts +++ b/x-pack/test/functional/apps/dashboard/dashboard_tagging.ts @@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const listingTable = getService('listingTable'); const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const find = getService('find'); const PageObjects = getPageObjects([ @@ -67,12 +68,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); + await kibanaServer.savedObjects.clean({ + types: ['search', 'index-pattern', 'visualization', 'dashboard', 'tag'], + }); + }); + it('adds a new tag to a new Dashboard', async () => { await createTagFromDashboard(); PageObjects.dashboard.saveDashboard(dashboardTitle, {}, false); diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_to_dashboard_drilldown.ts b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_to_dashboard_drilldown.ts index 3e83d385936019..f42be7b4229f96 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_to_dashboard_drilldown.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_to_dashboard_drilldown.ts @@ -156,7 +156,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('Copy to space', () => { + // FLAKY: https://github.com/elastic/kibana/issues/83824 + describe.skip('Copy to space', () => { const destinationSpaceId = 'custom_space'; before(async () => { await spaces.create({ diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index 70f6fc49f00637..e7aa3e6a54e603 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -89,11 +89,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('only shows the dashboard navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql([ - 'Overview', - 'Dashboard', - 'Stack Management', - ]); + expect(navLinks.map((link) => link.text)).to.eql(['Dashboard', 'Stack Management']); }); it(`landing page shows "Create new Dashboard" button`, async () => { @@ -296,7 +292,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Dashboard']); + expect(navLinks).to.eql(['Dashboard']); }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { @@ -427,7 +423,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Dashboard']); + expect(navLinks).to.eql(['Dashboard']); }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { @@ -502,7 +498,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('no dashboard privileges', () => { + // FLAKY: https://github.com/elastic/kibana/issues/116881 + describe.skip('no dashboard privileges', () => { before(async () => { await security.role.create('no_dashboard_privileges_role', { elasticsearch: { diff --git a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts index 79ddaea13dfa52..4ee61811e5f850 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts +++ b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts @@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); + const reportingAPI = getService('reporting'); const filterBar = getService('filterBar'); const find = getService('find'); const retry = getService('retry'); @@ -124,9 +125,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Field Formatters and Scripted Fields', () => { before(async () => { + await reportingAPI.initLogs(); await esArchiver.load('x-pack/test/functional/es_archives/reporting/hugedata'); }); after(async () => { + await reportingAPI.teardownLogs(); await esArchiver.unload('x-pack/test/functional/es_archives/reporting/hugedata'); }); diff --git a/x-pack/test/functional/apps/dashboard/sync_colors.ts b/x-pack/test/functional/apps/dashboard/sync_colors.ts index 2fcc1cf5614fbf..a3628635dfa2ff 100644 --- a/x-pack/test/functional/apps/dashboard/sync_colors.ts +++ b/x-pack/test/functional/apps/dashboard/sync_colors.ts @@ -22,6 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardAddPanel = getService('dashboardAddPanel'); const filterBar = getService('filterBar'); const elasticChart = getService('elasticChart'); + const kibanaServer = getService('kibanaServer'); function getColorMapping(debugState: DebugState | null) { if (!debugState) return {}; @@ -37,12 +38,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe.skip('sync colors', function () { before(async function () { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); after(async function () { await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); it('should sync colors on dashboard by default', async function () { diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index 2010bfd56d2afb..8ebf277d63cbe1 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -91,7 +91,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = await appsMenu.readLinks(); expect(navLinks.map((link) => link.text)).to.eql([ - 'Overview', 'Discover', 'Stack Management', // because `global_discover_all_role` enables search sessions and reporting ]); @@ -201,7 +200,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Discover']); + expect(navLinks).to.eql(['Discover']); }); it(`doesn't show save button`, async () => { @@ -293,7 +292,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Discover']); + expect(navLinks).to.eql(['Discover']); }); it(`doesn't show save button`, async () => { diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index 650d67f05129c3..584558c90c2b28 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -14,6 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const queryBar = getService('queryBar'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects([ 'common', 'error', @@ -31,13 +32,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('discover field visualize button', () => { beforeEach(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); await PageObjects.common.navigateToApp('discover'); await setDiscoverTimeRange(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); it('shows "visualize" field button', async () => { diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts index 913a5034bacc5f..69f2f585d8dba5 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts @@ -67,7 +67,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Overview', 'Graph']); + expect(navLinks.map((link) => link.text)).to.eql(['Graph']); }); it('landing page shows "Create new graph" button', async () => { @@ -130,7 +130,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Graph']); + expect(navLinks).to.eql(['Graph']); }); it('does not show a "Create new Workspace" button', async () => { diff --git a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts index f7510c3c303184..95ddd0a7b59448 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; const policyName = 'testPolicy1'; +const repoName = 'test'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'indexLifecycleManagement']); @@ -16,12 +17,23 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const esClient = getService('es'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/114473 and https://github.com/elastic/kibana/issues/114474 - describe.skip('Home page', function () { + describe('Home page', function () { before(async () => { + await esClient.snapshot.createRepository({ + name: repoName, + body: { + type: 'fs', + settings: { + // use one of the values defined in path.repo in test/functional/config.js + location: '/tmp/', + }, + }, + verify: false, + }); await pageObjects.common.navigateToApp('indexLifecycleManagement'); }); after(async () => { + await esClient.snapshot.deleteRepository({ name: repoName }); await esClient.ilm.deleteLifecycle({ name: policyName }); }); @@ -41,6 +53,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { coldEnabled: true, frozenEnabled: true, deleteEnabled: true, + snapshotRepository: repoName, }); await retry.waitFor('navigation back to home page.', async () => { diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index c1610ebe0709f2..d04ec8f4d66b4a 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -179,7 +179,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('does not show Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Discover']); + expect(navLinks).to.eql(['Discover']); }); it(`doesn't show Index Patterns in management side-nav`, async () => { diff --git a/x-pack/test/functional/apps/lens/heatmap.ts b/x-pack/test/functional/apps/lens/heatmap.ts index deca06b6b351a4..5b80e6ad5cf550 100644 --- a/x-pack/test/functional/apps/lens/heatmap.ts +++ b/x-pack/test/functional/apps/lens/heatmap.ts @@ -13,7 +13,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const testSubjects = getService('testSubjects'); - describe('lens heatmap', () => { + // FLAKY: https://github.com/elastic/kibana/issues/117404 + // FLAKY: https://github.com/elastic/kibana/issues/113043 + describe.skip('lens heatmap', () => { before(async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts index 86ceb4812ad3b0..f9e4835f044afc 100644 --- a/x-pack/test/functional/apps/lens/index.ts +++ b/x-pack/test/functional/apps/lens/index.ts @@ -7,18 +7,24 @@ import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService, loadTestFile }: FtrProviderContext) { +export default function ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const log = getService('log'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['timePicker']); describe('lens app', () => { before(async () => { log.debug('Starting lens before method'); await browser.setWindowSize(1280, 800); await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.load('x-pack/test/functional/es_archives/lens/basic'); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update({ defaultIndex: 'logstash-*', 'dateFormat:tz': 'UTC' }); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/default' ); @@ -26,7 +32,10 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); await kibanaServer.importExport.unload( 'x-pack/test/functional/fixtures/kbn_archiver/lens/default' ); @@ -50,14 +59,13 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./time_shift')); loadTestFile(require.resolve('./drag_and_drop')); loadTestFile(require.resolve('./geo_field')); - loadTestFile(require.resolve('./lens_reporting')); - loadTestFile(require.resolve('./lens_tagging')); loadTestFile(require.resolve('./formula')); loadTestFile(require.resolve('./heatmap')); loadTestFile(require.resolve('./reference_lines')); loadTestFile(require.resolve('./inspector')); loadTestFile(require.resolve('./error_handling')); - + loadTestFile(require.resolve('./lens_tagging')); + loadTestFile(require.resolve('./lens_reporting')); // has to be last one in the suite because it overrides saved objects loadTestFile(require.resolve('./rollup')); }); diff --git a/x-pack/test/functional/apps/lens/lens_reporting.ts b/x-pack/test/functional/apps/lens/lens_reporting.ts index bd0c003ff3f45c..2d0ff14ef95f25 100644 --- a/x-pack/test/functional/apps/lens/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/lens_reporting.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'dashboard', 'reporting']); + const PageObjects = getPageObjects(['common', 'dashboard', 'reporting', 'timePicker']); const es = getService('es'); const esArchiver = getService('esArchiver'); const listingTable = getService('listingTable'); @@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('lens reporting', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/reporting'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await security.testUser.setRoles( [ 'test_logstash_reader', @@ -30,6 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/lens/reporting'); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); await es.deleteByQuery({ index: '.reporting-*', refresh: true, diff --git a/x-pack/test/functional/apps/lens/lens_tagging.ts b/x-pack/test/functional/apps/lens/lens_tagging.ts index cbe04b26830d63..3852fdb0456acf 100644 --- a/x-pack/test/functional/apps/lens/lens_tagging.ts +++ b/x-pack/test/functional/apps/lens/lens_tagging.ts @@ -11,8 +11,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const listingTable = getService('listingTable'); const testSubjects = getService('testSubjects'); - const esArchiver = getService('esArchiver'); const retry = getService('retry'); + const esArchiver = getService('esArchiver'); const find = getService('find'); const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); @@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'dashboard', 'visualize', 'lens', + 'timePicker', ]); const lensTag = 'extreme-lens-tag'; @@ -31,12 +32,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('lens tagging', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); + after(async () => { + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + }); + it('adds a new tag to a Lens visualization', async () => { // create lens await dashboardAddPanel.clickCreateNewLink(); diff --git a/x-pack/test/functional/apps/lens/rollup.ts b/x-pack/test/functional/apps/lens/rollup.ts index 267c83cda1386d..7de0d7e76c95cc 100644 --- a/x-pack/test/functional/apps/lens/rollup.ts +++ b/x-pack/test/functional/apps/lens/rollup.ts @@ -9,21 +9,22 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'lens', 'header']); + const PageObjects = getPageObjects(['visualize', 'lens', 'header', 'timePicker']); const find = getService('find'); const listingTable = getService('listingTable'); const esArchiver = getService('esArchiver'); - // FAILING: https://github.com/elastic/kibana/issues/84957 - describe.skip('lens rollup tests', () => { + describe('lens rollup tests', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/rollup/data'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/rollup/config'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/lens/rollup/data'); await esArchiver.unload('x-pack/test/functional/es_archives/lens/rollup/config'); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); }); it('should allow creation of lens xy chart', async () => { diff --git a/x-pack/test/functional/apps/lens/table.ts b/x-pack/test/functional/apps/lens/table.ts index 892534eec7033b..94bc5e8b266ea5 100644 --- a/x-pack/test/functional/apps/lens/table.ts +++ b/x-pack/test/functional/apps/lens/table.ts @@ -60,12 +60,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal('@timestamp per 3 hours'); expect(await PageObjects.lens.getDatatableHeaderText(2)).to.equal('Average of bytes'); - await PageObjects.lens.toggleColumnVisibility('lnsDatatable_rows > lns-dimensionTrigger'); + await PageObjects.lens.toggleColumnVisibility('lnsDatatable_rows > lns-dimensionTrigger', 1); expect(await PageObjects.lens.getDatatableHeaderText(0)).to.equal('@timestamp per 3 hours'); expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal('Average of bytes'); - await PageObjects.lens.toggleColumnVisibility('lnsDatatable_rows > lns-dimensionTrigger'); + await PageObjects.lens.toggleColumnVisibility('lnsDatatable_rows > lns-dimensionTrigger', 4); expect(await PageObjects.lens.getDatatableHeaderText(0)).to.equal('Top values of ip'); expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal('@timestamp per 3 hours'); diff --git a/x-pack/test/functional/apps/management/feature_controls/management_security.ts b/x-pack/test/functional/apps/management/feature_controls/management_security.ts index 8235bf6e1e9e26..5366274cd6f587 100644 --- a/x-pack/test/functional/apps/management/feature_controls/management_security.ts +++ b/x-pack/test/functional/apps/management/feature_controls/management_security.ts @@ -36,7 +36,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should not show the Stack Management nav link', async () => { const links = await appsMenu.readLinks(); - expect(links.map((link) => link.text)).to.eql(['Overview', 'Dashboard']); + expect(links.map((link) => link.text)).to.eql(['Dashboard']); }); it('should render the "application not found" view when navigating to management directly', async () => { diff --git a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts index b141aeea16cfe2..dcd82ea05ccf3b 100644 --- a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts +++ b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts @@ -165,7 +165,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Maps']); + expect(navLinks).to.eql(['Maps']); }); it(`does not show create new button`, async () => { diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js index 33184f2d352131..6a2a843682f26d 100644 --- a/x-pack/test/functional/apps/maps/index.js +++ b/x-pack/test/functional/apps/maps/index.js @@ -79,7 +79,7 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./joins')); loadTestFile(require.resolve('./mapbox_styles')); loadTestFile(require.resolve('./mvt_scaling')); - loadTestFile(require.resolve('./mvt_super_fine')); + loadTestFile(require.resolve('./mvt_geotile_grid')); loadTestFile(require.resolve('./add_layer_panel')); loadTestFile(require.resolve('./import_geojson')); loadTestFile(require.resolve('./layer_errors')); diff --git a/x-pack/test/functional/apps/maps/mapbox_styles.js b/x-pack/test/functional/apps/maps/mapbox_styles.js index 471e7440822c5d..a2b79156ea32e8 100644 --- a/x-pack/test/functional/apps/maps/mapbox_styles.js +++ b/x-pack/test/functional/apps/maps/mapbox_styles.js @@ -120,7 +120,6 @@ export default function ({ getPageObjects, getService }) { maxzoom: 24, filter: [ 'all', - ['!=', ['get', '__kbn_is_centroid_feature__'], true], ['any', ['==', ['geometry-type'], 'Polygon'], ['==', ['geometry-type'], 'MultiPolygon']], ['==', ['get', '__kbn_isvisibleduetojoin__'], true], ], @@ -196,7 +195,6 @@ export default function ({ getPageObjects, getService }) { maxzoom: 24, filter: [ 'all', - ['!=', ['get', '__kbn_is_centroid_feature__'], true], [ 'any', ['==', ['geometry-type'], 'Polygon'], diff --git a/x-pack/test/functional/apps/maps/mvt_super_fine.js b/x-pack/test/functional/apps/maps/mvt_geotile_grid.js similarity index 52% rename from x-pack/test/functional/apps/maps/mvt_super_fine.js rename to x-pack/test/functional/apps/maps/mvt_geotile_grid.js index 6c5065a77c1d29..ffda75f8bf98ae 100644 --- a/x-pack/test/functional/apps/maps/mvt_super_fine.js +++ b/x-pack/test/functional/apps/maps/mvt_geotile_grid.js @@ -14,13 +14,12 @@ export default function ({ getPageObjects, getService }) { const inspector = getService('inspector'); const security = getService('security'); - describe('mvt grid layer', () => { + describe('mvt geotile grid layer', () => { before(async () => { await security.testUser.setRoles( ['global_maps_all', 'test_logstash_reader', 'geoshape_data_reader'], false ); - await PageObjects.maps.loadSavedMap('geo grid vector grid example SUPER_FINE resolution'); }); after(async () => { @@ -28,7 +27,8 @@ export default function ({ getPageObjects, getService }) { await security.testUser.restoreDefaults(); }); - it('should render with mvt-source', async () => { + it('should render with mvt-source (style meta from ES)', async () => { + await PageObjects.maps.loadSavedMap('MVT geotile grid (style meta from ES)'); const mapboxStyle = await PageObjects.maps.getMapboxStyle(); //Source should be correct @@ -79,5 +79,95 @@ export default function ({ getPageObjects, getService }) { 'fill-opacity': 0.75, }); }); + + it('should render with mvt-source (style meta from local - count)', async () => { + await PageObjects.maps.loadSavedMap('MVT geotile grid (style meta from local - count)'); + const mapboxStyle = await PageObjects.maps.getMapboxStyle(); + + const fillLayer = mapboxStyle.layers.find( + (layer) => layer.id === MB_VECTOR_SOURCE_ID + '_fill' + ); + + expect(fillLayer.paint).to.eql({ + 'fill-color': [ + 'interpolate', + ['linear'], + [ + 'coalesce', + [ + 'case', + ['==', ['get', '_count'], null], + 0, + ['max', ['min', ['to-number', ['get', '_count']], 10], 1], + ], + 0, + ], + 0, + 'rgba(0,0,0,0)', + 1, + '#ecf1f7', + 2.125, + '#d9e3ef', + 3.25, + '#c5d5e7', + 4.375, + '#b2c7df', + 5.5, + '#9eb9d8', + 6.625, + '#8bacd0', + 7.75, + '#769fc8', + 8.875, + '#6092c0', + ], + 'fill-opacity': 0.75, + }); + }); + + it('should render with mvt-source (style meta from local - metric)', async () => { + await PageObjects.maps.loadSavedMap('MVT geotile grid (style meta from local - metric)'); + const mapboxStyle = await PageObjects.maps.getMapboxStyle(); + + const fillLayer = mapboxStyle.layers.find( + (layer) => layer.id === MB_VECTOR_SOURCE_ID + '_fill' + ); + + expect(fillLayer.paint).to.eql({ + 'fill-color': [ + 'interpolate', + ['linear'], + [ + 'coalesce', + [ + 'case', + ['==', ['get', 'sum_of_bytes.value'], null], + -1, + ['max', ['min', ['to-number', ['get', 'sum_of_bytes.value']], 14941], 0], + ], + -1, + ], + -1, + 'rgba(0,0,0,0)', + 0, + '#ecf1f7', + 1867.625, + '#d9e3ef', + 3735.25, + '#c5d5e7', + 5602.875, + '#b2c7df', + 7470.5, + '#9eb9d8', + 9338.125, + '#8bacd0', + 11205.75, + '#769fc8', + 13073.375, + '#6092c0', + ], + 'fill-opacity': 0.75, + }); + }); }); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/forecasts.ts b/x-pack/test/functional/apps/ml/anomaly_detection/forecasts.ts new file mode 100644 index 00000000000000..f65653e2c03c5c --- /dev/null +++ b/x-pack/test/functional/apps/ml/anomaly_detection/forecasts.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; + +// @ts-expect-error not full interface +const JOB_CONFIG: Job = { + job_id: `fq_single_1_smv`, + description: 'count() on farequote dataset with 15m bucket span', + groups: ['farequote', 'automated', 'single-metric'], + analysis_config: { + bucket_span: '15m', + influencers: [], + detectors: [ + { + function: 'count', + }, + ], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '10mb' }, + model_plot_config: { enabled: true }, +}; + +// @ts-expect-error not full interface +const DATAFEED_CONFIG: Datafeed = { + datafeed_id: 'datafeed-fq_single_1_smv', + indices: ['ft_farequote'], + job_id: 'fq_single_1_smv', + query: { bool: { must: [{ match_all: {} }] } }, +}; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + describe('forecasts', function () { + this.tags(['mlqa']); + + describe('with single metric job', function () { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it('opens a job from job list link', async () => { + await ml.testExecution.logTestStep('navigate to job list'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.testExecution.logTestStep('open job in single metric viewer'); + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id, 1); + + await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id); + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); + }); + + it('displays job results', async () => { + await ml.testExecution.logTestStep('pre-fills the job selection'); + await ml.jobSelection.assertJobSelection([JOB_CONFIG.job_id]); + + await ml.testExecution.logTestStep('pre-fills the detector input'); + await ml.singleMetricViewer.assertDetectorInputExist(); + await ml.singleMetricViewer.assertDetectorInputValue('0'); + + await ml.testExecution.logTestStep('displays the chart'); + await ml.singleMetricViewer.assertChartExist(); + + await ml.testExecution.logTestStep('should not display the forecasts toggle checkbox'); + await ml.forecast.assertForecastCheckboxMissing(); + + await ml.testExecution.logTestStep('should open the forecasts modal'); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastModalRunButtonEnabled(true); + + await ml.testExecution.logTestStep('should run the forecast and close the modal'); + await ml.forecast.clickForecastModalRunButton(); + + await ml.testExecution.logTestStep('should display the forecasts toggle checkbox'); + await ml.forecast.assertForecastCheckboxExists(); + + await ml.testExecution.logTestStep( + 'should display the forecast in the single metric chart' + ); + await ml.forecast.assertForecastChartElementsExists(); + + await ml.testExecution.logTestStep('should hide the forecast in the single metric chart'); + await ml.forecast.clickForecastCheckbox(); + await ml.forecast.assertForecastChartElementsHidden(); + + await ml.testExecution.logTestStep('should open the forecasts modal and list the forecast'); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastTableExists(); + await ml.forecast.assertForecastTableNotEmpty(); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts index d87da8469db118..ed5f618f86644c 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts @@ -24,5 +24,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./annotations')); loadTestFile(require.resolve('./aggregated_scripted_job')); loadTestFile(require.resolve('./custom_urls')); + loadTestFile(require.resolve('./forecasts')); }); } 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/feature_importance.ts index 3faee67c01a531..725030605840c5 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts @@ -14,8 +14,7 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - // FLAKY: https://github.com/elastic/kibana/issues/116078 - describe.skip('total feature importance panel and decision path popover', function () { + describe('total feature importance panel and decision path popover', function () { const testDataList: Array<{ suiteTitle: string; archive: string; @@ -64,6 +63,7 @@ export default function ({ getService }: FtrProviderContext) { training_percent: 35, prediction_field_name: 'CentralAir_prediction', num_top_classes: -1, + max_trees: 10, }, }, model_memory_limit: '60mb', @@ -109,6 +109,7 @@ export default function ({ getService }: FtrProviderContext) { training_percent: 35, prediction_field_name: 'heatingqc', num_top_classes: -1, + max_trees: 10, }, }, model_memory_limit: '60mb', @@ -140,6 +141,7 @@ export default function ({ getService }: FtrProviderContext) { dependent_variable: 'stab', num_top_feature_importance_values: 5, training_percent: 35, + max_trees: 10, }, }, analyzed_fields: { diff --git a/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts b/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts index 1c47893dbafd08..66a32a888b77a3 100644 --- a/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts +++ b/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts @@ -6,42 +6,16 @@ */ import { FtrProviderContext } from '../../../ftr_provider_context'; -import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; - -// @ts-expect-error not full interface -const JOB_CONFIG: Job = { - job_id: `fq_multi_1_ae`, - description: - 'mean/min/max(responsetime) partition=airline on farequote dataset with 1h bucket span', - groups: ['farequote', 'automated', 'multi-metric'], - analysis_config: { - bucket_span: '1h', - influencers: ['airline'], - detectors: [ - { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }, - { function: 'min', field_name: 'responsetime', partition_field_name: 'airline' }, - { function: 'max', field_name: 'responsetime', partition_field_name: 'airline' }, - ], - }, - data_description: { time_field: '@timestamp' }, - analysis_limits: { model_memory_limit: '20mb' }, - model_plot_config: { enabled: true }, -}; - -// @ts-expect-error not full interface -const DATAFEED_CONFIG: Datafeed = { - datafeed_id: 'datafeed-fq_multi_1_ae', - indices: ['ft_farequote'], - job_id: 'fq_multi_1_ae', - query: { bool: { must: [{ match_all: {} }] } }, -}; +import { JOB_CONFIG, DATAFEED_CONFIG, ML_EMBEDDABLE_TYPES } from './constants'; const testDataList = [ { + type: 'testData', suiteSuffix: 'with multi metric job', panelTitle: `ML anomaly charts for ${JOB_CONFIG.job_id}`, jobConfig: JOB_CONFIG, datafeedConfig: DATAFEED_CONFIG, + dashboardTitle: `ML anomaly charts for fq_multi_1_ae ${Date.now()}`, expected: { influencers: [ { @@ -59,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const ml = getService('ml'); const PageObjects = getPageObjects(['common', 'timePicker', 'dashboard']); - describe('anomaly charts', function () { + describe('anomaly charts in dashboard', function () { this.tags(['mlqa']); before(async () => { @@ -69,6 +43,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await ml.securityUI.loginAsMlPowerUser(); }); + after(async () => { + await ml.api.cleanMlIndices(); + }); + for (const testData of testDataList) { describe(testData.suiteSuffix, function () { before(async () => { @@ -79,14 +57,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('dashboard'); }); - after(async () => { - await ml.api.cleanMlIndices(); - }); - it('can open job selection flyout', async () => { - await PageObjects.dashboard.clickCreateDashboardPrompt(); + await PageObjects.dashboard.clickNewDashboard(); await ml.dashboardEmbeddables.assertDashboardIsEmpty(); - await ml.dashboardEmbeddables.openJobSelectionFlyout(); + await ml.dashboardEmbeddables.openAnomalyJobSelectionFlyout( + ML_EMBEDDABLE_TYPES.ANOMALY_CHARTS + ); }); it('can select jobs', async () => { @@ -109,6 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.pauseAutoRefresh(); await ml.dashboardEmbeddables.assertAnomalyChartsSeverityThresholdControlExists(); await ml.dashboardEmbeddables.assertAnomalyChartsExists(); + await PageObjects.dashboard.saveDashboard(testData.dashboardTitle); }); }); } diff --git a/x-pack/test/functional/apps/ml/embeddables/anomaly_embeddables_migration.ts b/x-pack/test/functional/apps/ml/embeddables/anomaly_embeddables_migration.ts new file mode 100644 index 00000000000000..a7fcfa1b83475e --- /dev/null +++ b/x-pack/test/functional/apps/ml/embeddables/anomaly_embeddables_migration.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { JOB_CONFIG, DATAFEED_CONFIG, ML_EMBEDDABLE_TYPES } from './constants'; + +const testDataList = [ + { + type: ML_EMBEDDABLE_TYPES.ANOMALY_SWIMLANE, + panelTitle: 'ML anomaly swim lane', + dashboardSavedObject: { + type: 'dashboard', + attributes: { + title: `7.15.2 ML anomaly swimlane dashboard ${Date.now()}`, + description: '', + panelsJSON: `[{"version":"7.15.2","type":"ml_anomaly_swimlane","gridData":{"x":0,"y":0,"w":24,"h":15,"i":"c177ed0a-dea0-40f8-8980-cfb0c6bc13a8"},"panelIndex":"c177ed0a-dea0-40f8-8980-cfb0c6bc13a8","embeddableConfig":{"jobIds":["fq_multi_1_ae"],"swimlaneType":"viewBy","viewBy":"airline","enhancements":{}},"title":"ML anomaly swim lane"}]`, + optionsJSON: '{"useMargins":true,"syncColors":false,"hidePanelTitles":false}', + timeRestore: true, + timeTo: '2016-02-11T00:00:00.000Z', + timeFrom: '2016-02-07T00:00:00.000Z', + refreshInterval: { + pause: true, + value: 0, + }, + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', + }, + }, + coreMigrationVersion: '7.15.2', + }, + }, + { + type: ML_EMBEDDABLE_TYPES.ANOMALY_CHARTS, + panelTitle: 'ML anomaly charts', + dashboardSavedObject: { + type: 'dashboard', + attributes: { + title: `7.15.2 ML anomaly charts dashboard ${Date.now()}`, + description: '', + panelsJSON: + '[{"version":"7.15.2","type":"ml_anomaly_charts","gridData":{"x":0,"y":0,"w":38,"h":21,"i":"1155890b-c19c-4d98-8153-50e6434612f1"},"panelIndex":"1155890b-c19c-4d98-8153-50e6434612f1","embeddableConfig":{"jobIds":["fq_multi_1_ae"],"maxSeriesToPlot":6,"severityThreshold":0,"enhancements":{}},"title":"ML anomaly charts"}]', + optionsJSON: '{"useMargins":true,"syncColors":false,"hidePanelTitles":false}', + timeRestore: true, + timeTo: '2016-02-11T00:00:00.000Z', + timeFrom: '2016-02-07T00:00:00.000Z', + refreshInterval: { + pause: true, + value: 0, + }, + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', + }, + }, + coreMigrationVersion: '7.15.2', + }, + }, +]; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + const PageObjects = getPageObjects(['common', 'timePicker', 'dashboard']); + + describe('anomaly embeddables migration in Dashboard', function () { + this.tags(['mlqa']); + + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + + await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG); + // Using bulk API because create API might return 400 for conflict errors + await ml.testResources.createBulkSavedObjects( + testDataList.map((d) => d.dashboardSavedObject) + ); + + await PageObjects.common.navigateToApp('dashboard'); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + for (const testData of testDataList) { + const { dashboardSavedObject, panelTitle, type } = testData; + describe(`for ${panelTitle}`, function () { + before(async () => { + await PageObjects.common.navigateToApp('dashboard'); + }); + + after(async () => { + await ml.testResources.deleteDashboardByTitle(dashboardSavedObject.attributes.title); + }); + + it(`loads saved dashboard from version ${dashboardSavedObject.coreMigrationVersion}`, async () => { + await PageObjects.dashboard.loadSavedDashboard(dashboardSavedObject.attributes.title); + + await ml.dashboardEmbeddables.assertDashboardPanelExists(panelTitle); + + if (type === ML_EMBEDDABLE_TYPES.ANOMALY_CHARTS) { + await ml.dashboardEmbeddables.assertAnomalyChartsSeverityThresholdControlExists(); + await ml.dashboardEmbeddables.assertAnomalyChartsExists(); + } + + if (type === ML_EMBEDDABLE_TYPES.ANOMALY_SWIMLANE) { + await ml.dashboardEmbeddables.assertAnomalySwimlaneExists(); + } + }); + }); + } + }); +} diff --git a/x-pack/test/functional/apps/ml/embeddables/constants.ts b/x-pack/test/functional/apps/ml/embeddables/constants.ts new file mode 100644 index 00000000000000..f315b7ee44dc86 --- /dev/null +++ b/x-pack/test/functional/apps/ml/embeddables/constants.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Datafeed, Job } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; + +// @ts-expect-error not full interface +export const JOB_CONFIG: Job = { + job_id: `fq_multi_1_ae`, + description: + 'mean/min/max(responsetime) partition=airline on farequote dataset with 1h bucket span', + groups: ['farequote', 'automated', 'multi-metric'], + analysis_config: { + bucket_span: '1h', + influencers: ['airline'], + detectors: [ + { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'min', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'max', field_name: 'responsetime', partition_field_name: 'airline' }, + ], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '20mb' }, + model_plot_config: { enabled: true }, +}; + +// @ts-expect-error not full interface +export const DATAFEED_CONFIG: Datafeed = { + datafeed_id: 'datafeed-fq_multi_1_ae', + indices: ['ft_farequote'], + job_id: 'fq_multi_1_ae', + query: { bool: { must: [{ match_all: {} }] } }, +}; + +export const ML_EMBEDDABLE_TYPES = { + ANOMALY_SWIMLANE: 'ml_anomaly_swimlane', + ANOMALY_CHARTS: 'ml_anomaly_charts', +} as const; diff --git a/x-pack/test/functional/apps/ml/embeddables/index.ts b/x-pack/test/functional/apps/ml/embeddables/index.ts index dc059a1862c801..31074a59866a60 100644 --- a/x-pack/test/functional/apps/ml/embeddables/index.ts +++ b/x-pack/test/functional/apps/ml/embeddables/index.ts @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('embeddables', function () { this.tags(['skipFirefox']); loadTestFile(require.resolve('./anomaly_charts_dashboard_embeddables')); + loadTestFile(require.resolve('./anomaly_embeddables_migration')); }); } diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index 448774f1a0c7f1..356e3822179647 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -237,11 +237,11 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep( 'should display the forecast modal with enabled run button' ); - await ml.singleMetricViewer.assertForecastButtonExists(); - await ml.singleMetricViewer.assertForecastButtonEnabled(true); - await ml.singleMetricViewer.openForecastModal(); - await ml.singleMetricViewer.assertForecastModalRunButtonEnabled(true); - await ml.singleMetricViewer.closeForecastModal(); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastModalRunButtonEnabled(true); + await ml.forecast.closeForecastModal(); }); it('should display elements on Anomaly Explorer page correctly', async () => { diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index b96da748507866..be57904b944514 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -230,11 +230,11 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep( 'should display the forecast modal with disabled run button' ); - await ml.singleMetricViewer.assertForecastButtonExists(); - await ml.singleMetricViewer.assertForecastButtonEnabled(true); - await ml.singleMetricViewer.openForecastModal(); - await ml.singleMetricViewer.assertForecastModalRunButtonEnabled(false); - await ml.singleMetricViewer.closeForecastModal(); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastModalRunButtonEnabled(false); + await ml.forecast.closeForecastModal(); }); it('should display elements on Anomaly Explorer page correctly', async () => { diff --git a/x-pack/test/functional/apps/monitoring/index.js b/x-pack/test/functional/apps/monitoring/index.js index a67964d325164b..20217399a91913 100644 --- a/x-pack/test/functional/apps/monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/index.js @@ -34,9 +34,14 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./kibana/instance')); loadTestFile(require.resolve('./kibana/instance_mb')); + loadTestFile(require.resolve('./logstash/overview')); + loadTestFile(require.resolve('./logstash/overview_mb')); + loadTestFile(require.resolve('./logstash/nodes')); + loadTestFile(require.resolve('./logstash/nodes_mb')); loadTestFile(require.resolve('./logstash/pipelines')); loadTestFile(require.resolve('./logstash/pipelines_mb')); - + loadTestFile(require.resolve('./logstash/node_detail')); + loadTestFile(require.resolve('./logstash/node_detail_mb')); loadTestFile(require.resolve('./beats/cluster')); loadTestFile(require.resolve('./beats/overview')); loadTestFile(require.resolve('./beats/listing')); diff --git a/x-pack/test/functional/apps/monitoring/logstash/node_detail.js b/x-pack/test/functional/apps/monitoring/logstash/node_detail.js new file mode 100644 index 00000000000000..2e75422f3028fd --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/logstash/node_detail.js @@ -0,0 +1,125 @@ +/* + * 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 { getLifecycleMethods } from '../_get_lifecycle_methods'; + +export default function ({ getService, getPageObjects }) { + const PageObjects = getPageObjects(['common']); + const retry = getService('retry'); + const clusterOverview = getService('monitoringClusterOverview'); + const nodes = getService('monitoringLogstashNodes'); + const nodeDetail = getService('monitoringLogstashNodeDetail'); + const pipelinesList = getService('monitoringLogstashPipelines'); + + describe('Logstash node detail', () => { + describe('Node', () => { + const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); + const nodeSummaryStatus = { + eventsIn: 'Events Received\n42.4k', + eventsOut: 'Events Emitted\n39.4k', + httpAddress: 'Transport Address\n127.0.0.1:9600', + numReloads: 'Config Reloads\n0', + pipelineBatchSize: 'Batch Size\n125', + pipelineWorkers: 'Pipeline Workers\n8', + uptime: 'Uptime\n8 minutes', + version: 'Version\n7.0.0-alpha1', + }; + + before(async () => { + await setup('x-pack/test/functional/es_archives/monitoring/logstash_pipelines', { + from: 'Jan 22, 2018 @ 09:10:00.000', + to: 'Jan 22, 2018 @ 09:41:00.000', + }); + + await clusterOverview.closeAlertsModal(); + + // go to logstash nodes + await clusterOverview.clickLsNodes(); + expect(await nodes.isOnNodesListing()).to.be(true); + }); + + after(async () => { + await tearDown(); + }); + + it('detail view should have summary status showing correct info', async () => { + await nodes.clickRowByResolver('29a3dfa6-c146-4534-9bc0-be475d2ce950'); + expect(await nodeDetail.getSummary()).to.eql(nodeSummaryStatus); + }); + it('advanced view should have summary status showing correct info', async () => { + await nodeDetail.clickAdvanced(); + + expect(await nodeDetail.getSummary()).to.eql(nodeSummaryStatus); + }); + it('pipelines view should have summary status showing correct info', async () => { + await nodeDetail.clickPipelines(); + + expect(await nodeDetail.getSummary()).to.eql(nodeSummaryStatus); + }); + it('pipelines view should have Pipelines table showing correct rows with default sorting', async () => { + const rows = await pipelinesList.getRows(); + expect(rows.length).to.be(3); + + await pipelinesList.clickIdCol(); + + const pipelinesAll = await pipelinesList.getPipelinesAll(); + + const tableData = [ + { id: 'nginx_logs', eventsEmittedRate: '62.5 e/s', nodeCount: '1' }, + { id: 'test_interpolation', eventsEmittedRate: '0 e/s', nodeCount: '1' }, + { id: 'tweets_about_labradoodles', eventsEmittedRate: '1.2 e/s', nodeCount: '1' }, + ]; + + // check the all data in the table + pipelinesAll.forEach((obj, index) => { + expect(pipelinesAll[index].id).to.be(tableData[index].id); + expect(pipelinesAll[index].eventsEmittedRate).to.be(tableData[index].eventsEmittedRate); + expect(pipelinesAll[index].nodeCount).to.be(tableData[index].nodeCount); + }); + }); + + it('should have Pipelines Table showing correct rows after sorting by Events Emitted Rate Asc', async () => { + await pipelinesList.clickEventsEmittedRateCol(); + + const rows = await pipelinesList.getRows(); + expect(rows.length).to.be(3); + + const pipelinesAll = await pipelinesList.getPipelinesAll(); + + const tableData = [ + { id: 'test_interpolation', eventsEmittedRate: '0 e/s', nodeCount: '1' }, + { id: 'tweets_about_labradoodles', eventsEmittedRate: '1.2 e/s', nodeCount: '1' }, + { id: 'nginx_logs', eventsEmittedRate: '62.5 e/s', nodeCount: '1' }, + ]; + + // check the all data in the table + pipelinesAll.forEach((obj, index) => { + expect(pipelinesAll[index].id).to.be(tableData[index].id); + expect(pipelinesAll[index].eventsEmittedRate).to.be(tableData[index].eventsEmittedRate); + expect(pipelinesAll[index].nodeCount).to.be(tableData[index].nodeCount); + }); + }); + + it('should filter for non-existent pipeline', async () => { + await pipelinesList.setFilter('foobar'); + await pipelinesList.assertNoData(); + await pipelinesList.clearFilter(); + }); + + it('should filter for specific pipelines', async () => { + await pipelinesList.setFilter('la'); + await PageObjects.common.pressEnterKey(); + await retry.try(async () => { + const rows = await pipelinesList.getRows(); + expect(rows.length).to.be(2); + }); + await pipelinesList.clearFilter(); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/logstash/node_detail_mb.js b/x-pack/test/functional/apps/monitoring/logstash/node_detail_mb.js new file mode 100644 index 00000000000000..aba95b42c3043e --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/logstash/node_detail_mb.js @@ -0,0 +1,125 @@ +/* + * 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 { getLifecycleMethods } from '../_get_lifecycle_methods'; + +export default function ({ getService, getPageObjects }) { + const PageObjects = getPageObjects(['common']); + const retry = getService('retry'); + const clusterOverview = getService('monitoringClusterOverview'); + const nodes = getService('monitoringLogstashNodes'); + const nodeDetail = getService('monitoringLogstashNodeDetail'); + const pipelinesList = getService('monitoringLogstashPipelines'); + + describe('Logstash node detail mb', () => { + describe('Node', () => { + const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); + const nodeSummaryStatus = { + eventsIn: 'Events Received\n42.4k', + eventsOut: 'Events Emitted\n39.4k', + httpAddress: 'Transport Address\n127.0.0.1:9600', + numReloads: 'Config Reloads\n0', + pipelineBatchSize: 'Batch Size\n125', + pipelineWorkers: 'Pipeline Workers\n8', + uptime: 'Uptime\n8 minutes', + version: 'Version\n7.0.0-alpha1', + }; + + before(async () => { + await setup('x-pack/test/functional/es_archives/monitoring/logstash_pipelines_mb', { + from: 'Jan 22, 2018 @ 09:10:00.000', + to: 'Jan 22, 2018 @ 09:41:00.000', + }); + + await clusterOverview.closeAlertsModal(); + + // go to logstash nodes + await clusterOverview.clickLsNodes(); + expect(await nodes.isOnNodesListing()).to.be(true); + await nodes.clickRowByResolver('29a3dfa6-c146-4534-9bc0-be475d2ce950'); + }); + + after(async () => { + await tearDown(); + }); + + it('detail view should have summary status showing correct info', async () => { + expect(await nodeDetail.getSummary()).to.eql(nodeSummaryStatus); + }); + it('advanced view should have summary status showing correct info', async () => { + await nodeDetail.clickAdvanced(); + + expect(await nodeDetail.getSummary()).to.eql(nodeSummaryStatus); + }); + it('pipelines view should have summary status showing correct info', async () => { + await nodeDetail.clickPipelines(); + + expect(await nodeDetail.getSummary()).to.eql(nodeSummaryStatus); + }); + it('pipelines view should have Pipelines table showing correct rows with default sorting', async () => { + const rows = await pipelinesList.getRows(); + expect(rows.length).to.be(3); + + await pipelinesList.clickIdCol(); + + const pipelinesAll = await pipelinesList.getPipelinesAll(); + + const tableData = [ + { id: 'nginx_logs', eventsEmittedRate: '62.5 e/s', nodeCount: '1' }, + { id: 'test_interpolation', eventsEmittedRate: '0 e/s', nodeCount: '1' }, + { id: 'tweets_about_labradoodles', eventsEmittedRate: '1.2 e/s', nodeCount: '1' }, + ]; + + // check the all data in the table + pipelinesAll.forEach((obj, index) => { + expect(pipelinesAll[index].id).to.be(tableData[index].id); + expect(pipelinesAll[index].eventsEmittedRate).to.be(tableData[index].eventsEmittedRate); + expect(pipelinesAll[index].nodeCount).to.be(tableData[index].nodeCount); + }); + }); + + it('should have Pipelines Table showing correct rows after sorting by Events Emitted Rate Asc', async () => { + await pipelinesList.clickEventsEmittedRateCol(); + + const rows = await pipelinesList.getRows(); + expect(rows.length).to.be(3); + + const pipelinesAll = await pipelinesList.getPipelinesAll(); + + const tableData = [ + { id: 'test_interpolation', eventsEmittedRate: '0 e/s', nodeCount: '1' }, + { id: 'tweets_about_labradoodles', eventsEmittedRate: '1.2 e/s', nodeCount: '1' }, + { id: 'nginx_logs', eventsEmittedRate: '62.5 e/s', nodeCount: '1' }, + ]; + + // check the all data in the table + pipelinesAll.forEach((obj, index) => { + expect(pipelinesAll[index].id).to.be(tableData[index].id); + expect(pipelinesAll[index].eventsEmittedRate).to.be(tableData[index].eventsEmittedRate); + expect(pipelinesAll[index].nodeCount).to.be(tableData[index].nodeCount); + }); + }); + + it('should filter for non-existent pipeline', async () => { + await pipelinesList.setFilter('foobar'); + await pipelinesList.assertNoData(); + await pipelinesList.clearFilter(); + }); + + it('should filter for specific pipelines', async () => { + await pipelinesList.setFilter('la'); + await PageObjects.common.pressEnterKey(); + await retry.try(async () => { + const rows = await pipelinesList.getRows(); + expect(rows.length).to.be(2); + }); + await pipelinesList.clearFilter(); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/logstash/nodes.js b/x-pack/test/functional/apps/monitoring/logstash/nodes.js new file mode 100644 index 00000000000000..75e3c7bac7c01c --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/logstash/nodes.js @@ -0,0 +1,104 @@ +/* + * 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 { getLifecycleMethods } from '../_get_lifecycle_methods'; + +export default function ({ getService, getPageObjects }) { + const clusterOverview = getService('monitoringClusterOverview'); + const nodes = getService('monitoringLogstashNodes'); + const logstashSummaryStatus = getService('monitoringLogstashSummaryStatus'); + + describe('Logstash nodes', () => { + const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); + + before(async () => { + await setup('x-pack/test/functional/es_archives/monitoring/logstash_pipelines', { + from: 'Jan 22, 2018 @ 09:10:00.000', + to: 'Jan 22, 2018 @ 09:41:00.000', + }); + + await clusterOverview.closeAlertsModal(); + + // go to logstash nodes + await clusterOverview.clickLsNodes(); + expect(await nodes.isOnNodesListing()).to.be(true); + }); + + after(async () => { + await tearDown(); + }); + it('should have Logstash Cluster Summary Status showing correct info', async () => { + expect(await logstashSummaryStatus.getContent()).to.eql({ + eventsInTotal: 'Events Received\n117.9k', + eventsOutTotal: 'Events Emitted\n111.9k', + memoryUsed: 'Memory\n528.4 MB / 1.9 GB', + nodeCount: 'Nodes\n2', + }); + }); + it('should have a nodes table with the correct number of rows', async () => { + const rows = await nodes.getRows(); + expect(rows.length).to.be(2); + }); + it('should have a nodes table with the correct data', async () => { + const nodesAll = await nodes.getNodesAll(); + + const tableData = [ + { + id: 'Shaunaks-MacBook-Pro.local', + httpAddress: '127.0.0.1:9601', + alertStatus: 'Clear', + cpuUsage: '0.00%', + loadAverage: '4.54', + jvmHeapUsed: '22.00%', + eventsOut: '73.5k', + configReloadsSuccess: '0 successes', + configReloadsFailure: '0 failures', + version: '7.0.0-alpha1', + }, + { + id: 'Shaunaks-MacBook-Pro.local', + httpAddress: '127.0.0.1:9600', + alertStatus: 'Clear', + cpuUsage: '2.00%', + loadAverage: '4.76', + jvmHeapUsed: '30.00%', + eventsOut: '38.4k', + configReloadsSuccess: '0 successes', + configReloadsFailure: '0 failures', + version: '7.0.0-alpha1', + }, + ]; + + nodesAll.forEach((obj, index) => { + expect(nodesAll[index].id).to.be(tableData[index].id); + expect(nodesAll[index].httpAddress).to.be(tableData[index].httpAddress); + expect(nodesAll[index].alertStatus).to.be(tableData[index].alertStatus); + expect(nodesAll[index].cpuUsage).to.be(tableData[index].cpuUsage); + expect(nodesAll[index].loadAverage).to.be(tableData[index].loadAverage); + expect(nodesAll[index].jvmHeapUsed).to.be(tableData[index].jvmHeapUsed); + expect(nodesAll[index].eventsOut).to.be(tableData[index].eventsOut); + expect(nodesAll[index].configReloadsSuccess).to.be(tableData[index].configReloadsSuccess); + expect(nodesAll[index].configReloadsFailure).to.be(tableData[index].configReloadsFailure); + expect(nodesAll[index].version).to.be(tableData[index].version); + }); + }); + + it('should filter for non-existent node', async () => { + await nodes.setFilter('foobar'); + await nodes.assertNoData(); + await nodes.clearFilter(); + }); + + it('should filter for specific nodes', async () => { + await nodes.setFilter('sha'); + const rows = await nodes.getRows(); + expect(rows.length).to.be(2); + await nodes.clearFilter(); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/logstash/nodes_mb.js b/x-pack/test/functional/apps/monitoring/logstash/nodes_mb.js new file mode 100644 index 00000000000000..1f55d3a0c72dd0 --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/logstash/nodes_mb.js @@ -0,0 +1,104 @@ +/* + * 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 { getLifecycleMethods } from '../_get_lifecycle_methods'; + +export default function ({ getService, getPageObjects }) { + const clusterOverview = getService('monitoringClusterOverview'); + const nodes = getService('monitoringLogstashNodes'); + const logstashSummaryStatus = getService('monitoringLogstashSummaryStatus'); + + describe('Logstash nodes mb', () => { + const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); + + before(async () => { + await setup('x-pack/test/functional/es_archives/monitoring/logstash_pipelines_mb', { + from: 'Jan 22, 2018 @ 09:10:00.000', + to: 'Jan 22, 2018 @ 09:41:00.000', + }); + + await clusterOverview.closeAlertsModal(); + + // go to logstash nodes + await clusterOverview.clickLsNodes(); + expect(await nodes.isOnNodesListing()).to.be(true); + }); + + after(async () => { + await tearDown(); + }); + it('should have Logstash Cluster Summary Status showing correct info', async () => { + expect(await logstashSummaryStatus.getContent()).to.eql({ + eventsInTotal: 'Events Received\n117.9k', + eventsOutTotal: 'Events Emitted\n111.9k', + memoryUsed: 'Memory\n528.4 MB / 1.9 GB', + nodeCount: 'Nodes\n2', + }); + }); + it('should have a nodes table with the correct number of rows', async () => { + const rows = await nodes.getRows(); + expect(rows.length).to.be(2); + }); + it('should have a nodes table with the correct data', async () => { + const nodesAll = await nodes.getNodesAll(); + + const tableData = [ + { + id: 'Shaunaks-MacBook-Pro.local', + httpAddress: '127.0.0.1:9601', + alertStatus: 'Clear', + cpuUsage: '0.00%', + loadAverage: '4.54', + jvmHeapUsed: '22.00%', + eventsOut: '73.5k', + configReloadsSuccess: '0 successes', + configReloadsFailure: '0 failures', + version: '7.0.0-alpha1', + }, + { + id: 'Shaunaks-MacBook-Pro.local', + httpAddress: '127.0.0.1:9600', + alertStatus: 'Clear', + cpuUsage: '2.00%', + loadAverage: '4.76', + jvmHeapUsed: '30.00%', + eventsOut: '38.4k', + configReloadsSuccess: '0 successes', + configReloadsFailure: '0 failures', + version: '7.0.0-alpha1', + }, + ]; + + nodesAll.forEach((obj, index) => { + expect(nodesAll[index].id).to.be(tableData[index].id); + expect(nodesAll[index].httpAddress).to.be(tableData[index].httpAddress); + expect(nodesAll[index].alertStatus).to.be(tableData[index].alertStatus); + expect(nodesAll[index].cpuUsage).to.be(tableData[index].cpuUsage); + expect(nodesAll[index].loadAverage).to.be(tableData[index].loadAverage); + expect(nodesAll[index].jvmHeapUsed).to.be(tableData[index].jvmHeapUsed); + expect(nodesAll[index].eventsOut).to.be(tableData[index].eventsOut); + expect(nodesAll[index].configReloadsSuccess).to.be(tableData[index].configReloadsSuccess); + expect(nodesAll[index].configReloadsFailure).to.be(tableData[index].configReloadsFailure); + expect(nodesAll[index].version).to.be(tableData[index].version); + }); + }); + + it('should filter for non-existent node', async () => { + await nodes.setFilter('foobar'); + await nodes.assertNoData(); + await nodes.clearFilter(); + }); + + it('should filter for specific nodes', async () => { + await nodes.setFilter('sha'); + const rows = await nodes.getRows(); + expect(rows.length).to.be(2); + await nodes.clearFilter(); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/logstash/overview.js b/x-pack/test/functional/apps/monitoring/logstash/overview.js new file mode 100644 index 00000000000000..593159ec6b2669 --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/logstash/overview.js @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { getLifecycleMethods } from '../_get_lifecycle_methods'; + +export default function ({ getService, getPageObjects }) { + const clusterOverview = getService('monitoringClusterOverview'); + const overview = getService('monitoringLogstashOverview'); + const logstashSummaryStatus = getService('monitoringLogstashSummaryStatus'); + + describe('Logstash overview', () => { + const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); + + before(async () => { + await setup('x-pack/test/functional/es_archives/monitoring/logstash_pipelines', { + from: 'Jan 22, 2018 @ 09:10:00.000', + to: 'Jan 22, 2018 @ 09:41:00.000', + }); + + await clusterOverview.closeAlertsModal(); + + // go to logstash overview + await clusterOverview.clickLsOverview(); + expect(await overview.isOnOverview()).to.be(true); + }); + + after(async () => { + await tearDown(); + }); + it('should have Logstash Cluster Summary Status showing correct info', async () => { + expect(await logstashSummaryStatus.getContent()).to.eql({ + eventsInTotal: 'Events Received\n117.9k', + eventsOutTotal: 'Events Emitted\n111.9k', + memoryUsed: 'Memory\n528.4 MB / 1.9 GB', + nodeCount: 'Nodes\n2', + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/logstash/overview_mb.js b/x-pack/test/functional/apps/monitoring/logstash/overview_mb.js new file mode 100644 index 00000000000000..91faede36b1d6b --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/logstash/overview_mb.js @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { getLifecycleMethods } from '../_get_lifecycle_methods'; + +export default function ({ getService, getPageObjects }) { + const clusterOverview = getService('monitoringClusterOverview'); + const overview = getService('monitoringLogstashOverview'); + const logstashSummaryStatus = getService('monitoringLogstashSummaryStatus'); + + describe('Logstash overview mb', () => { + const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); + + before(async () => { + await setup('x-pack/test/functional/es_archives/monitoring/logstash_pipelines_mb', { + from: 'Jan 22, 2018 @ 09:10:00.000', + to: 'Jan 22, 2018 @ 09:41:00.000', + }); + + await clusterOverview.closeAlertsModal(); + + // go to logstash overview + await clusterOverview.clickLsOverview(); + expect(await overview.isOnOverview()).to.be(true); + }); + + after(async () => { + await tearDown(); + }); + it('should have an Logstash Summary Status with correct info', async () => { + expect(await logstashSummaryStatus.getContent()).to.eql({ + eventsInTotal: 'Events Received\n117.9k', + eventsOutTotal: 'Events Emitted\n111.9k', + memoryUsed: 'Memory\n528.4 MB / 1.9 GB', + nodeCount: 'Nodes\n2', + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/logstash/pipelines.js b/x-pack/test/functional/apps/monitoring/logstash/pipelines.js index 72a6ff8e1af23f..931afc83e84159 100644 --- a/x-pack/test/functional/apps/monitoring/logstash/pipelines.js +++ b/x-pack/test/functional/apps/monitoring/logstash/pipelines.js @@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }) { const pipelinesList = getService('monitoringLogstashPipelines'); const lsClusterSummaryStatus = getService('monitoringLogstashSummaryStatus'); - describe('Logstash pipelines', () => { + // FLAKY: https://github.com/elastic/kibana/issues/116070 + describe.skip('Logstash pipelines', () => { const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); before(async () => { diff --git a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts index 790909164b33d7..4dce6bca8f67a2 100644 --- a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts +++ b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - describe('Export import saved objects between versions', function () { + // Failing: See https://github.com/elastic/kibana/issues/116058 + describe.skip('Export import saved objects between versions', function () { before(async function () { await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.load('x-pack/test/functional/es_archives/getting_started/shakespeare'); diff --git a/x-pack/test/functional/apps/spaces/copy_saved_objects.ts b/x-pack/test/functional/apps/spaces/copy_saved_objects.ts index 2d2fdf61a94b68..807e0fff16ff17 100644 --- a/x-pack/test/functional/apps/spaces/copy_saved_objects.ts +++ b/x-pack/test/functional/apps/spaces/copy_saved_objects.ts @@ -17,7 +17,8 @@ export default function spaceSelectorFunctonalTests({ const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['security', 'settings', 'copySavedObjectsToSpace']); - describe('Copy Saved Objects to Space', function () { + // FLAKY: https://github.com/elastic/kibana/issues/44575 + describe.skip('Copy Saved Objects to Space', function () { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/spaces/copy_saved_objects'); diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index 0b9d1f420c6637..bab90a6d567fe7 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -22,8 +22,7 @@ export default function spaceSelectorFunctionalTests({ 'spaceSelector', ]); - // FLAKY: https://github.com/elastic/kibana/issues/99581 - describe.skip('Spaces', function () { + describe('Spaces', function () { this.tags('includeFirefox'); describe('Space Selector', () => { before(async () => { diff --git a/x-pack/test/functional/apps/uptime/synthetics_integration.ts b/x-pack/test/functional/apps/uptime/synthetics_integration.ts index 77a4e5c3c94c18..54b768bbd7c7cb 100644 --- a/x-pack/test/functional/apps/uptime/synthetics_integration.ts +++ b/x-pack/test/functional/apps/uptime/synthetics_integration.ts @@ -129,7 +129,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { use_output: 'default', }); - describe('When on the Synthetics Integration Policy Create Page', function () { + // Failing: See https://github.com/elastic/kibana/issues/116980 + describe.skip('When on the Synthetics Integration Policy Create Page', function () { this.tags(['ciGroup10']); const basicConfig = { name: monitorName, diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts index 7bfae9ba36be42..d089ab47c0cf71 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts @@ -214,7 +214,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Visualize Library']); + expect(navLinks).to.eql(['Visualize Library']); }); it(`landing page shows "Create new Visualization" button`, async () => { @@ -329,7 +329,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Visualize Library']); + expect(navLinks).to.eql(['Visualize Library']); }); it(`landing page shows "Create new Visualization" button`, async () => { diff --git a/x-pack/test/functional/es_archives/lens/basic/data.json b/x-pack/test/functional/es_archives/lens/basic/data.json deleted file mode 100644 index a985de882929d4..00000000000000 --- a/x-pack/test/functional/es_archives/lens/basic/data.json +++ /dev/null @@ -1,577 +0,0 @@ -{ - "type": "doc", - "value": { - "id": "space:default", - "index": ".kibana_1", - "source": { - "migrationVersion": { - "space": "6.6.0" - }, - "references": [], - "space": { - "_reserved": true, - "description": "This is the default space!", - "disabledFeatures": [], - "name": "Default" - }, - "type": "space" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "index-pattern:logstash-*", - "index": ".kibana_1", - "source": { - "index-pattern": { - "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\":\"type\",\"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}]", - "timeFieldName": "@timestamp", - "title": "logstash-*" - }, - "migrationVersion": { - "index-pattern": "6.5.0" - }, - "references": [], - "type": "index-pattern", - "updated_at": "2018-12-21T00:43:07.096Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "index-pattern:log*", - "index": ".kibana_1", - "source": { - "index-pattern": { - "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\":\"type\",\"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}]", - "timeFieldName": "@timestamp", - "title": "log*" - }, - "migrationVersion": { - "index-pattern": "6.5.0" - }, - "references": [], - "type": "index-pattern", - "updated_at": "2018-12-21T00:43:07.096Z" - }, - "type": "_doc" - } -} - - -{ - "type": "doc", - "value": { - "id": "custom_space:index-pattern:logstash-*", - "index": ".kibana_1", - "source": { - "index-pattern": { - "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\":\"type\",\"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}]", - "timeFieldName": "@timestamp", - "title": "logstash-*" - }, - "migrationVersion": { - "index-pattern": "6.5.0" - }, - "namespace": "custom_space", - "references": [], - "type": "index-pattern", - "updated_at": "2018-12-21T00:43:07.096Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "visualization:i-exist", - "index": ".kibana_1", - "source": { - "migrationVersion": { - "visualization": "7.3.1" - }, - "references": [ - { - "id": "logstash-*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern" - } - ], - "type": "visualization", - "updated_at": "2019-01-22T19:32:31.206Z", - "visualization": { - "description": "", - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" - }, - "title": "A Pie", - "uiStateJSON": "{}", - "version": 1, - "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}" - } - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "custom_space:visualization:i-exist", - "index": ".kibana_1", - "source": { - "migrationVersion": { - "visualization": "7.3.1" - }, - "namespace": "custom_space", - "references": [ - { - "id": "logstash-*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern" - } - ], - "type": "visualization", - "updated_at": "2019-01-22T19:32:31.206Z", - "visualization": { - "description": "", - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" - }, - "title": "A Pie", - "uiStateJSON": "{}", - "version": 1, - "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}" - } - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "query:okjpgs", - "index": ".kibana_1", - "source": { - "query": { - "description": "Ok responses for jpg files", - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "index": "b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b", - "key": "extension.raw", - "negate": false, - "params": { - "query": "jpg" - }, - "type": "phrase", - "value": "jpg" - }, - "query": { - "match": { - "extension.raw": { - "query": "jpg", - "type": "phrase" - } - } - } - } - ], - "query": { - "language": "kuery", - "query": "response:200" - }, - "title": "OKJpgs" - }, - "references": [], - "type": "query", - "updated_at": "2019-07-17T17:54:26.378Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "config:8.0.0", - "index": ".kibana_1", - "source": { - "config": { - "accessibility:disableAnimations": true, - "buildNum": 9007199254740991, - "dateFormat:tz": "UTC", - "defaultIndex": "logstash-*" - }, - "references": [], - "type": "config", - "updated_at": "2019-09-04T18:47:24.761Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "lens:76fc4200-cf44-11e9-b933-fd84270f3ac1", - "index": ".kibana_1", - "source": { - "lens": { - "expression": "kibana\n| kibana_context query=\"{\\\"language\\\":\\\"kuery\\\",\\\"query\\\":\\\"\\\"}\" filters=\"[]\"\n| lens_merge_tables layerIds=\"c61a8afb-a185-4fae-a064-fb3846f6c451\" \n tables={esaggs index=\"logstash-*\" metricsAtAllLevels=false partialRows=false includeFormatHints=true aggConfigs={lens_auto_date aggConfigs=\"[{\\\"id\\\":\\\"2cd09808-3915-49f4-b3b0-82767eba23f7\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"max\\\",\\\"schema\\\":\\\"metric\\\",\\\"params\\\":{\\\"field\\\":\\\"bytes\\\"}}]\"} | lens_rename_columns idMap=\"{\\\"col-0-2cd09808-3915-49f4-b3b0-82767eba23f7\\\":{\\\"dataType\\\":\\\"number\\\",\\\"isBucketed\\\":false,\\\"label\\\":\\\"Maximum of bytes\\\",\\\"operationType\\\":\\\"max\\\",\\\"scale\\\":\\\"ratio\\\",\\\"sourceField\\\":\\\"bytes\\\",\\\"id\\\":\\\"2cd09808-3915-49f4-b3b0-82767eba23f7\\\"}}\"}\n| lens_metric_chart title=\"Maximum of bytes\" accessor=\"2cd09808-3915-49f4-b3b0-82767eba23f7\" mode=\"full\"", - "state": { - "datasourceMetaData": { - "filterableIndexPatterns": [ - { - "id": "logstash-*", - "title": "logstash-*" - } - ] - }, - "datasourceStates": { - "indexpattern": { - "currentIndexPatternId": "logstash-*", - "layers": { - "c61a8afb-a185-4fae-a064-fb3846f6c451": { - "columnOrder": [ - "2cd09808-3915-49f4-b3b0-82767eba23f7" - ], - "columns": { - "2cd09808-3915-49f4-b3b0-82767eba23f7": { - "dataType": "number", - "isBucketed": false, - "label": "Maximum of bytes", - "operationType": "max", - "scale": "ratio", - "sourceField": "bytes" - } - }, - "indexPatternId": "logstash-*" - } - } - } - }, - "filters": [], - "query": { - "language": "kuery", - "query": "" - }, - "visualization": { - "accessor": "2cd09808-3915-49f4-b3b0-82767eba23f7", - "isHorizontal": false, - "layerId": "c61a8afb-a185-4fae-a064-fb3846f6c451", - "layers": [ - { - "accessors": [ - "d3e62a7a-c259-4fff-a2fc-eebf20b7008a", - "26ef70a9-c837-444c-886e-6bd905ee7335" - ], - "layerId": "c61a8afb-a185-4fae-a064-fb3846f6c451", - "seriesType": "area", - "splitAccessor": "54cd64ed-2a44-4591-af84-b2624504569a", - "xAccessor": "d6e40cea-6299-43b4-9c9d-b4ee305a2ce8" - } - ], - "legend": { - "isVisible": true, - "position": "right" - }, - "preferredSeriesType": "area" - } - }, - "title": "Artistpreviouslyknownaslens", - "visualizationType": "lnsMetric" - }, - "references": [], - "type": "lens", - "updated_at": "2019-10-16T00:28:08.979Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "lens:9536bed0-d57e-11ea-b169-e3a222a76b9c", - "index": ".kibana_1", - "source": { - "lens": { - "expression": "kibana\n| kibana_context query=\"{\\\"language\\\":\\\"kuery\\\",\\\"query\\\":\\\"\\\"}\" filters=\"[]\"\n| lens_merge_tables layerIds=\"4ba1a1be-6e67-434b-b3a0-f30db8ea5395\" \n tables={esaggs index=\"logstash-*\" metricsAtAllLevels=true partialRows=true includeFormatHints=true aggConfigs=\"[{\\\"id\\\":\\\"bafe3009-1776-4227-a0fe-b0d6ccbb4961\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"terms\\\",\\\"schema\\\":\\\"segment\\\",\\\"params\\\":{\\\"field\\\":\\\"geo.dest\\\",\\\"orderBy\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\",\\\"order\\\":\\\"desc\\\",\\\"size\\\":7,\\\"otherBucket\\\":false,\\\"otherBucketLabel\\\":\\\"Other\\\",\\\"missingBucket\\\":false,\\\"missingBucketLabel\\\":\\\"Missing\\\"}},{\\\"id\\\":\\\"c1ebe4c9-f283-486c-ae95-6b3e99e83bd8\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"terms\\\",\\\"schema\\\":\\\"segment\\\",\\\"params\\\":{\\\"field\\\":\\\"geo.src\\\",\\\"orderBy\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\",\\\"order\\\":\\\"desc\\\",\\\"size\\\":3,\\\"otherBucket\\\":false,\\\"otherBucketLabel\\\":\\\"Other\\\",\\\"missingBucket\\\":false,\\\"missingBucketLabel\\\":\\\"Missing\\\"}},{\\\"id\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"avg\\\",\\\"schema\\\":\\\"metric\\\",\\\"params\\\":{\\\"field\\\":\\\"bytes\\\",\\\"missing\\\":0}}]\" | lens_rename_columns idMap=\"{\\\"col-0-bafe3009-1776-4227-a0fe-b0d6ccbb4961\\\":{\\\"dataType\\\":\\\"string\\\",\\\"isBucketed\\\":true,\\\"label\\\":\\\"Top values of geo.dest\\\",\\\"operationType\\\":\\\"terms\\\",\\\"params\\\":{\\\"orderBy\\\":{\\\"columnId\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\",\\\"type\\\":\\\"column\\\"},\\\"orderDirection\\\":\\\"desc\\\",\\\"size\\\":7},\\\"scale\\\":\\\"ordinal\\\",\\\"sourceField\\\":\\\"geo.dest\\\",\\\"id\\\":\\\"bafe3009-1776-4227-a0fe-b0d6ccbb4961\\\"},\\\"col-2-c1ebe4c9-f283-486c-ae95-6b3e99e83bd8\\\":{\\\"label\\\":\\\"Top values of geo.src\\\",\\\"dataType\\\":\\\"string\\\",\\\"operationType\\\":\\\"terms\\\",\\\"scale\\\":\\\"ordinal\\\",\\\"sourceField\\\":\\\"geo.src\\\",\\\"isBucketed\\\":true,\\\"params\\\":{\\\"size\\\":3,\\\"orderBy\\\":{\\\"type\\\":\\\"column\\\",\\\"columnId\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\"},\\\"orderDirection\\\":\\\"desc\\\"},\\\"id\\\":\\\"c1ebe4c9-f283-486c-ae95-6b3e99e83bd8\\\"},\\\"col-3-3dc0bd55-2087-4e60-aea2-f9910714f7db\\\":{\\\"dataType\\\":\\\"number\\\",\\\"isBucketed\\\":false,\\\"label\\\":\\\"Average of bytes\\\",\\\"operationType\\\":\\\"avg\\\",\\\"scale\\\":\\\"ratio\\\",\\\"sourceField\\\":\\\"bytes\\\",\\\"id\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\"}}\"}\n| lens_pie shape=\"pie\" hideLabels=false groups=\"bafe3009-1776-4227-a0fe-b0d6ccbb4961\"\n groups=\"c1ebe4c9-f283-486c-ae95-6b3e99e83bd8\" metric=\"3dc0bd55-2087-4e60-aea2-f9910714f7db\" numberDisplay=\"percent\" categoryDisplay=\"default\" legendDisplay=\"default\" legendPosition=\"right\" percentDecimals=3 nestedLegend=false", - "state": { - "datasourceMetaData": { - "filterableIndexPatterns": [ - { - "id": "logstash-*", - "title": "logstash-*" - } - ] - }, - "datasourceStates": { - "indexpattern": { - "currentIndexPatternId": "logstash-*", - "layers": { - "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { - "columnOrder": [ - "bafe3009-1776-4227-a0fe-b0d6ccbb4961", - "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8", - "3dc0bd55-2087-4e60-aea2-f9910714f7db" - ], - "columns": { - "3dc0bd55-2087-4e60-aea2-f9910714f7db": { - "dataType": "number", - "isBucketed": false, - "label": "Average of bytes", - "operationType": "avg", - "scale": "ratio", - "sourceField": "bytes" - }, - "5bd1c078-e1dd-465b-8d25-7a6404befa88": { - "dataType": "date", - "isBucketed": true, - "label": "@timestamp", - "operationType": "date_histogram", - "params": { - "interval": "auto" - }, - "scale": "interval", - "sourceField": "@timestamp" - }, - "65340cf3-8402-4494-96f2-293701c59571": { - "dataType": "number", - "isBucketed": true, - "label": "Top values of bytes", - "operationType": "terms", - "params": { - "orderBy": { - "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", - "type": "column" - }, - "orderDirection": "desc", - "size": 3 - }, - "scale": "ordinal", - "sourceField": "bytes" - }, - "87554e1d-3dbf-4c1c-a358-4c9d40424cfa": { - "dataType": "string", - "isBucketed": true, - "label": "Top values of type", - "operationType": "terms", - "params": { - "orderBy": { - "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", - "type": "column" - }, - "orderDirection": "desc", - "size": 3 - }, - "scale": "ordinal", - "sourceField": "type" - }, - "bafe3009-1776-4227-a0fe-b0d6ccbb4961": { - "dataType": "string", - "isBucketed": true, - "label": "Top values of geo.dest", - "operationType": "terms", - "params": { - "orderBy": { - "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", - "type": "column" - }, - "orderDirection": "desc", - "size": 7 - }, - "scale": "ordinal", - "sourceField": "geo.dest" - }, - "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8": { - "dataType": "string", - "isBucketed": true, - "label": "Top values of geo.src", - "operationType": "terms", - "params": { - "orderBy": { - "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", - "type": "column" - }, - "orderDirection": "desc", - "size": 3 - }, - "scale": "ordinal", - "sourceField": "geo.src" - } - }, - "indexPatternId": "logstash-*" - } - } - } - }, - "filters": [], - "query": { - "language": "kuery", - "query": "" - }, - "visualization": { - "layers": [ - { - "categoryDisplay": "default", - "groups": [ - "bafe3009-1776-4227-a0fe-b0d6ccbb4961", - "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8" - ], - "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", - "legendDisplay": "default", - "metric": "3dc0bd55-2087-4e60-aea2-f9910714f7db", - "nestedLegend": false, - "numberDisplay": "percent" - } - ], - "shape": "pie" - } - }, - "title": "lnsPieVis", - "visualizationType": "lnsPie" - }, - "references": [], - "type": "lens", - "updated_at": "2020-08-03T11:43:43.421Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "lens:76fc4200-cf44-11e9-b933-fd84270f3ac2", - "index": ".kibana_1", - "source": { - "lens": { - "expression": "kibana\n| kibana_context query=\"{\\\"query\\\":\\\"\\\",\\\"language\\\":\\\"kuery\\\"}\" filters=\"[]\"\n| lens_merge_tables layerIds=\"4ba1a1be-6e67-434b-b3a0-f30db8ea5395\" \n tables={esaggs index=\"logstash-*\" metricsAtAllLevels=false partialRows=false includeFormatHints=true aggConfigs={lens_auto_date aggConfigs=\"[{\\\"id\\\":\\\"7a5d833b-ca6f-4e48-a924-d2a28d365dc3\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"terms\\\",\\\"schema\\\":\\\"segment\\\",\\\"params\\\":{\\\"field\\\":\\\"ip\\\",\\\"orderBy\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\",\\\"order\\\":\\\"desc\\\",\\\"size\\\":3,\\\"otherBucket\\\":false,\\\"otherBucketLabel\\\":\\\"Other\\\",\\\"missingBucket\\\":false,\\\"missingBucketLabel\\\":\\\"Missing\\\"}},{\\\"id\\\":\\\"3cf18f28-3495-4d45-a55f-d97f88022099\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"date_histogram\\\",\\\"schema\\\":\\\"segment\\\",\\\"params\\\":{\\\"field\\\":\\\"@timestamp\\\",\\\"useNormalizedEsInterval\\\":true,\\\"interval\\\":\\\"auto\\\",\\\"drop_partials\\\":false,\\\"min_doc_count\\\":0,\\\"extended_bounds\\\":{}}},{\\\"id\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"avg\\\",\\\"schema\\\":\\\"metric\\\",\\\"params\\\":{\\\"field\\\":\\\"bytes\\\",\\\"missing\\\":0}}]\"} | lens_rename_columns idMap=\"{\\\"col-0-7a5d833b-ca6f-4e48-a924-d2a28d365dc3\\\":{\\\"label\\\":\\\"Top values of ip\\\",\\\"dataType\\\":\\\"ip\\\",\\\"operationType\\\":\\\"terms\\\",\\\"scale\\\":\\\"ordinal\\\",\\\"suggestedPriority\\\":0,\\\"sourceField\\\":\\\"ip\\\",\\\"isBucketed\\\":true,\\\"params\\\":{\\\"size\\\":3,\\\"orderBy\\\":{\\\"type\\\":\\\"column\\\",\\\"columnId\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\"},\\\"orderDirection\\\":\\\"desc\\\"},\\\"id\\\":\\\"7a5d833b-ca6f-4e48-a924-d2a28d365dc3\\\"},\\\"col-1-3cf18f28-3495-4d45-a55f-d97f88022099\\\":{\\\"label\\\":\\\"@timestamp\\\",\\\"dataType\\\":\\\"date\\\",\\\"operationType\\\":\\\"date_histogram\\\",\\\"suggestedPriority\\\":1,\\\"sourceField\\\":\\\"@timestamp\\\",\\\"isBucketed\\\":true,\\\"scale\\\":\\\"interval\\\",\\\"params\\\":{\\\"interval\\\":\\\"auto\\\"},\\\"id\\\":\\\"3cf18f28-3495-4d45-a55f-d97f88022099\\\"},\\\"col-2-3dc0bd55-2087-4e60-aea2-f9910714f7db\\\":{\\\"label\\\":\\\"Average of bytes\\\",\\\"dataType\\\":\\\"number\\\",\\\"operationType\\\":\\\"avg\\\",\\\"sourceField\\\":\\\"bytes\\\",\\\"isBucketed\\\":false,\\\"scale\\\":\\\"ratio\\\",\\\"id\\\":\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\"}}\"}\n| lens_xy_chart xTitle=\"@timestamp\" yTitle=\"Average of bytes\" legend={lens_xy_legendConfig isVisible=true position=\"right\"} \n layers={lens_xy_layer layerId=\"4ba1a1be-6e67-434b-b3a0-f30db8ea5395\" hide=false xAccessor=\"3cf18f28-3495-4d45-a55f-d97f88022099\" yScaleType=\"linear\" xScaleType=\"time\" isHistogram=true splitAccessor=\"7a5d833b-ca6f-4e48-a924-d2a28d365dc3\" seriesType=\"bar_stacked\" accessors=\"3dc0bd55-2087-4e60-aea2-f9910714f7db\" columnToLabel=\"{\\\"3dc0bd55-2087-4e60-aea2-f9910714f7db\\\":\\\"Average of bytes\\\",\\\"7a5d833b-ca6f-4e48-a924-d2a28d365dc3\\\":\\\"Top values of ip\\\"}\"}", - "state": { - "datasourceMetaData": { - "filterableIndexPatterns": [ - { - "id": "logstash-*", - "title": "logstash-*" - } - ] - }, - "datasourceStates": { - "indexpattern": { - "currentIndexPatternId": "logstash-*", - "layers": { - "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { - "columnOrder": [ - "7a5d833b-ca6f-4e48-a924-d2a28d365dc3", - "3cf18f28-3495-4d45-a55f-d97f88022099", - "3dc0bd55-2087-4e60-aea2-f9910714f7db" - ], - "columns": { - "3cf18f28-3495-4d45-a55f-d97f88022099": { - "dataType": "date", - "isBucketed": true, - "label": "@timestamp", - "operationType": "date_histogram", - "params": { - "interval": "auto" - }, - "scale": "interval", - "sourceField": "@timestamp", - "suggestedPriority": 1 - }, - "3dc0bd55-2087-4e60-aea2-f9910714f7db": { - "dataType": "number", - "isBucketed": false, - "label": "Average of bytes", - "operationType": "avg", - "scale": "ratio", - "sourceField": "bytes" - }, - "7a5d833b-ca6f-4e48-a924-d2a28d365dc3": { - "dataType": "ip", - "isBucketed": true, - "label": "Top values of ip", - "operationType": "terms", - "params": { - "orderBy": { - "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", - "type": "column" - }, - "orderDirection": "desc", - "size": 3 - }, - "scale": "ordinal", - "sourceField": "ip", - "suggestedPriority": 0 - } - }, - "indexPatternId": "logstash-*" - } - } - } - }, - "filters": [], - "query": { - "language": "kuery", - "query": "" - }, - "visualization": { - "layers": [ - { - "accessors": [ - "3dc0bd55-2087-4e60-aea2-f9910714f7db" - ], - "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", - "seriesType": "bar_stacked", - "splitAccessor": "7a5d833b-ca6f-4e48-a924-d2a28d365dc3", - "xAccessor": "3cf18f28-3495-4d45-a55f-d97f88022099" - } - ], - "legend": { - "isVisible": true, - "position": "right" - }, - "preferredSeriesType": "bar_stacked" - } - }, - "title": "lnsXYvis", - "visualizationType": "lnsXY" - }, - "references": [], - "type": "lens", - "updated_at": "2019-10-16T00:28:08.979Z" - }, - "type": "_doc" - } -} - -{ - "type": "doc", - "value": { - "id": "ui-metric:DashboardPanelVersionInUrl:8.0.0", - "index": ".kibana_1", - "source": { - "type": "ui-metric", - "ui-metric": { - "count": 1 - }, - "updated_at": "2019-10-16T00:28:24.399Z" - }, - "type": "_doc" - } -} diff --git a/x-pack/test/functional/es_archives/lens/basic/mappings.json b/x-pack/test/functional/es_archives/lens/basic/mappings.json deleted file mode 100644 index 5ff0a0e4661c36..00000000000000 --- a/x-pack/test/functional/es_archives/lens/basic/mappings.json +++ /dev/null @@ -1,1252 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "apm-telemetry": "07ee1939fa4302c62ddc052ec03fed90", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "config": "87aca8fdb053154f11383fce3dbf3edf", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", - "inventory-view": "84b320fd67209906333ffce261128462", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "21c3ea0763beb1ecb0162529706b88c5", - "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", - "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "server": "ec97f1c5da1a19609a60874e5af1100c", - "siem-ui-timeline": "1f6f0860ad7bc0dba3e42467ca40470d", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", - "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "description": { - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "interval": { - "type": "keyword" - }, - "scheduledTaskId": { - "type": "keyword" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "properties": { - "has_any_services": { - "type": "boolean" - }, - "services_per_agent": { - "properties": { - "dotnet": { - "null_value": 0, - "type": "long" - }, - "go": { - "null_value": 0, - "type": "long" - }, - "java": { - "null_value": 0, - "type": "long" - }, - "js-base": { - "null_value": 0, - "type": "long" - }, - "nodejs": { - "null_value": 0, - "type": "long" - }, - "python": { - "null_value": 0, - "type": "long" - }, - "ruby": { - "null_value": 0, - "type": "long" - }, - "rum-js": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "accessibility:disableAnimations": { - "type": "boolean" - }, - "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": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "gis-map": { - "properties": { - "bounds": { - "dynamic": false, - "properties": {} - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "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" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "properties": { - "description": { - "type": "text" - }, - "fields": { - "properties": { - "container": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "pod": { - "type": "keyword" - }, - "tiebreaker": { - "type": "keyword" - }, - "timestamp": { - "type": "keyword" - } - } - }, - "logAlias": { - "type": "keyword" - }, - "logColumns": { - "properties": { - "fieldColumn": { - "properties": { - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - } - } - }, - "messageColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "timestampColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - } - }, - "type": "nested" - }, - "metricAlias": { - "type": "keyword" - }, - "name": { - "type": "text" - } - } - }, - "inventory-view": { - "properties": { - "autoBounds": { - "type": "boolean" - }, - "autoReload": { - "type": "boolean" - }, - "boundsOverride": { - "properties": { - "max": { - "type": "integer" - }, - "min": { - "type": "integer" - } - } - }, - "customOptions": { - "properties": { - "field": { - "type": "keyword" - }, - "text": { - "type": "keyword" - } - }, - "type": "nested" - }, - "filterQuery": { - "properties": { - "expression": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - } - } - }, - "groupBy": { - "properties": { - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - }, - "metric": { - "properties": { - "type": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "nodeType": { - "type": "keyword" - }, - "time": { - "type": "integer" - }, - "view": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "bounds": { - "dynamic": false, - "properties": {} - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "mapsTotalCount": { - "type": "long" - }, - "timeCaptured": { - "type": "date" - } - } - }, - "metrics-explorer-view": { - "properties": { - "chartOptions": { - "properties": { - "stack": { - "type": "boolean" - }, - "type": { - "type": "keyword" - }, - "yAxisMode": { - "type": "keyword" - } - } - }, - "currentTimerange": { - "properties": { - "from": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "to": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "options": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "filterQuery": { - "type": "keyword" - }, - "groupBy": { - "type": "keyword" - }, - "limit": { - "type": "integer" - }, - "metrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "color": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - } - } - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "index-pattern": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "space": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "visualization": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "sort": { - "properties": { - "columnId": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "imageUrl": { - "index": false, - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaceId": { - "type": "keyword" - }, - "telemetry": { - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "dynamic": "true", - "properties": { - "indexName": { - "type": "keyword" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "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" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/data.json.gz b/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/data.json.gz deleted file mode 100644 index c434eee5dd8d35..00000000000000 Binary files a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json b/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json deleted file mode 100644 index e67abaf2032c75..00000000000000 --- a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json +++ /dev/null @@ -1,2185 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "6e96ac5e648f57523879661ea72525b7", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "alert": "7b44fba6773e37c806ce290ea9b7024e", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", - "application_usage_transactional": "43b8830d5d0df85a6823d290885fc9fd", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", - "cases": "32aa96a6d3855ddda53010ae2048ac22", - "cases-comments": "c2061fb929f585df57425102fa928b4b", - "cases-configure": "42711cbb311976c0687853f4c1354572", - "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "c63748b75f39d0c54de12d12c1ccbc20", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", - "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862", - "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", - "exception-list": "4818e7dfc3e538562c80ec34eb6f841b", - "exception-list-agnostic": "4818e7dfc3e538562c80ec34eb6f841b", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", - "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", - "fleet-agents": "034346488514b7058a79140b19ddf631", - "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", - "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", - "ingest-package-policies": "48e8bd97e488008e21c0b5a2367b83ad", - "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "d33c68a69ff1e78c9888dedd2164ac22", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "4a05b35c3a3a58fbc72dd0202dc3487f", - "maps-telemetry": "5ef305b18111b77789afefbd36b66171", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "namespaces": "2f4316de49999235636386fe51dc06c1", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "5c4b9a6effceb17ae8a0ab22d0c49767", - "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", - "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", - "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "telemetry": "36a616f7026dfa617d6655df850fe16d", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc", - "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "apm-indices": { - "properties": { - "error": { - "type": "keyword" - }, - "metric": { - "type": "keyword" - }, - "onboarding": { - "type": "keyword" - }, - "sourcemap": { - "type": "keyword" - }, - "span": { - "type": "keyword" - }, - "transaction": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "dynamic": "false", - "type": "object" - }, - "app_search_telemetry": { - "dynamic": "false", - "type": "object" - }, - "application_usage_totals": { - "dynamic": "false", - "type": "object" - }, - "application_usage_transactional": { - "dynamic": "false", - "properties": { - "timestamp": { - "type": "date" - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad-template": { - "dynamic": "false", - "properties": { - "help": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "tags": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "template_key": { - "type": "keyword" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "connector_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "false", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "endpoint:user-artifact": { - "properties": { - "body": { - "type": "binary" - }, - "compressionAlgorithm": { - "index": false, - "type": "keyword" - }, - "created": { - "index": false, - "type": "date" - }, - "decodedSha256": { - "index": false, - "type": "keyword" - }, - "decodedSize": { - "index": false, - "type": "long" - }, - "encodedSha256": { - "type": "keyword" - }, - "encodedSize": { - "index": false, - "type": "long" - }, - "encryptionAlgorithm": { - "index": false, - "type": "keyword" - }, - "identifier": { - "type": "keyword" - } - } - }, - "endpoint:user-artifact-manifest": { - "properties": { - "created": { - "index": false, - "type": "date" - }, - "schemaVersion": { - "type": "keyword" - }, - "semanticVersion": { - "index": false, - "type": "keyword" - }, - "artifacts": { - "type": "nested", - "properties": { - "policyId": { - "type": "keyword", - "index": false - }, - "artifactId": { - "type": "keyword", - "index": false - } - } - } - } - }, - "epm-packages": { - "properties": { - "es_index_patterns": { - "enabled": false, - "type": "object" - }, - "installed_es": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "installed_kibana": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "internal": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "removable": { - "type": "boolean" - }, - "version": { - "type": "keyword" - } - } - }, - "exception-list": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "exception-list-agnostic": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "fleet-agent-actions": { - "properties": { - "agent_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "data": { - "type": "binary" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agent-events": { - "properties": { - "action_id": { - "type": "keyword" - }, - "agent_id": { - "type": "keyword" - }, - "policy_id": { - "type": "keyword" - }, - "data": { - "type": "text" - }, - "message": { - "type": "text" - }, - "payload": { - "type": "text" - }, - "stream_id": { - "type": "keyword" - }, - "subtype": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agents": { - "properties": { - "access_api_key_id": { - "type": "keyword" - }, - "active": { - "type": "boolean" - }, - "policy_id": { - "type": "keyword" - }, - "policy_revision": { - "type": "integer" - }, - "current_error_events": { - "index": false, - "type": "text" - }, - "default_api_key": { - "type": "binary" - }, - "default_api_key_id": { - "type": "keyword" - }, - "enrolled_at": { - "type": "date" - }, - "last_checkin": { - "type": "date" - }, - "last_checkin_status": { - "type": "keyword" - }, - "last_updated": { - "type": "date" - }, - "local_metadata": { - "type": "flattened" - }, - "packages": { - "type": "keyword" - }, - "shared_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "unenrolled_at": { - "type": "date" - }, - "unenrollment_started_at": { - "type": "date" - }, - "updated_at": { - "type": "date" - }, - "user_provided_metadata": { - "type": "flattened" - }, - "version": { - "type": "keyword" - } - } - }, - "fleet-enrollment-api-keys": { - "properties": { - "active": { - "type": "boolean" - }, - "api_key": { - "type": "binary" - }, - "api_key_id": { - "type": "keyword" - }, - "policy_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "expire_at": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "ingest-agent-policies": { - "properties": { - "description": { - "type": "text" - }, - "is_default": { - "type": "boolean" - }, - "monitoring_enabled": { - "index": false, - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "package_policies": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest-outputs": { - "properties": { - "ca_sha256": { - "index": false, - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "ingest-package-policies": { - "properties": { - "policy_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "enabled": { - "type": "boolean" - }, - "inputs": { - "enabled": false, - "properties": { - "config": { - "type": "flattened" - }, - "enabled": { - "type": "boolean" - }, - "streams": { - "properties": { - "compiled_stream": { - "type": "flattened" - }, - "config": { - "type": "flattened" - }, - "data_stream": { - "properties": { - "dataset": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "enabled": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "type": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "output_id": { - "type": "keyword" - }, - "package": { - "properties": { - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "revision": { - "type": "integer" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest_manager_settings": { - "properties": { - "agent_auto_upgrade": { - "type": "keyword" - }, - "has_seen_add_data_notice": { - "index": false, - "type": "boolean" - }, - "kibana_ca_sha256": { - "type": "keyword" - }, - "kibana_url": { - "type": "keyword" - }, - "package_auto_upgrade": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "description": { - "type": "text" - }, - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "enabled": false, - "type": "object" - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "canvas-workpad": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "config": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "namespaces": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "index": false, - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "index": false, - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "sort": { - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "search-telemetry": { - "dynamic": "false", - "type": "object" - }, - "siem-detection-engine-rule-actions": { - "properties": { - "actions": { - "properties": { - "action_type_id": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alertThrottle": { - "type": "keyword" - }, - "ruleAlertId": { - "type": "keyword" - }, - "ruleThrottle": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-status": { - "properties": { - "alertId": { - "type": "keyword" - }, - "bulkCreateTimeDurations": { - "type": "float" - }, - "gap": { - "type": "text" - }, - "lastFailureAt": { - "type": "date" - }, - "lastFailureMessage": { - "type": "text" - }, - "lastLookBackDate": { - "type": "date" - }, - "lastSuccessAt": { - "type": "date" - }, - "lastSuccessMessage": { - "type": "text" - }, - "searchAfterTimeDurations": { - "type": "float" - }, - "status": { - "type": "keyword" - }, - "statusDate": { - "type": "date" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "eventType": { - "type": "keyword" - }, - "excludedRowRendererIds": { - "type": "text" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "filters": { - "properties": { - "exists": { - "type": "text" - }, - "match_all": { - "type": "text" - }, - "meta": { - "properties": { - "alias": { - "type": "text" - }, - "controlledBy": { - "type": "text" - }, - "disabled": { - "type": "boolean" - }, - "field": { - "type": "text" - }, - "formattedValue": { - "type": "text" - }, - "index": { - "type": "keyword" - }, - "key": { - "type": "keyword" - }, - "negate": { - "type": "boolean" - }, - "params": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "value": { - "type": "text" - } - } - }, - "missing": { - "type": "text" - }, - "query": { - "type": "text" - }, - "range": { - "type": "text" - }, - "script": { - "type": "text" - } - } - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "savedQueryId": { - "type": "keyword" - }, - "sort": { - "properties": { - "columnId": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - }, - "templateTimelineId": { - "type": "text" - }, - "templateTimelineVersion": { - "type": "integer" - }, - "timelineType": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "properties": { - "errorMessage": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "indexName": { - "type": "keyword" - }, - "lastCompletedStep": { - "type": "long" - }, - "locked": { - "type": "date" - }, - "newIndexName": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexOptions": { - "properties": { - "openAndClose": { - "type": "boolean" - }, - "queueSettings": { - "properties": { - "queuedAt": { - "type": "long" - }, - "startedAt": { - "type": "long" - } - } - } - } - }, - "reindexTaskId": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexTaskPercComplete": { - "type": "float" - }, - "runningReindexCount": { - "type": "integer" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "properties": { - "certAgeThreshold": { - "type": "long" - }, - "certExpirationThreshold": { - "type": "long" - }, - "heartbeatIndices": { - "type": "keyword" - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "workplace_search_telemetry": { - "dynamic": "false", - "type": "object" - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/data.json b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/data.json index 06543e44de56c2..2a9ac3d232f595 100644 --- a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/data.json +++ b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/data.json @@ -21,28 +21,6 @@ } } -{ - "type": "doc", - "value": { - "id": "index-pattern:aac3e500-f2c7-11ea-8250-fb138aa491e7", - "index": ".kibana_1", - "source": { - "index-pattern": { - "fields": "[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"category\"}}},{\"count\":0,\"name\":\"currency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"customer_birth_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"customer_first_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"customer_first_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"customer_first_name\"}}},{\"count\":1,\"name\":\"customer_full_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"customer_full_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"customer_full_name\"}}},{\"count\":0,\"name\":\"customer_gender\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"customer_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"customer_last_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"customer_last_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"customer_last_name\"}}},{\"count\":0,\"name\":\"customer_phone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"day_of_week\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"day_of_week_i\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"manufacturer\"}}},{\"count\":1,\"name\":\"order_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"order_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products._id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products._id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products._id\"}}},{\"count\":0,\"name\":\"products.base_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.base_unit_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products.category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products.category\"}}},{\"count\":0,\"name\":\"products.created_on\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.discount_percentage\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products.manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products.manufacturer\"}}},{\"count\":0,\"name\":\"products.min_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.product_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.product_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products.product_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products.product_name\"}}},{\"count\":0,\"name\":\"products.quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.tax_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.taxful_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.taxless_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.unit_discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"taxful_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"taxless_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"total_quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"total_unique_products\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "timeFieldName": "order_date", - "title": "ec*" - }, - "migrationVersion": { - "index-pattern": "7.6.0" - }, - "references": [ - ], - "type": "index-pattern", - "updated_at": "2020-09-09T18:10:54.007Z" - } - } -} - { "type": "doc", "value": { @@ -67,28 +45,6 @@ } } -{ - "type": "doc", - "value": { - "id": "config:8.0.0", - "index": ".kibana_1", - "source": { - "config": { - "buildNum": 9007199254740991, - "dateFormat:tz": "UTC", - "defaultIndex": "aac3e500-f2c7-11ea-8250-fb138aa491e7" - }, - "migrationVersion": { - "config": "7.13.0" - }, - "references": [ - ], - "type": "config", - "updated_at": "2020-08-26T22:46:48.711Z" - } - } -} - { "type": "doc", "value": { @@ -198,45 +154,6 @@ } } -{ - "type": "doc", - "value": { - "id": "search:bbe45ae0-f2c7-11ea-8250-fb138aa491e7", - "index": ".kibana_1", - "source": { - "migrationVersion": { - "search": "7.4.0" - }, - "references": [ - { - "id": "aac3e500-f2c7-11ea-8250-fb138aa491e7", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern" - } - ], - "search": { - "columns": [ - "category", - "customer_full_name", - "taxful_total_price", - "currency" - ], - "description": "", - "hits": 0, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" - }, - "sort": [ - ], - "title": "EC SEARCH from DEFAULT", - "version": 1 - }, - "type": "search", - "updated_at": "2020-09-09T18:10:58.011Z" - } - } -} - { "type": "doc", "value": { diff --git a/x-pack/test/functional/es_archives/reporting/hugedata/data.json.gz b/x-pack/test/functional/es_archives/reporting/hugedata/data.json.gz index e5fb8a73234e4f..c35837a0091332 100644 Binary files a/x-pack/test/functional/es_archives/reporting/hugedata/data.json.gz and b/x-pack/test/functional/es_archives/reporting/hugedata/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json b/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json deleted file mode 100644 index d1cb75c1f5150f..00000000000000 --- a/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json +++ /dev/null @@ -1,2523 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "executionStatus": { - "properties": { - "error": { - "properties": { - "message": { - "type": "keyword" - }, - "reason": { - "type": "keyword" - } - } - }, - "lastExecutionDate": { - "type": "date" - }, - "status": { - "type": "keyword" - } - } - }, - "meta": { - "properties": { - "versionApiKeyLastmodified": { - "type": "keyword" - } - } - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "notifyWhen": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedAt": { - "type": "date" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "api_key_pending_invalidation": { - "properties": { - "apiKeyId": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - } - } - }, - "apm-indices": { - "properties": { - "error": { - "type": "keyword" - }, - "metric": { - "type": "keyword" - }, - "onboarding": { - "type": "keyword" - }, - "sourcemap": { - "type": "keyword" - }, - "span": { - "type": "keyword" - }, - "transaction": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "dynamic": "false", - "type": "object" - }, - "app_search_telemetry": { - "dynamic": "false", - "type": "object" - }, - "application_usage_daily": { - "dynamic": "false", - "properties": { - "timestamp": { - "type": "date" - } - } - }, - "application_usage_totals": { - "dynamic": "false", - "type": "object" - }, - "application_usage_transactional": { - "dynamic": "false", - "type": "object" - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad-template": { - "dynamic": "false", - "properties": { - "help": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "tags": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "template_key": { - "type": "keyword" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "connector": { - "properties": { - "fields": { - "properties": { - "key": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "id": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "settings": { - "properties": { - "syncAlerts": { - "type": "boolean" - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "alertId": { - "type": "keyword" - }, - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "index": { - "type": "keyword" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector": { - "properties": { - "fields": { - "properties": { - "key": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "id": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-connector-mappings": { - "properties": { - "mappings": { - "properties": { - "action_type": { - "type": "keyword" - }, - "source": { - "type": "keyword" - }, - "target": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "false", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "core-usage-stats": { - "dynamic": "false", - "type": "object" - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "doc_values": false, - "index": false, - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "optionsJSON": { - "index": false, - "type": "text" - }, - "panelsJSON": { - "index": false, - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "pause": { - "doc_values": false, - "index": false, - "type": "boolean" - }, - "section": { - "doc_values": false, - "index": false, - "type": "integer" - }, - "value": { - "doc_values": false, - "index": false, - "type": "integer" - } - } - }, - "timeFrom": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "timeRestore": { - "doc_values": false, - "index": false, - "type": "boolean" - }, - "timeTo": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "endpoint:user-artifact": { - "properties": { - "body": { - "type": "binary" - }, - "compressionAlgorithm": { - "index": false, - "type": "keyword" - }, - "created": { - "index": false, - "type": "date" - }, - "decodedSha256": { - "index": false, - "type": "keyword" - }, - "decodedSize": { - "index": false, - "type": "long" - }, - "encodedSha256": { - "type": "keyword" - }, - "encodedSize": { - "index": false, - "type": "long" - }, - "encryptionAlgorithm": { - "index": false, - "type": "keyword" - }, - "identifier": { - "type": "keyword" - } - } - }, - "endpoint:user-artifact-manifest": { - "properties": { - "created": { - "index": false, - "type": "date" - }, - "ids": { - "index": false, - "type": "keyword" - }, - "schemaVersion": { - "type": "keyword" - }, - "semanticVersion": { - "index": false, - "type": "keyword" - } - } - }, - "enterprise_search_telemetry": { - "dynamic": "false", - "type": "object" - }, - "epm-packages": { - "properties": { - "es_index_patterns": { - "enabled": false, - "type": "object" - }, - "install_source": { - "type": "keyword" - }, - "install_started_at": { - "type": "date" - }, - "install_status": { - "type": "keyword" - }, - "install_version": { - "type": "keyword" - }, - "installed_es": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "installed_kibana": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "internal": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "package_assets": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "removable": { - "type": "boolean" - }, - "version": { - "type": "keyword" - } - } - }, - "epm-packages-assets": { - "properties": { - "asset_path": { - "type": "keyword" - }, - "data_base64": { - "type": "binary" - }, - "data_utf8": { - "index": false, - "type": "text" - }, - "install_source": { - "type": "keyword" - }, - "media_type": { - "type": "keyword" - }, - "package_name": { - "type": "keyword" - }, - "package_version": { - "type": "keyword" - } - } - }, - "exception-list": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "immutable": { - "type": "boolean" - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os_types": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "exception-list-agnostic": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "immutable": { - "type": "boolean" - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os_types": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "file-upload-usage-collection-telemetry": { - "properties": { - "file_upload": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "fleet-agent-actions": { - "properties": { - "ack_data": { - "type": "text" - }, - "agent_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "data": { - "type": "binary" - }, - "policy_id": { - "type": "keyword" - }, - "policy_revision": { - "type": "integer" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agent-events": { - "properties": { - "action_id": { - "type": "keyword" - }, - "agent_id": { - "type": "keyword" - }, - "data": { - "type": "text" - }, - "message": { - "type": "text" - }, - "payload": { - "type": "text" - }, - "policy_id": { - "type": "keyword" - }, - "stream_id": { - "type": "keyword" - }, - "subtype": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agents": { - "properties": { - "access_api_key_id": { - "type": "keyword" - }, - "active": { - "type": "boolean" - }, - "current_error_events": { - "index": false, - "type": "text" - }, - "default_api_key": { - "type": "binary" - }, - "default_api_key_id": { - "type": "keyword" - }, - "enrolled_at": { - "type": "date" - }, - "last_checkin": { - "type": "date" - }, - "last_checkin_status": { - "type": "keyword" - }, - "last_updated": { - "type": "date" - }, - "local_metadata": { - "type": "flattened" - }, - "packages": { - "type": "keyword" - }, - "policy_id": { - "type": "keyword" - }, - "policy_revision": { - "type": "integer" - }, - "type": { - "type": "keyword" - }, - "unenrolled_at": { - "type": "date" - }, - "unenrollment_started_at": { - "type": "date" - }, - "updated_at": { - "type": "date" - }, - "upgrade_started_at": { - "type": "date" - }, - "upgraded_at": { - "type": "date" - }, - "user_provided_metadata": { - "type": "flattened" - }, - "version": { - "type": "keyword" - } - } - }, - "fleet-enrollment-api-keys": { - "properties": { - "active": { - "type": "boolean" - }, - "api_key": { - "type": "binary" - }, - "api_key_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "expire_at": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "policy_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "legacyIndexPatternRef": { - "index": false, - "type": "text" - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "dynamic": "false", - "properties": { - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "dynamic": "false", - "type": "object" - }, - "ingest-agent-policies": { - "properties": { - "description": { - "type": "text" - }, - "is_default": { - "type": "boolean" - }, - "is_managed": { - "type": "boolean" - }, - "monitoring_enabled": { - "index": false, - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "package_policies": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest-outputs": { - "properties": { - "ca_sha256": { - "index": false, - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "config_yaml": { - "type": "text" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "ingest-package-policies": { - "properties": { - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "enabled": { - "type": "boolean" - }, - "inputs": { - "enabled": false, - "properties": { - "compiled_input": { - "type": "flattened" - }, - "config": { - "type": "flattened" - }, - "enabled": { - "type": "boolean" - }, - "streams": { - "properties": { - "compiled_stream": { - "type": "flattened" - }, - "config": { - "type": "flattened" - }, - "data_stream": { - "properties": { - "dataset": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "enabled": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "type": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "output_id": { - "type": "keyword" - }, - "package": { - "properties": { - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "policy_id": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest_manager_settings": { - "properties": { - "agent_auto_upgrade": { - "type": "keyword" - }, - "has_seen_add_data_notice": { - "index": false, - "type": "boolean" - }, - "kibana_ca_sha256": { - "type": "keyword" - }, - "kibana_urls": { - "type": "keyword" - }, - "package_auto_upgrade": { - "type": "keyword" - } - } - }, - "inventory-view": { - "dynamic": "false", - "type": "object" - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "legacy-url-alias": { - "dynamic": "false", - "type": "object" - }, - "lens": { - "properties": { - "description": { - "type": "text" - }, - "expression": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "bounds": { - "dynamic": "false", - "type": "object" - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "enabled": false, - "type": "object" - }, - "metrics-explorer-view": { - "dynamic": "false", - "type": "object" - }, - "ml-job": { - "properties": { - "datafeed_id": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "job_id": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "ml-telemetry": { - "dynamic": "false", - "type": "object" - }, - "monitoring-telemetry": { - "properties": { - "reportedClusterUuids": { - "type": "keyword" - } - } - }, - "namespace": { - "type": "keyword" - }, - "namespaces": { - "type": "keyword" - }, - "originId": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "description": { - "type": "text" - }, - "grid": { - "enabled": false, - "type": "object" - }, - "hits": { - "doc_values": false, - "index": false, - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "pre712": { - "type": "boolean" - }, - "sort": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "search-session": { - "properties": { - "appId": { - "type": "keyword" - }, - "created": { - "type": "date" - }, - "expires": { - "type": "date" - }, - "idMapping": { - "enabled": false, - "type": "object" - }, - "initialState": { - "enabled": false, - "type": "object" - }, - "name": { - "type": "keyword" - }, - "persisted": { - "type": "boolean" - }, - "restoreState": { - "enabled": false, - "type": "object" - }, - "sessionId": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "touched": { - "type": "date" - }, - "urlGeneratorId": { - "type": "keyword" - } - } - }, - "search-telemetry": { - "dynamic": "false", - "type": "object" - }, - "security-solution-signals-migration": { - "properties": { - "created": { - "index": false, - "type": "date" - }, - "createdBy": { - "index": false, - "type": "text" - }, - "destinationIndex": { - "index": false, - "type": "keyword" - }, - "error": { - "index": false, - "type": "text" - }, - "sourceIndex": { - "type": "keyword" - }, - "status": { - "index": false, - "type": "keyword" - }, - "taskId": { - "index": false, - "type": "keyword" - }, - "updated": { - "index": false, - "type": "date" - }, - "updatedBy": { - "index": false, - "type": "text" - }, - "version": { - "type": "long" - } - } - }, - "server": { - "dynamic": "false", - "type": "object" - }, - "siem-detection-engine-rule-actions": { - "properties": { - "actions": { - "properties": { - "action_type_id": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alertThrottle": { - "type": "keyword" - }, - "ruleAlertId": { - "type": "keyword" - }, - "ruleThrottle": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-status": { - "properties": { - "alertId": { - "type": "keyword" - }, - "bulkCreateTimeDurations": { - "type": "float" - }, - "gap": { - "type": "text" - }, - "lastFailureAt": { - "type": "date" - }, - "lastFailureMessage": { - "type": "text" - }, - "lastLookBackDate": { - "type": "date" - }, - "lastSuccessAt": { - "type": "date" - }, - "lastSuccessMessage": { - "type": "text" - }, - "searchAfterTimeDurations": { - "type": "float" - }, - "status": { - "type": "keyword" - }, - "statusDate": { - "type": "date" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "eventType": { - "type": "keyword" - }, - "excludedRowRendererIds": { - "type": "text" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "filters": { - "properties": { - "exists": { - "type": "text" - }, - "match_all": { - "type": "text" - }, - "meta": { - "properties": { - "alias": { - "type": "text" - }, - "controlledBy": { - "type": "text" - }, - "disabled": { - "type": "boolean" - }, - "field": { - "type": "text" - }, - "formattedValue": { - "type": "text" - }, - "index": { - "type": "keyword" - }, - "key": { - "type": "keyword" - }, - "negate": { - "type": "boolean" - }, - "params": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "value": { - "type": "text" - } - } - }, - "missing": { - "type": "text" - }, - "query": { - "type": "text" - }, - "range": { - "type": "text" - }, - "script": { - "type": "text" - } - } - }, - "indexNames": { - "type": "text" - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "savedQueryId": { - "type": "keyword" - }, - "sort": { - "dynamic": "false", - "properties": { - "columnId": { - "type": "keyword" - }, - "columnType": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - }, - "templateTimelineId": { - "type": "text" - }, - "templateTimelineVersion": { - "type": "integer" - }, - "timelineType": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "imageUrl": { - "index": false, - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaces-usage-stats": { - "dynamic": "false", - "type": "object" - }, - "tag": { - "properties": { - "color": { - "type": "text" - }, - "description": { - "type": "text" - }, - "name": { - "type": "text" - } - } - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-counter": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "properties": { - "errorMessage": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "indexName": { - "type": "keyword" - }, - "lastCompletedStep": { - "type": "long" - }, - "locked": { - "type": "date" - }, - "newIndexName": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexOptions": { - "properties": { - "openAndClose": { - "type": "boolean" - }, - "queueSettings": { - "properties": { - "queuedAt": { - "type": "long" - }, - "startedAt": { - "type": "long" - } - } - } - } - }, - "reindexTaskId": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexTaskPercComplete": { - "type": "float" - }, - "runningReindexCount": { - "type": "integer" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "dynamic": "false", - "type": "object" - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "user-action": { - "dynamic": "false", - "type": "object" - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "savedSearchRefName": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "index": false, - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "index": false, - "type": "text" - } - } - }, - "workplace_search_telemetry": { - "dynamic": "false", - "type": "object" - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - diff --git a/x-pack/test/functional/es_archives/reporting/logs/data.json.gz b/x-pack/test/functional/es_archives/reporting/logs/data.json.gz deleted file mode 100644 index dbd8f6f8e2e765..00000000000000 Binary files a/x-pack/test/functional/es_archives/reporting/logs/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/reporting/logs/mappings.json b/x-pack/test/functional/es_archives/reporting/logs/mappings.json deleted file mode 100644 index 2e1873e43ffcc6..00000000000000 --- a/x-pack/test/functional/es_archives/reporting/logs/mappings.json +++ /dev/null @@ -1,263 +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" - } - } - }, - "graph-workspace": { - "dynamic": "strict", - "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": { - "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" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaceId": { - "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/x-pack/test/functional/es_archives/reporting/multi_index/data.json.gz b/x-pack/test/functional/es_archives/reporting/multi_index/data.json.gz deleted file mode 100644 index bb0e05d632f54f..00000000000000 Binary files a/x-pack/test/functional/es_archives/reporting/multi_index/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/reporting/multi_index/mappings.json b/x-pack/test/functional/es_archives/reporting/multi_index/mappings.json deleted file mode 100644 index f28ffce8ce3ce0..00000000000000 --- a/x-pack/test/functional/es_archives/reporting/multi_index/mappings.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - }, - "index": "tests-001", - "mappings": { - "properties": { - "@date": { - "type": "date" - }, - "ants": { - "type": "integer" - }, - "country": { - "type": "keyword" - }, - "name": { - "type": "keyword" - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - }, - "index": "tests-002", - "mappings": { - "properties": { - "@date": { - "type": "date" - }, - "ants": { - "type": "integer" - }, - "country": { - "type": "keyword" - }, - "name": { - "type": "keyword" - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - }, - "index": "tests-003", - "mappings": { - "properties": { - "@date": { - "type": "date" - }, - "ants": { - "type": "integer" - }, - "country": { - "type": "keyword" - }, - "name": { - "type": "keyword" - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/data.json.gz b/x-pack/test/functional/es_archives/reporting/multi_index_kibana/data.json.gz deleted file mode 100644 index a6330916d62f77..00000000000000 Binary files a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json b/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json deleted file mode 100644 index 69c6cbc3b46b5f..00000000000000 --- a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json +++ /dev/null @@ -1,2027 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "6e96ac5e648f57523879661ea72525b7", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "alert": "7b44fba6773e37c806ce290ea9b7024e", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", - "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", - "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "cases": "32aa96a6d3855ddda53010ae2048ac22", - "cases-comments": "c2061fb929f585df57425102fa928b4b", - "cases-configure": "42711cbb311976c0687853f4c1354572", - "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "ae24d22d5986d04124cc6568f771066f", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "d33c68a69ff1e78c9888dedd2164ac22", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "4a05b35c3a3a58fbc72dd0202dc3487f", - "maps": "bfd39d88aadadb4be597ea984d433dbe", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "namespaces": "2f4316de49999235636386fe51dc06c1", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "telemetry": "36a616f7026dfa617d6655df850fe16d", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "296a89039fc4260292be36b1b005d8f2", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", - "url": "b675c3be8d76ecf029294d51dc7ec65d", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "apm-indices": { - "properties": { - "error": { - "type": "keyword" - }, - "metric": { - "type": "keyword" - }, - "onboarding": { - "type": "keyword" - }, - "sourcemap": { - "type": "keyword" - }, - "span": { - "type": "keyword" - }, - "transaction": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "properties": { - "agents": { - "properties": { - "dotnet": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "go": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "java": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "js-base": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "nodejs": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "python": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "ruby": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "rum-js": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - } - } - }, - "cardinality": { - "properties": { - "transaction": { - "properties": { - "name": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - }, - "user_agent": { - "properties": { - "original": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "counts": { - "properties": { - "agent_configuration": { - "properties": { - "all": { - "type": "long" - } - } - }, - "error": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "max_error_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "max_transaction_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "services": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "span": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "traces": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - } - } - }, - "has_any_services": { - "type": "boolean" - }, - "indices": { - "properties": { - "all": { - "properties": { - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - } - } - } - } - }, - "shards": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "ml": { - "properties": { - "all_jobs_count": { - "type": "long" - } - } - } - } - }, - "retainment": { - "properties": { - "error": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "span": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services_per_agent": { - "properties": { - "dotnet": { - "null_value": 0, - "type": "long" - }, - "go": { - "null_value": 0, - "type": "long" - }, - "java": { - "null_value": 0, - "type": "long" - }, - "js-base": { - "null_value": 0, - "type": "long" - }, - "nodejs": { - "null_value": 0, - "type": "long" - }, - "python": { - "null_value": 0, - "type": "long" - }, - "ruby": { - "null_value": 0, - "type": "long" - }, - "rum-js": { - "null_value": 0, - "type": "long" - } - } - }, - "tasks": { - "properties": { - "agent_configuration": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "agents": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "cardinality": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "groupings": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "indices_stats": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "processor_events": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "versions": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - } - } - }, - "version": { - "properties": { - "apm_server": { - "properties": { - "major": { - "type": "long" - }, - "minor": { - "type": "long" - }, - "patch": { - "type": "long" - } - } - } - } - } - } - }, - "application_usage_totals": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - } - } - }, - "application_usage_transactional": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - }, - "timestamp": { - "type": "date" - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "connector_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "description": { - "type": "text" - }, - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoPointFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoShapeFieldCount": { - "type": "long" - }, - "mapsTotalCount": { - "type": "long" - }, - "settings": { - "properties": { - "showMapVisualizationTypes": { - "type": "boolean" - } - } - }, - "timeCaptured": { - "type": "date" - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "config": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "index-pattern": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "namespaces": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "properties": { - "errorMessage": { - "type": "keyword" - }, - "indexName": { - "type": "keyword" - }, - "lastCompletedStep": { - "type": "integer" - }, - "locked": { - "type": "date" - }, - "newIndexName": { - "type": "keyword" - }, - "reindexOptions": { - "properties": { - "openAndClose": { - "type": "boolean" - }, - "queueSettings": { - "properties": { - "queuedAt": { - "type": "long" - }, - "startedAt": { - "type": "long" - } - } - } - } - }, - "reindexTaskId": { - "type": "keyword" - }, - "reindexTaskPercComplete": { - "type": "float" - }, - "runningReindexCount": { - "type": "integer" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "properties": { - "certAgeThreshold": { - "type": "long" - }, - "certExpirationThreshold": { - "type": "long" - }, - "heartbeatIndices": { - "type": "keyword" - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/es_archives/reporting/nanos/data.json b/x-pack/test/functional/es_archives/reporting/nanos/data.json new file mode 100644 index 00000000000000..02a56e95dd1f6f --- /dev/null +++ b/x-pack/test/functional/es_archives/reporting/nanos/data.json @@ -0,0 +1,25 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "nanos", + "source": { + "date": "2015-01-01T12:10:30", + "message": "Hello 1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "nanos", + "source": { + "date": "2015-01-01T12:10:30.123456789Z", + "message": "Hello 2" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/reporting/nanos/data.json.gz b/x-pack/test/functional/es_archives/reporting/nanos/data.json.gz deleted file mode 100644 index 2811c495aae2d2..00000000000000 Binary files a/x-pack/test/functional/es_archives/reporting/nanos/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/reporting/nanos/mappings.json b/x-pack/test/functional/es_archives/reporting/nanos/mappings.json index 216b89e4bfbcf1..3db5e17b7557f1 100644 --- a/x-pack/test/functional/es_archives/reporting/nanos/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/nanos/mappings.json @@ -1,1031 +1,3 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "415a6b78886a072bc79bbf1cef25a0b3", - "alert": "4f896c3659aa95c75b078a96a19f5bf2", - "apm-telemetry": "07ee1939fa4302c62ddc052ec03fed90", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "config": "87aca8fdb053154f11383fce3dbf3edf", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "server": "ec97f1c5da1a19609a60874e5af1100c", - "siem-ui-timeline": "1f6f0860ad7bc0dba3e42467ca40470d", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "25de8c2deec044392922989cfcf24c54", - "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "config": { - "enabled": false, - "type": "object" - }, - "secrets": { - "type": "binary" - }, - "actionTypeId": { - "type": "keyword" - }, - "description": { - "type": "text" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - }, - "interval": { - "type": "keyword" - }, - "scheduledTaskId": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "properties": { - "has_any_services": { - "type": "boolean" - }, - "services_per_agent": { - "properties": { - "dotnet": { - "null_value": 0, - "type": "long" - }, - "go": { - "null_value": 0, - "type": "long" - }, - "java": { - "null_value": 0, - "type": "long" - }, - "js-base": { - "null_value": 0, - "type": "long" - }, - "nodejs": { - "null_value": 0, - "type": "long" - }, - "python": { - "null_value": 0, - "type": "long" - }, - "ruby": { - "null_value": 0, - "type": "long" - }, - "rum-js": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "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" - }, - "search:queryLanguage": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "properties": { - "description": { - "type": "text" - }, - "fields": { - "properties": { - "container": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "pod": { - "type": "keyword" - }, - "tiebreaker": { - "type": "keyword" - }, - "timestamp": { - "type": "keyword" - } - } - }, - "logAlias": { - "type": "keyword" - }, - "logColumns": { - "properties": { - "fieldColumn": { - "properties": { - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - } - } - }, - "messageColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "timestampColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - } - }, - "type": "nested" - }, - "metricAlias": { - "type": "keyword" - }, - "name": { - "type": "text" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "map": { - "properties": { - "bounds": { - "dynamic": false, - "properties": {} - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "mapsTotalCount": { - "type": "long" - }, - "timeCaptured": { - "type": "date" - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "dashboard": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "index-pattern": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "search": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "space": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "sort": { - "properties": { - "columnId": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "telemetry": { - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "dynamic": "true", - "properties": { - "indexName": { - "type": "keyword" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "user-action": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - { "type": "index", "value": { diff --git a/x-pack/test/functional/es_archives/reporting/sales/data.json.gz b/x-pack/test/functional/es_archives/reporting/sales/data.json.gz index 9478d482abe164..4d517a0c2597c3 100644 Binary files a/x-pack/test/functional/es_archives/reporting/sales/data.json.gz and b/x-pack/test/functional/es_archives/reporting/sales/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/reporting/sales/mappings.json b/x-pack/test/functional/es_archives/reporting/sales/mappings.json index 317b185046ce13..498aef34028fb0 100644 --- a/x-pack/test/functional/es_archives/reporting/sales/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/sales/mappings.json @@ -1,267 +1,3 @@ -{ - "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" - } - } - }, - "graph-workspace": { - "dynamic": "strict", - "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": { - "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" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaceId": { - "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" - } - } - } -} - { "type": "index", "value": { diff --git a/x-pack/test/functional/es_archives/signals/README.md b/x-pack/test/functional/es_archives/signals/README.md index 97c8c504a40393..01f8405d6ed589 100644 --- a/x-pack/test/functional/es_archives/signals/README.md +++ b/x-pack/test/functional/es_archives/signals/README.md @@ -24,3 +24,7 @@ A legacy signals index. It has no migration metadata fields and a very old mappi #### `signals/outdated_signals_index` A signals index that had previously been updated but is now out of date. It has migration metadata fields and a recent mapping version. + +#### `signals/index_alias_clash` + +An index that has the .siem-signals alias, but is NOT a signals index. Used for simulating an alerts-as-data index, which will have the .siem-signals alias but different mappings. This way we can test that functionality that needs to target only signals indices (e.g. mapping updates to apply field aliases) work correctly in the presence of alerts-as-data indices. diff --git a/x-pack/test/functional/es_archives/signals/index_alias_clash/data.json b/x-pack/test/functional/es_archives/signals/index_alias_clash/data.json new file mode 100644 index 00000000000000..8bba21bbfbae87 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/index_alias_clash/data.json @@ -0,0 +1,11 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "signal_name_clash", + "source": { + "@timestamp": "2020-10-28T05:08:53.000Z" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/signals/index_alias_clash/mappings.json b/x-pack/test/functional/es_archives/signals/index_alias_clash/mappings.json new file mode 100644 index 00000000000000..3a77af645b1181 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/index_alias_clash/mappings.json @@ -0,0 +1,24 @@ +{ + "type": "index", + "value": { + "aliases": { + ".siem-signals-default": { + "is_write_index": false + } + }, + "index": "index_alias_clash", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json b/x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json new file mode 100644 index 00000000000000..8f77950407841f --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json @@ -0,0 +1,433 @@ +{ + "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\":\"type\",\"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}]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "coreMigrationVersion": "8.0.0", + "id": "logstash-*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2018-12-21T00:43:07.096Z", + "version": "WzEzLDJd" +} + +{ + "attributes": { + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "c61a8afb-a185-4fae-a064-fb3846f6c451": { + "columnOrder": [ + "2cd09808-3915-49f4-b3b0-82767eba23f7" + ], + "columns": { + "2cd09808-3915-49f4-b3b0-82767eba23f7": { + "dataType": "number", + "isBucketed": false, + "label": "Maximum of bytes", + "operationType": "max", + "scale": "ratio", + "sourceField": "bytes" + } + } + } + } + } + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "accessor": "2cd09808-3915-49f4-b3b0-82767eba23f7", + "isHorizontal": false, + "layerId": "c61a8afb-a185-4fae-a064-fb3846f6c451", + "layers": [ + { + "accessors": [ + "d3e62a7a-c259-4fff-a2fc-eebf20b7008a", + "26ef70a9-c837-444c-886e-6bd905ee7335" + ], + "layerId": "c61a8afb-a185-4fae-a064-fb3846f6c451", + "seriesType": "area", + "splitAccessor": "54cd64ed-2a44-4591-af84-b2624504569a", + "xAccessor": "d6e40cea-6299-43b4-9c9d-b4ee305a2ce8" + } + ], + "legend": { + "isVisible": true, + "position": "right" + }, + "preferredSeriesType": "area" + } + }, + "title": "Artistpreviouslyknownaslens", + "visualizationType": "lnsMetric" + }, + "coreMigrationVersion": "8.0.0", + "id": "76fc4200-cf44-11e9-b933-fd84270f3ac1", + "migrationVersion": { + "lens": "7.14.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "indexpattern-datasource-layer-c61a8afb-a185-4fae-a064-fb3846f6c451", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2019-10-16T00:28:08.979Z", + "version": "WzIwLDJd" +} + +{ + "attributes": { + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { + "columnOrder": [ + "7a5d833b-ca6f-4e48-a924-d2a28d365dc3", + "3cf18f28-3495-4d45-a55f-d97f88022099", + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "columns": { + "3cf18f28-3495-4d45-a55f-d97f88022099": { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": { + "interval": "auto" + }, + "scale": "interval", + "sourceField": "@timestamp" + }, + "3dc0bd55-2087-4e60-aea2-f9910714f7db": { + "dataType": "number", + "isBucketed": false, + "label": "Average of bytes", + "operationType": "average", + "scale": "ratio", + "sourceField": "bytes" + }, + "7a5d833b-ca6f-4e48-a924-d2a28d365dc3": { + "dataType": "ip", + "isBucketed": true, + "label": "Top values of ip", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "ip" + } + } + } + } + } + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "accessors": [ + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "seriesType": "bar_stacked", + "splitAccessor": "7a5d833b-ca6f-4e48-a924-d2a28d365dc3", + "xAccessor": "3cf18f28-3495-4d45-a55f-d97f88022099" + } + ], + "legend": { + "isVisible": true, + "position": "right" + }, + "preferredSeriesType": "bar_stacked" + } + }, + "title": "lnsXYvis", + "visualizationType": "lnsXY" + }, + "coreMigrationVersion": "8.0.0", + "id": "76fc4200-cf44-11e9-b933-fd84270f3ac2", + "migrationVersion": { + "lens": "7.14.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "indexpattern-datasource-layer-4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2019-10-16T00:28:08.979Z", + "version": "WzIyLDJd" +} + +{ + "attributes": { + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { + "columnOrder": [ + "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8", + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "columns": { + "3dc0bd55-2087-4e60-aea2-f9910714f7db": { + "dataType": "number", + "isBucketed": false, + "label": "Average of bytes", + "operationType": "average", + "scale": "ratio", + "sourceField": "bytes" + }, + "5bd1c078-e1dd-465b-8d25-7a6404befa88": { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": { + "interval": "auto" + }, + "scale": "interval", + "sourceField": "@timestamp" + }, + "65340cf3-8402-4494-96f2-293701c59571": { + "dataType": "number", + "isBucketed": true, + "label": "Top values of bytes", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "bytes" + }, + "87554e1d-3dbf-4c1c-a358-4c9d40424cfa": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of type", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "type" + }, + "bafe3009-1776-4227-a0fe-b0d6ccbb4961": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of geo.dest", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 7 + }, + "scale": "ordinal", + "sourceField": "geo.dest" + }, + "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of geo.src", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "geo.src" + } + } + } + } + } + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "categoryDisplay": "default", + "groups": [ + "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8" + ], + "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "legendDisplay": "default", + "metric": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "nestedLegend": false, + "numberDisplay": "percent" + } + ], + "shape": "pie" + } + }, + "title": "lnsPieVis", + "visualizationType": "lnsPie" + }, + "coreMigrationVersion": "8.0.0", + "id": "9536bed0-d57e-11ea-b169-e3a222a76b9c", + "migrationVersion": { + "lens": "7.14.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "indexpattern-datasource-layer-4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2020-08-03T11:43:43.421Z", + "version": "WzIxLDJd" +} + +{ + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "A Pie", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}},\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"distinctColors\":true},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}" + }, + "coreMigrationVersion": "8.0.0", + "id": "i-exist", + "migrationVersion": { + "visualization": "7.14.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2019-01-22T19:32:31.206Z", + "version": "WzE2LDJd" +} + +{ + "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\":\"type\",\"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}]", + "timeFieldName": "@timestamp", + "title": "log*" + }, + "coreMigrationVersion": "8.0.0", + "id": "log*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2018-12-21T00:43:07.096Z", + "version": "WzE0LDJd" +} + +{ + "attributes": { + "description": "Ok responses for jpg files", + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "index": "b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b", + "key": "extension.raw", + "negate": false, + "params": { + "query": "jpg" + }, + "type": "phrase", + "value": "jpg" + }, + "query": { + "match": { + "extension.raw": { + "query": "jpg", + "type": "phrase" + } + } + } + } + ], + "query": { + "language": "kuery", + "query": "response:200" + }, + "title": "OKJpgs" + }, + "coreMigrationVersion": "8.0.0", + "id": "okjpgs", + "references": [], + "type": "query", + "updated_at": "2019-07-17T17:54:26.378Z", + "version": "WzE4LDJd" +} \ No newline at end of file diff --git a/x-pack/test/functional/fixtures/kbn_archiver/maps.json b/x-pack/test/functional/fixtures/kbn_archiver/maps.json index 78e49997d5c9ec..94ab038ae973b2 100644 --- a/x-pack/test/functional/fixtures/kbn_archiver/maps.json +++ b/x-pack/test/functional/fixtures/kbn_archiver/maps.json @@ -725,7 +725,7 @@ "description": "", "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\":\"SUPER_FINE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"TILED_VECTOR\"}]", "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"settings\":{\"autoFitToDataBounds\":false}}", - "title": "geo grid vector grid example SUPER_FINE resolution", + "title": "MVT geotile grid (style meta from ES)", "uiStateJSON": "{\"isDarkMode\":false}" }, "coreMigrationVersion": "8.0.0", @@ -744,6 +744,56 @@ "version": "WzU1LDJd" } +{ + "attributes": { + "description":"", + "layerListJSON":"[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\":\"SUPER_FINE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\",\"type\":\"ORDINAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true},\"type\":\"TILED_VECTOR\"}]", + "mapStateJSON":"{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-21T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "title":"MVT geotile grid (style meta from local - count)", + "uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"g1xkv\"]}" + }, + "coreMigrationVersion":"8.1.0", + "id":"943443a0-3b48-11ec-8a0d-af01166a5cc3", + "migrationVersion": { + "map":"8.0.0" + }, + "references": [ + { + "id":"c698b940-e149-11e8-a35a-370a8516603a", + "name":"layer_0_source_index_pattern", + "type":"index-pattern" + } + ], + "type":"map", + "updated_at":"2021-11-01T19:20:50.287Z", + "version":"WzkwLDFd" +} + +{ + "attributes": { + "description":"", + "layerListJSON":"[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\":\"SUPER_FINE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"sum\",\"field\":\"bytes\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"origin\":\"source\",\"name\":\"sum_of_bytes\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true},\"type\":\"TILED_VECTOR\"}]", + "mapStateJSON":"{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T04:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "title":"MVT geotile grid (style meta from local - metric)", + "uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"g1xkv\"]}" + }, + "coreMigrationVersion":"8.1.0", + "id":"9ff6f170-3b56-11ec-9cfb-57b0ede90800", + "migrationVersion": { + "map":"8.0.0" + }, + "references": [ + { + "id":"c698b940-e149-11e8-a35a-370a8516603a", + "name":"layer_0_source_index_pattern", + "type":"index-pattern" + } + ], + "type":"map", + "updated_at":"2021-11-01T21:01:40.951Z", + "version":"WzkyLDFd" +} + { "attributes": { "description": "", diff --git a/x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json b/x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json index c1274b4c78c905..ce308d9ec0bf32 100644 --- a/x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json +++ b/x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json @@ -712,3 +712,54 @@ "updated_at": "2021-09-20T23:37:22.367Z", "version": "WzY3LDFd" } + +{ + "attributes": { + "fields": "[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"category\"}}},{\"count\":0,\"name\":\"currency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"customer_birth_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"customer_first_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"customer_first_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"customer_first_name\"}}},{\"count\":1,\"name\":\"customer_full_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"customer_full_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"customer_full_name\"}}},{\"count\":0,\"name\":\"customer_gender\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"customer_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"customer_last_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"customer_last_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"customer_last_name\"}}},{\"count\":0,\"name\":\"customer_phone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"day_of_week\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"day_of_week_i\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"geoip.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"manufacturer\"}}},{\"count\":1,\"name\":\"order_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"order_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products._id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products._id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products._id\"}}},{\"count\":0,\"name\":\"products.base_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.base_unit_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products.category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products.category\"}}},{\"count\":0,\"name\":\"products.created_on\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.discount_percentage\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products.manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products.manufacturer\"}}},{\"count\":0,\"name\":\"products.min_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.product_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.product_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"products.product_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"products.product_name\"}}},{\"count\":0,\"name\":\"products.quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.tax_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.taxful_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.taxless_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"products.unit_discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"taxful_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"taxless_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"total_quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"total_unique_products\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "order_date", + "title": "ec*" + }, + "coreMigrationVersion": "8.0.0", + "id": "aac3e500-f2c7-11ea-8250-fb138aa491e7", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2020-09-09T18:10:54.007Z", + "version": "WzIwLDJd" +} + +{ + "attributes": { + "columns": [ + "category", + "customer_full_name", + "taxful_total_price", + "currency" + ], + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [], + "title": "EC SEARCH from DEFAULT", + "version": 1 + }, + "coreMigrationVersion": "8.0.0", + "id": "bbe45ae0-f2c7-11ea-8250-fb138aa491e7", + "migrationVersion": { + "search": "8.0.0" + }, + "references": [ + { + "id": "aac3e500-f2c7-11ea-8250-fb138aa491e7", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "updated_at": "2020-09-09T18:10:58.011Z", + "version": "WzI4LDJd" +} diff --git a/x-pack/test/functional/fixtures/kbn_archiver/reporting/logs.json b/x-pack/test/functional/fixtures/kbn_archiver/reporting/logs.json new file mode 100644 index 00000000000000..64fd4c8e5c20d8 --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/reporting/logs.json @@ -0,0 +1,441 @@ +{ + "attributes": { + "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": "logstash-*" + }, + "coreMigrationVersion": "8.0.0", + "id": "logstash-*", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzIsMl0=" +} + +{ + "attributes": { + "columns": [ + "clientip", + "extension" + ], + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[{\"meta\":{\"alias\":\"datefilter🥜\",\"negate\":false,\"type\":\"range\",\"key\":\"@timestamp\",\"value\":\"Sep 20, 2015 @ 03:19:40.307 to Sep 20, 2015 @ 03:26:56.221\",\"params\":{\"gte\":\"2015-09-20T10:19:40.307Z\",\"lt\":\"2015-09-20T10:26:56.221Z\"},\"disabled\":false,\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"range\":{\"@timestamp\":{\"gte\":\"2015-09-20T10:19:40.307Z\",\"lt\":\"2015-09-20T10:26:56.221Z\"}},\"$state\":{\"store\":\"appState\"}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "title": "A Saved Search With a DATE FILTER", + "version": 1 + }, + "coreMigrationVersion": "8.0.0", + "id": "d7a79750-3edd-11e9-99cc-4d80163ee9e7", + "migrationVersion": { + "search": "8.0.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + } + ], + "type": "search", + "updated_at": "2019-03-05T00:34:28.706Z", + "version": "WzMsMl0=" +} + +{ + "attributes": { + "buildNum": 9007199254740991, + "dateFormat:tz": "UTC", + "defaultIndex": "89655130-5013-11e9-bce7-4dabcb8bef24", + "csv:quoteValues": true, + "csv:separator": ",", + "search:queryLanguage": "lucene" + }, + "coreMigrationVersion": "8.0.0", + "id": "8.0.0", + "migrationVersion": { + "config": "8.0.0" + }, + "references": [], + "type": "config", + "updated_at": "2019-07-09T21:57:57.129Z", + "version": "WzMsMl0=" +} + +{ + "attributes": { + "fieldFormatMap": "{\"date\":{\"id\":\"date_nanos\"}}", + "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\":\"date\",\"type\":\"date\",\"esTypes\":[\"date_nanos\"],\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "title": "nanos" + }, + "coreMigrationVersion": "8.0.0", + "id": "907bc200-a294-11e9-a900-ef10e0ac769e", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2019-07-09T22:07:17.154Z", + "version": "WzQsMl0=" +} + +{ + "attributes": { + "columns": [ + "date", + "message", + "_id" + ], + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "date", + "desc" + ] + ], + "title": "TESTS DATE NANOS", + "version": 1 + }, + "coreMigrationVersion": "8.0.0", + "id": "e4035040-a295-11e9-a900-ef10e0ac769e", + "migrationVersion": { + "search": "8.0.0" + }, + "references": [ + { + "id": "907bc200-a294-11e9-a900-ef10e0ac769e", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "updated_at": "2019-07-09T22:07:22.318Z", + "version": "WzUsMl0=" +} + +{ + "id": "timeless-sales", + "attributes": { + "fields": "[{\"name\":\"@date\",\"type\":\"date\",\"count\":1,\"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\":\"metric\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"name\",\"type\":\"string\",\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"power\",\"type\":\"number\",\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"success\",\"type\":\"boolean\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "title": "sales" + }, + "migrationVersion": { + "index-pattern": "6.5.0" + }, + "references": [ + ], + "type": "index-pattern", + "updated_at": "2019-03-05T22:52:35.474Z" +} + +{ + "id": "71e3ee20-3f99-11e9-b8ee-6b9604f2f877", + "migrationVersion": { + "search": "7.0.0" + }, + "references": [ + { + "id": "timeless-sales", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + }, + { + "id": "timeless-sales", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + } + ], + "attributes": { + "columns": [ + "name", + "power" + ], + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"power\",\"negate\":false,\"params\":{\"gte\":1,\"lt\":null},\"type\":\"range\",\"value\":\"1 to \",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"range\":{\"power\":{\"gte\":1,\"lt\":null}}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [[ + "power", + "asc" + ]], + "title": "SALE POWER", + "version": 1 + }, + "type": "search", + "updated_at": "2019-03-05T22:53:08.481Z" +} + +{ + "attributes": { + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "0385075d-0f3b-4a12-b3c0-68a95781d48d": { + "columnOrder": [ + "03195b79-6315-40f7-b513-5222330367d7", + "2cb8226c-bfe5-4505-9c66-9f99ff6b5822" + ], + "columns": { + "03195b79-6315-40f7-b513-5222330367d7": { + "dataType": "date", + "isBucketed": true, + "label": "date", + "operationType": "date_histogram", + "params": { + "interval": "auto" + }, + "scale": "interval", + "sourceField": "date" + }, + "2cb8226c-bfe5-4505-9c66-9f99ff6b5822": { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "Records" + } + }, + "incompleteColumns": {} + } + } + } + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "accessors": [ + "2cb8226c-bfe5-4505-9c66-9f99ff6b5822" + ], + "layerId": "0385075d-0f3b-4a12-b3c0-68a95781d48d", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "xAccessor": "03195b79-6315-40f7-b513-5222330367d7" + } + ], + "legend": { + "isVisible": true, + "position": "right" + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide" + } + }, + "title": "distogram", + "visualizationType": "lnsXY" + }, + "coreMigrationVersion": "8.0.0", + "id": "4b498150-6821-11eb-9375-4bd700f7d8d4", + "migrationVersion": { + "lens": "8.0.0" + }, + "references": [], + "type": "lens", + "updated_at": "2021-02-06T02:16:17.129Z", + "version": "WzgsMl0=" +} + +{ + "attributes": { + "fieldAttrs": "{\"_id\":{\"count\":1},\"gender\":{\"count\":1},\"name\":{\"count\":1},\"value\":{\"count\":1},\"year\":{\"count\":1},\"years_ago\":{\"count\":1},\"date_informal\":{\"count\":1}}", + "fieldFormatMap": "{\"years_ago\":{\"id\":\"number\",\"params\":{\"pattern\":\"0,0.00000000000000000000\"}},\"year\":{\"id\":\"number\",\"params\":{\"pattern\":\"0\"}},\"date_informal\":{\"id\":\"date\",\"params\":{\"parsedUrl\":{\"origin\":\"http://localhost:5620\",\"pathname\":\"/app/dashboards\",\"basePath\":\"\"},\"pattern\":\"MMM Do YY\"}}}", + "fields": "[{\"count\":1,\"script\":\"2019 - doc['year'].value\",\"lang\":\"painless\",\"name\":\"years_ago\",\"type\":\"number\",\"scripted\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":1,\"script\":\"doc['date'].value\",\"lang\":\"painless\",\"name\":\"date_informal\",\"type\":\"date\",\"scripted\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "runtimeFieldMap": "{}", + "timeFieldName": "date", + "title": "babynames" + }, + "coreMigrationVersion": "8.0.0", + "id": "89655130-5013-11e9-bce7-4dabcb8bef24", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2021-02-06T02:15:25.565Z", + "version": "WzcsMl0=" +} + +{ + "attributes": { + "columns": [ + "_id", + "name", + "gender", + "value", + "year", + "years_ago", + "date_informal" + ], + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"sort\":[{\"date\":\"desc\"}],\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"fieldsFromSource\":[\"_id\",\"_index\",\"_score\",\"_source\",\"_type\",\"date\",\"gender\",\"name\",\"percent\",\"value\",\"year\",\"years_ago\",\"date_informal\"],\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "date", + "desc" + ] + ], + "title": "namessearch", + "version": 1 + }, + "coreMigrationVersion": "8.0.0", + "id": "cdb908f0-6820-11eb-9375-4bd700f7d8d4", + "migrationVersion": { + "search": "8.0.0" + }, + "references": [ + { + "id": "89655130-5013-11e9-bce7-4dabcb8bef24", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "updated_at": "2021-02-06T02:15:31.253Z", + "version": "WzYsMl0=" +} + +{ + "attributes": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}" + }, + "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}", + "panelsJSON": "[{\"version\":\"7.11.0\",\"type\":\"search\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":13,\"i\":\"56f914c9-9597-4781-bfc6-229d40b382c7\"},\"panelIndex\":\"56f914c9-9597-4781-bfc6-229d40b382c7\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_56f914c9-9597-4781-bfc6-229d40b382c7\"},{\"version\":\"7.11.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":13,\"w\":48,\"h\":14,\"i\":\"f3ea512f-e441-4206-8aa7-000b1418ea2b\"},\"panelIndex\":\"f3ea512f-e441-4206-8aa7-000b1418ea2b\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_f3ea512f-e441-4206-8aa7-000b1418ea2b\"}]", + "timeRestore": false, + "title": "names dashboard", + "version": 1 + }, + "coreMigrationVersion": "8.0.0", + "id": "52af6d10-6821-11eb-9375-4bd700f7d8d4", + "migrationVersion": { + "dashboard": "8.0.0" + }, + "references": [ + { + "id": "cdb908f0-6820-11eb-9375-4bd700f7d8d4", + "name": "56f914c9-9597-4781-bfc6-229d40b382c7:panel_56f914c9-9597-4781-bfc6-229d40b382c7", + "type": "search" + }, + { + "id": "4b498150-6821-11eb-9375-4bd700f7d8d4", + "name": "f3ea512f-e441-4206-8aa7-000b1418ea2b:panel_f3ea512f-e441-4206-8aa7-000b1418ea2b", + "type": "lens" + } + ], + "type": "dashboard", + "updated_at": "2021-02-06T02:16:29.540Z", + "version": "WzksMl0=" +} + +{ + "attributes": { + "@created": "2020-07-16T20:33:21.826Z", + "@timestamp": "2020-07-16T20:34:01.093Z", + "assets": {}, + "colors": [ + "#37988d", + "#c19628", + "#b83c6f", + "#3f9939", + "#1785b0", + "#ca5f35", + "#45bdb0", + "#f2bc33", + "#e74b8b", + "#4fbf48", + "#1ea6dc", + "#fd7643", + "#72cec3", + "#f5cc5d", + "#ec77a8", + "#7acf74", + "#4cbce4", + "#fd986f", + "#a1ded7", + "#f8dd91", + "#f2a4c5", + "#a6dfa2", + "#86d2ed", + "#fdba9f", + "#000000", + "#444444", + "#777777", + "#BBBBBB", + "#FFFFFF", + "rgba(255,255,255,0)" + ], + "css": ".canvasPage {\n\n}", + "height": 720, + "isWriteable": true, + "name": "Workpad of Death", + "page": 0, + "pages": [ + { + "elements": [ + { + "expression": "image \n dataurl=\"https://via.placeholder.com/728x90.png?text=test+external+image\" mode=\"contain\"\n| render", + "id": "element-4612f502-7880-418b-8107-a629e9b842bc", + "position": { + "angle": 0, + "height": 300, + "left": 20, + "parent": null, + "top": 20, + "width": 500 + } + } + ], + "groups": [], + "id": "page-28d24ed2-c162-408c-92b3-978d7433aa1d", + "style": { + "background": "#FFF" + }, + "transition": {} + } + ], + "variables": [], + "width": 1080 + }, + "coreMigrationVersion": "8.1.0", + "id": "workpad-e7464259-0b75-4b8c-81c8-8422b15ff201", + "migrationVersion": { + "canvas-workpad": "8.0.0" + }, + "references": [], + "type": "canvas-workpad", + "updated_at": "2020-07-16T20:34:01.098Z", + "version": "WzIsMl0=" +} diff --git a/x-pack/test/functional/page_objects/api_keys_page.ts b/x-pack/test/functional/page_objects/api_keys_page.ts index 99c2aa01a4eda2..9349eaa4bda0c3 100644 --- a/x-pack/test/functional/page_objects/api_keys_page.ts +++ b/x-pack/test/functional/page_objects/api_keys_page.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function ApiKeysPageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const find = getService('find'); return { async noAPIKeysHeading() { @@ -26,5 +27,70 @@ export function ApiKeysPageProvider({ getService }: FtrProviderContext) { async apiKeysPermissionDeniedMessage() { return await testSubjects.getVisibleText('apiKeysPermissionDeniedMessage'); }, + + async clickOnPromptCreateApiKey() { + return await testSubjects.click('apiKeysCreatePromptButton'); + }, + + async clickOnTableCreateApiKey() { + return await testSubjects.click('apiKeysCreateTableButton'); + }, + + async setApiKeyName(apiKeyName: string) { + return await testSubjects.setValue('apiKeyNameInput', apiKeyName); + }, + + async setApiKeyCustomExpiration(expirationTime: string) { + return await testSubjects.setValue('apiKeyCustomExpirationInput', expirationTime); + }, + + async submitOnCreateApiKey() { + return await testSubjects.click('formFlyoutSubmitButton'); + }, + + async isApiKeyModalExists() { + return await find.existsByCssSelector('[role="dialog"]'); + }, + + async getNewApiKeyCreation() { + const euiCallOutHeader = await find.byCssSelector('.euiCallOutHeader__title'); + return euiCallOutHeader.getVisibleText(); + }, + + async toggleCustomExpiration() { + return await testSubjects.click('apiKeyCustomExpirationSwitch'); + }, + + async getErrorCallOutText() { + const alertElem = await find.byCssSelector('[role="dialog"] [role="alert"] .euiText'); + return await alertElem.getVisibleText(); + }, + + async getApiKeysFirstPromptTitle() { + const titlePromptElem = await find.byCssSelector('.euiEmptyPrompt .euiTitle'); + return await titlePromptElem.getVisibleText(); + }, + + async deleteAllApiKeyOneByOne() { + const hasApiKeysToDelete = await testSubjects.exists('apiKeysTableDeleteAction'); + if (hasApiKeysToDelete) { + const apiKeysToDelete = await testSubjects.findAll('apiKeysTableDeleteAction'); + for (const element of apiKeysToDelete) { + await element.click(); + await testSubjects.click('confirmModalConfirmButton'); + } + } + }, + + async bulkDeleteApiKeys() { + const hasApiKeysToDelete = await testSubjects.exists('checkboxSelectAll', { + allowHidden: true, + }); + if (hasApiKeysToDelete) { + await testSubjects.click('checkboxSelectAll'); + await testSubjects.click('bulkInvalidateActionButton'); + await testSubjects.click('confirmModalConfirmButton'); + } + }, }; } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 790ac3ede496f8..266aa4955b6e82 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -797,6 +797,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, async getDatatableHeader(index = 0) { + log.debug(`All headers ${await testSubjects.getVisibleText('dataGridHeader')}`); return find.byCssSelector( `[data-test-subj="lnsDataTable"] [data-test-subj="dataGridHeader"] [role=columnheader]:nth-child(${ index + 1 @@ -897,12 +898,18 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont ); }, - async toggleColumnVisibility(dimension: string) { + async toggleColumnVisibility(dimension: string, no = 1) { await this.openDimensionEditor(dimension); const id = 'lns-table-column-hidden'; + await PageObjects.common.sleep(500); const isChecked = await testSubjects.isEuiSwitchChecked(id); + log.debug(`switch status before the toggle = ${isChecked}`); await testSubjects.setEuiSwitch(id, isChecked ? 'uncheck' : 'check'); + await PageObjects.common.sleep(500); + const isChecked2 = await testSubjects.isEuiSwitchChecked(id); + log.debug(`switch status after the toggle = ${isChecked2}`); await this.closeDimensionEditor(); + await PageObjects.common.sleep(500); await PageObjects.header.waitUntilLoadingHasFinished(); }, diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 3e69a5f43928a3..2f9259c16d4bfe 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -27,6 +27,9 @@ import { MonitoringBeatsListingProvider, MonitoringBeatDetailProvider, MonitoringBeatsSummaryStatusProvider, + MonitoringLogstashOverviewProvider, + MonitoringLogstashNodesProvider, + MonitoringLogstashNodeDetailProvider, MonitoringLogstashPipelinesProvider, MonitoringLogstashSummaryStatusProvider, MonitoringKibanaOverviewProvider, @@ -88,6 +91,9 @@ export const services = { monitoringBeatsListing: MonitoringBeatsListingProvider, monitoringBeatDetail: MonitoringBeatDetailProvider, monitoringBeatsSummaryStatus: MonitoringBeatsSummaryStatusProvider, + monitoringLogstashOverview: MonitoringLogstashOverviewProvider, + monitoringLogstashNodes: MonitoringLogstashNodesProvider, + monitoringLogstashNodeDetail: MonitoringLogstashNodeDetailProvider, monitoringLogstashPipelines: MonitoringLogstashPipelinesProvider, monitoringLogstashSummaryStatus: MonitoringLogstashSummaryStatusProvider, monitoringKibanaOverview: MonitoringKibanaOverviewProvider, diff --git a/x-pack/test/functional/services/ml/dashboard_embeddables.ts b/x-pack/test/functional/services/ml/dashboard_embeddables.ts index 0dc5cc8fae2d5e..5c55a16698cb68 100644 --- a/x-pack/test/functional/services/ml/dashboard_embeddables.ts +++ b/x-pack/test/functional/services/ml/dashboard_embeddables.ts @@ -93,13 +93,21 @@ export function MachineLearningDashboardEmbeddablesProvider( }); }, - async openJobSelectionFlyout() { + async assertAnomalySwimlaneExists() { + await retry.tryForTime(60 * 1000, async () => { + await testSubjects.existOrFail(`mlAnomalySwimlaneEmbeddableWrapper`); + }); + }, + + async openAnomalyJobSelectionFlyout( + mlEmbeddableType: 'ml_anomaly_swimlane' | 'ml_anomaly_charts' + ) { await retry.tryForTime(60 * 1000, async () => { await dashboardAddPanel.clickEditorMenuButton(); await testSubjects.existOrFail('dashboardEditorContextMenu', { timeout: 2000 }); await dashboardAddPanel.clickEmbeddableFactoryGroupButton('ml'); - await dashboardAddPanel.clickAddNewEmbeddableLink('ml_anomaly_charts'); + await dashboardAddPanel.clickAddNewEmbeddableLink(mlEmbeddableType); await mlDashboardJobSelectionTable.assertJobSelectionTableExists(); }); 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 a1bf8c6a65d70a..cf34a1372157c4 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 @@ -73,6 +73,7 @@ export function MachineLearningDataFrameAnalyticsResultsProvider( async assertTotalFeatureImportanceEvaluatePanelExists() { await testSubjects.existOrFail('mlDFExpandableSection-FeatureImportanceSummary'); + await this.scrollFeatureImportanceIntoView(); await testSubjects.existOrFail('mlTotalFeatureImportanceChart', { timeout: 30 * 1000 }); }, @@ -213,5 +214,33 @@ export function MachineLearningDataFrameAnalyticsResultsProvider( expect(buttonVisible).to.equal(true, 'Expected data grid cell button to be visible'); }); }, + + async scrollContentSectionIntoView(sectionId: string) { + await testSubjects.scrollIntoView(`mlDFExpandableSection-${sectionId}`); + }, + + async scrollAnalysisIntoView() { + await this.scrollContentSectionIntoView('analysis'); + }, + + async scrollRegressionEvaluationIntoView() { + await this.scrollContentSectionIntoView('RegressionEvaluation'); + }, + + async scrollClassificationEvaluationIntoView() { + await this.scrollContentSectionIntoView('ClassificationEvaluation'); + }, + + async scrollFeatureImportanceIntoView() { + await this.scrollContentSectionIntoView('FeatureImportanceSummary'); + }, + + async scrollScatterplotMatrixIntoView() { + await this.scrollContentSectionIntoView('splom'); + }, + + async scrollResultsIntoView() { + await this.scrollContentSectionIntoView('results'); + }, }; } diff --git a/x-pack/test/functional/services/ml/forecast.ts b/x-pack/test/functional/services/ml/forecast.ts new file mode 100644 index 00000000000000..c26216c97adfe3 --- /dev/null +++ b/x-pack/test/functional/services/ml/forecast.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 { FtrProviderContext } from '../../ftr_provider_context'; + +export function MachineLearningForecastProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async assertForecastButtonExists() { + await testSubjects.existOrFail( + 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' + ); + }, + + async assertForecastButtonEnabled(expectedValue: boolean) { + const isEnabled = await testSubjects.isEnabled( + 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' + ); + expect(isEnabled).to.eql( + expectedValue, + `Expected "forecast" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ + isEnabled ? 'enabled' : 'disabled' + }')` + ); + }, + + async assertForecastChartElementsExists() { + await testSubjects.existOrFail(`mlForecastArea`, { + timeout: 30 * 1000, + }); + await testSubjects.existOrFail(`mlForecastValuesline`, { + timeout: 30 * 1000, + }); + await testSubjects.existOrFail(`mlForecastMarkers`, { + timeout: 30 * 1000, + }); + }, + + async assertForecastChartElementsHidden() { + await testSubjects.missingOrFail(`mlForecastArea`, { + allowHidden: true, + timeout: 30 * 1000, + }); + await testSubjects.missingOrFail(`mlForecastValuesline`, { + allowHidden: true, + timeout: 30 * 1000, + }); + await testSubjects.missingOrFail(`mlForecastMarkers`, { + allowHidden: true, + timeout: 30 * 1000, + }); + }, + + async assertForecastCheckboxExists() { + await testSubjects.existOrFail(`mlForecastCheckbox`, { + timeout: 30 * 1000, + }); + }, + + async assertForecastCheckboxMissing() { + await testSubjects.missingOrFail(`mlForecastCheckbox`, { + timeout: 30 * 1000, + }); + }, + + async clickForecastCheckbox() { + await testSubjects.click('mlForecastCheckbox'); + }, + + async openForecastModal() { + await testSubjects.click( + 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' + ); + await testSubjects.existOrFail('mlModalForecast'); + }, + + async closeForecastModal() { + await testSubjects.click('mlModalForecast > mlModalForecastButtonClose'); + await this.assertForecastModalMissing(); + }, + + async assertForecastModalMissing() { + await testSubjects.missingOrFail(`mlModalForecast`, { + timeout: 30 * 1000, + }); + }, + + async assertForecastModalRunButtonEnabled(expectedValue: boolean) { + const isEnabled = await testSubjects.isEnabled('mlModalForecast > mlModalForecastButtonRun'); + expect(isEnabled).to.eql( + expectedValue, + `Expected forecast "run" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ + isEnabled ? 'enabled' : 'disabled' + }')` + ); + }, + + async assertForecastTableExists() { + await testSubjects.existOrFail('mlModalForecast > mlModalForecastTable'); + }, + + async clickForecastModalRunButton() { + await testSubjects.click('mlModalForecast > mlModalForecastButtonRun'); + await this.assertForecastModalMissing(); + }, + + async getForecastTableRows() { + return await testSubjects.findAll('mlModalForecastTable > ~mlForecastsListRow'); + }, + + async assertForecastTableNotEmpty() { + const tableRows = await this.getForecastTableRows(); + expect(tableRows.length).to.be.greaterThan( + 0, + `Forecast table should have at least one row (got '${tableRows.length}')` + ); + }, + }; +} diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index 17302b27822237..4b48e4c0269eb9 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -24,6 +24,7 @@ import { MachineLearningDataVisualizerProvider } from './data_visualizer'; import { MachineLearningDataVisualizerFileBasedProvider } from './data_visualizer_file_based'; import { MachineLearningDataVisualizerIndexBasedProvider } from './data_visualizer_index_based'; import { MachineLearningDataVisualizerIndexPatternManagementProvider } from './data_visualizer_index_pattern_management'; +import { MachineLearningForecastProvider } from './forecast'; import { MachineLearningJobManagementProvider } from './job_management'; import { MachineLearningJobSelectionProvider } from './job_selection'; import { MachineLearningJobSourceSelectionProvider } from './job_source_selection'; @@ -92,6 +93,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const dataVisualizerIndexPatternManagement = MachineLearningDataVisualizerIndexPatternManagementProvider(context, dataVisualizerTable); + const forecast = MachineLearningForecastProvider(context); const jobAnnotations = MachineLearningJobAnnotationsProvider(context); const jobManagement = MachineLearningJobManagementProvider(context, api); const jobSelection = MachineLearningJobSelectionProvider(context); @@ -145,6 +147,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { dataVisualizerIndexBased, dataVisualizerIndexPatternManagement, dataVisualizerTable, + forecast, jobAnnotations, jobManagement, jobSelection, diff --git a/x-pack/test/functional/services/ml/security_common.ts b/x-pack/test/functional/services/ml/security_common.ts index 54d2fa48a826f0..925565143bda0b 100644 --- a/x-pack/test/functional/services/ml/security_common.ts +++ b/x-pack/test/functional/services/ml/security_common.ts @@ -58,7 +58,7 @@ export function MachineLearningSecurityCommonProvider({ getService }: FtrProvide { name: 'ft_ml_ui_extras', elasticsearch: { - cluster: ['manage_ingest_pipelines', 'monitor'], + cluster: ['manage_ingest_pipelines'], }, kibana: [], }, diff --git a/x-pack/test/functional/services/ml/single_metric_viewer.ts b/x-pack/test/functional/services/ml/single_metric_viewer.ts index ac3fd67e3f94e8..29f1ded74debaa 100644 --- a/x-pack/test/functional/services/ml/single_metric_viewer.ts +++ b/x-pack/test/functional/services/ml/single_metric_viewer.ts @@ -22,24 +22,6 @@ export function MachineLearningSingleMetricViewerProvider( await testSubjects.existOrFail('mlNoSingleMetricJobsFound'); }, - async assertForecastButtonExists() { - await testSubjects.existOrFail( - 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' - ); - }, - - async assertForecastButtonEnabled(expectedValue: boolean) { - const isEnabled = await testSubjects.isEnabled( - 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' - ); - expect(isEnabled).to.eql( - expectedValue, - `Expected "forecast" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ - isEnabled ? 'enabled' : 'disabled' - }')` - ); - }, - async assertDetectorInputExist() { await testSubjects.existOrFail( 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerDetectorSelect' @@ -97,28 +79,6 @@ export function MachineLearningSingleMetricViewerProvider( }); }, - async openForecastModal() { - await testSubjects.click( - 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' - ); - await testSubjects.existOrFail('mlModalForecast'); - }, - - async closeForecastModal() { - await testSubjects.click('mlModalForecast > mlModalForecastButtonClose'); - await testSubjects.missingOrFail('mlModalForecast'); - }, - - async assertForecastModalRunButtonEnabled(expectedValue: boolean) { - const isEnabled = await testSubjects.isEnabled('mlModalForecast > mlModalForecastButtonRun'); - expect(isEnabled).to.eql( - expectedValue, - `Expected forecast "run" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ - isEnabled ? 'enabled' : 'disabled' - }')` - ); - }, - async openAnomalyExplorer() { await testSubjects.click('mlAnomalyResultsViewSelectorExplorer'); await testSubjects.existOrFail('mlPageAnomalyExplorer'); diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index 65a892d124edb4..071db63125a55b 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -128,6 +128,20 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider return createResponse.id; }, + async createBulkSavedObjects(body: object[]): Promise { + log.debug(`Creating bulk saved objects'`); + + const createResponse = await supertest + .post(`/api/saved_objects/_bulk_create`) + .set(COMMON_REQUEST_HEADERS) + .send(body) + .expect(200) + .then((res: any) => res.body); + + log.debug(` > Created bulk saved objects'`); + return createResponse; + }, + async createIndexPatternIfNeeded(title: string, timeFieldName?: string): Promise { const indexPatternId = await this.getIndexPatternId(title); if (indexPatternId !== undefined) { diff --git a/x-pack/test/functional/services/monitoring/cluster_overview.js b/x-pack/test/functional/services/monitoring/cluster_overview.js index 215e92fa055beb..835e566386e0a1 100644 --- a/x-pack/test/functional/services/monitoring/cluster_overview.js +++ b/x-pack/test/functional/services/monitoring/cluster_overview.js @@ -45,6 +45,7 @@ export function MonitoringClusterOverviewProvider({ getService }) { const SUBJ_LS_UPTIME = `${SUBJ_LS_PANEL} > lsUptime`; const SUBJ_LS_JVM_HEAP = `${SUBJ_LS_PANEL} > lsJvmHeap`; const SUBJ_LS_PIPELINES = `${SUBJ_LS_PANEL} > lsPipelines`; + const SUBJ_LS_OVERVIEW = `${SUBJ_LS_PANEL} > lsOverview`; const SUBJ_BEATS_PANEL = `clusterItemContainerBeats`; const SUBJ_BEATS_OVERVIEW = `${SUBJ_BEATS_PANEL} > beatsOverview`; @@ -179,6 +180,12 @@ export function MonitoringClusterOverviewProvider({ getService }) { getLsJvmHeap() { return testSubjects.getVisibleText(SUBJ_LS_JVM_HEAP); } + clickLsOverview() { + return testSubjects.click(SUBJ_LS_OVERVIEW); + } + clickLsNodes() { + return testSubjects.click(SUBJ_LS_NODES); + } getLsPipelines() { return testSubjects.getVisibleText(SUBJ_LS_PIPELINES); } diff --git a/x-pack/test/functional/services/monitoring/index.js b/x-pack/test/functional/services/monitoring/index.js index 2ca0e7fc920c1b..5d337dc6ca8228 100644 --- a/x-pack/test/functional/services/monitoring/index.js +++ b/x-pack/test/functional/services/monitoring/index.js @@ -20,6 +20,9 @@ export { MonitoringBeatsOverviewProvider } from './beats_overview'; export { MonitoringBeatsListingProvider } from './beats_listing'; export { MonitoringBeatDetailProvider } from './beat_detail'; export { MonitoringBeatsSummaryStatusProvider } from './beats_summary_status'; +export { MonitoringLogstashOverviewProvider } from './logstash_overview'; +export { MonitoringLogstashNodesProvider } from './logstash_nodes'; +export { MonitoringLogstashNodeDetailProvider } from './logstash_node_detail'; export { MonitoringLogstashPipelinesProvider } from './logstash_pipelines'; export { MonitoringLogstashSummaryStatusProvider } from './logstash_summary_status'; export { MonitoringKibanaOverviewProvider } from './kibana_overview'; diff --git a/x-pack/test/functional/services/monitoring/logstash_node_detail.js b/x-pack/test/functional/services/monitoring/logstash_node_detail.js new file mode 100644 index 00000000000000..42d32c0c1a2aae --- /dev/null +++ b/x-pack/test/functional/services/monitoring/logstash_node_detail.js @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function MonitoringLogstashNodeDetailProvider({ getService }) { + const testSubjects = getService('testSubjects'); + + const SUBJ_SUMMARY = 'logstashDetailStatus'; + const SUBJ_SUMMARY_HTTP_ADDRESS = `${SUBJ_SUMMARY} > httpAddress`; + const SUBJ_SUMMARY_EVENTS_IN = `${SUBJ_SUMMARY} > eventsIn`; + const SUBJ_SUMMARY_EVENTS_OUT = `${SUBJ_SUMMARY} > eventsOut`; + const SUBJ_SUMMARY_NUM_RELOADS = `${SUBJ_SUMMARY} > numReloads`; + const SUBJ_SUMMARY_PIPELINE_WORKERS = `${SUBJ_SUMMARY} > pipelineWorkers`; + const SUBJ_SUMMARY_PIPELINE_BATCH_SIZE = `${SUBJ_SUMMARY} > pipelineBatchSize`; + const SUBJ_SUMMARY_VERSION = `${SUBJ_SUMMARY} > version`; + const SUBJ_SUMMARY_UPTIME = `${SUBJ_SUMMARY} > uptime`; + + return new (class LogstashNodeDetail { + async clickPipelines() { + return testSubjects.click('logstashNodeDetailPipelinesLink'); + } + async clickAdvanced() { + return testSubjects.click('logstashNodeDetailAdvancedLink'); + } + + async getSummary() { + return { + httpAddress: await testSubjects.getVisibleText(SUBJ_SUMMARY_HTTP_ADDRESS), + eventsIn: await testSubjects.getVisibleText(SUBJ_SUMMARY_EVENTS_IN), + eventsOut: await testSubjects.getVisibleText(SUBJ_SUMMARY_EVENTS_OUT), + numReloads: await testSubjects.getVisibleText(SUBJ_SUMMARY_NUM_RELOADS), + pipelineWorkers: await testSubjects.getVisibleText(SUBJ_SUMMARY_PIPELINE_WORKERS), + pipelineBatchSize: await testSubjects.getVisibleText(SUBJ_SUMMARY_PIPELINE_BATCH_SIZE), + version: await testSubjects.getVisibleText(SUBJ_SUMMARY_VERSION), + uptime: await testSubjects.getVisibleText(SUBJ_SUMMARY_UPTIME), + }; + } + })(); +} diff --git a/x-pack/test/functional/services/monitoring/logstash_nodes.js b/x-pack/test/functional/services/monitoring/logstash_nodes.js new file mode 100644 index 00000000000000..2666cdc3d06541 --- /dev/null +++ b/x-pack/test/functional/services/monitoring/logstash_nodes.js @@ -0,0 +1,102 @@ +/* + * 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 { range } from 'lodash'; +export function MonitoringLogstashNodesProvider({ getService, getPageObjects }) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['monitoring']); + const find = getService('find'); + + const SUBJ_OVERVIEW_PAGE = 'logstashNodesPage'; + const SUBJ_TABLE_CONTAINER = 'logstashNodesTableContainer'; + const SUBJ_SEARCH_BAR = `${SUBJ_TABLE_CONTAINER} > monitoringTableToolBar`; + const SUBJ_TABLE_NO_DATA = `${SUBJ_TABLE_CONTAINER} > monitoringTableNoData`; + const SUBJ_NODE_NAME = `${SUBJ_TABLE_CONTAINER} > name`; + const SUBJ_NODE_ALERT_STATUS = `${SUBJ_TABLE_CONTAINER} > alertStatusText`; + const SUBJ_NODE_IP = `${SUBJ_TABLE_CONTAINER} > httpAddress`; + const SUBJ_NODE_CPU_USAGE = `${SUBJ_TABLE_CONTAINER} > cpuUsage`; + const SUBJ_NODE_LOAD_AVERAGE = `${SUBJ_TABLE_CONTAINER} > loadAverage`; + const SUBJ_NODE_JVM_HEAP_USED = `${SUBJ_TABLE_CONTAINER} > jvmHeapUsed`; + const SUBJ_NODE_EVENTS_OUT = `${SUBJ_TABLE_CONTAINER} > eventsOut`; + const SUBJ_NODE_CONFIG_RELOADS_SUCCESS = `${SUBJ_TABLE_CONTAINER} > configReloadsSuccess`; + const SUBJ_NODE_CONFIG_RELOADS_FAILURE = `${SUBJ_TABLE_CONTAINER} > configReloadsFailure`; + const SUBJ_NODE_VERSION = `${SUBJ_TABLE_CONTAINER} > version`; + + const SUBJ_NODE_LINK_PREFIX = `${SUBJ_TABLE_CONTAINER} > nodeLink-`; + + return new (class LogstashNodes { + async isOnNodesListing() { + const pageId = await retry.try(() => testSubjects.find(SUBJ_OVERVIEW_PAGE)); + return pageId !== null; + } + async clickRowByResolver(nodeResolver) { + await retry.waitForWithTimeout('redirection to node detail', 30000, async () => { + await testSubjects.click(SUBJ_NODE_LINK_PREFIX + nodeResolver, 5000); + return testSubjects.exists('logstashDetailStatus', { timeout: 5000 }); + }); + } + getRows() { + return PageObjects.monitoring.tableGetRowsFromContainer(SUBJ_TABLE_CONTAINER); + } + async setFilter(text) { + await PageObjects.monitoring.tableSetFilter(SUBJ_SEARCH_BAR, text); + await this.waitForTableToFinishLoading(); + } + + async clearFilter() { + await PageObjects.monitoring.tableClearFilter(SUBJ_SEARCH_BAR); + await this.waitForTableToFinishLoading(); + } + + assertNoData() { + return PageObjects.monitoring.assertTableNoData(SUBJ_TABLE_NO_DATA); + } + async waitForTableToFinishLoading() { + await retry.try(async () => { + await find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); + }); + } + async getNodesAll() { + const name = await testSubjects.getVisibleTextAll(SUBJ_NODE_NAME); + const alertStatus = await testSubjects.getVisibleTextAll(SUBJ_NODE_ALERT_STATUS); + const httpAddress = await testSubjects.getVisibleTextAll(SUBJ_NODE_IP); + const cpuUsage = await testSubjects.getVisibleTextAll(SUBJ_NODE_CPU_USAGE); + const loadAverage = await testSubjects.getVisibleTextAll(SUBJ_NODE_LOAD_AVERAGE); + const jvmHeapUsed = await testSubjects.getVisibleTextAll(SUBJ_NODE_JVM_HEAP_USED); + const eventsOut = await testSubjects.getVisibleTextAll(SUBJ_NODE_EVENTS_OUT); + const configReloadsSuccess = await testSubjects.getVisibleTextAll( + SUBJ_NODE_CONFIG_RELOADS_SUCCESS + ); + const configReloadsFailure = await testSubjects.getVisibleTextAll( + SUBJ_NODE_CONFIG_RELOADS_FAILURE + ); + const version = await testSubjects.getVisibleTextAll(SUBJ_NODE_VERSION); + + // tuple-ize the icons and texts together into an array of objects + const tableRows = await this.getRows(); + const iterator = range(tableRows.length); + return iterator.reduce((all, current) => { + return [ + ...all, + { + id: name[current], + httpAddress: httpAddress[current], + alertStatus: alertStatus[current], + cpuUsage: cpuUsage[current], + loadAverage: loadAverage[current], + jvmHeapUsed: jvmHeapUsed[current], + eventsOut: eventsOut[current], + configReloadsSuccess: configReloadsSuccess[current], + configReloadsFailure: configReloadsFailure[current], + version: version[current], + }, + ]; + }, []); + } + })(); +} diff --git a/x-pack/test/functional/services/monitoring/logstash_overview.js b/x-pack/test/functional/services/monitoring/logstash_overview.js new file mode 100644 index 00000000000000..55ac738c8ffea9 --- /dev/null +++ b/x-pack/test/functional/services/monitoring/logstash_overview.js @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function MonitoringLogstashOverviewProvider({ getService }) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + const SUBJ_OVERVIEW_PAGE = 'logstashOverviewPage'; + + return new (class LogstashOverview { + async isOnOverview() { + const pageId = await retry.try(() => testSubjects.find(SUBJ_OVERVIEW_PAGE)); + return pageId !== null; + } + })(); +} diff --git a/x-pack/test/functional/services/observability/alerts/common.ts b/x-pack/test/functional/services/observability/alerts/common.ts index f47d17039b5aef..74fbbb742fa0e9 100644 --- a/x-pack/test/functional/services/observability/alerts/common.ts +++ b/x-pack/test/functional/services/observability/alerts/common.ts @@ -16,7 +16,7 @@ const DATE_WITH_DATA = { }; const ALERTS_FLYOUT_SELECTOR = 'alertsFlyout'; -const COPY_TO_CLIPBOARD_BUTTON_SELECTOR = 'copy-to-clipboard'; +const FILTER_FOR_VALUE_BUTTON_SELECTOR = 'filter-for-value'; const ALERTS_TABLE_CONTAINER_SELECTOR = 'events-viewer-panel'; const ACTION_COLUMN_INDEX = 1; @@ -149,16 +149,12 @@ export function ObservabilityAlertsCommonProvider({ // Cell actions - const copyToClipboardButtonExists = async () => { - return await testSubjects.exists(COPY_TO_CLIPBOARD_BUTTON_SELECTOR); - }; - - const getCopyToClipboardButton = async () => { - return await testSubjects.find(COPY_TO_CLIPBOARD_BUTTON_SELECTOR); + const filterForValueButtonExists = async () => { + return await testSubjects.exists(FILTER_FOR_VALUE_BUTTON_SELECTOR); }; const getFilterForValueButton = async () => { - return await testSubjects.find('filter-for-value'); + return await testSubjects.find(FILTER_FOR_VALUE_BUTTON_SELECTOR); }; const openActionsMenuForRow = async (rowIndex: number) => { @@ -212,19 +208,25 @@ export function ObservabilityAlertsCommonProvider({ return buttonText.substring(0, buttonText.indexOf('\n')); }; + const getActionsButtonByIndex = async (index: number) => { + const actionsOverflowButtons = await find.allByCssSelector( + '[data-test-subj="alerts-table-row-action-more"]' + ); + return actionsOverflowButtons[index] || null; + }; + return { getQueryBar, clearQueryBar, closeAlertsFlyout, + filterForValueButtonExists, getAlertsFlyout, getAlertsFlyoutDescriptionListDescriptions, getAlertsFlyoutDescriptionListTitles, getAlertsFlyoutOrFail, getAlertsFlyoutTitle, getAlertsFlyoutViewInAppButtonOrFail, - getCopyToClipboardButton, getFilterForValueButton, - copyToClipboardButtonExists, getNoDataPageOrFail, getNoDataStateOrFail, getTableCells, @@ -241,5 +243,6 @@ export function ObservabilityAlertsCommonProvider({ typeInQueryBar, openActionsMenuForRow, getTimeRange, + getActionsButtonByIndex, }; } diff --git a/x-pack/test/functional_enterprise_search/services/app_search_service.ts b/x-pack/test/functional_enterprise_search/services/app_search_service.ts index edb3957692f27a..6cd3cac9f336b4 100644 --- a/x-pack/test/functional_enterprise_search/services/app_search_service.ts +++ b/x-pack/test/functional_enterprise_search/services/app_search_service.ts @@ -22,7 +22,7 @@ export interface IUser { user: string; password: string; } -export { IEngine }; +export type { IEngine }; export class AppSearchService { getEnterpriseSearchUser(): IUser { diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index fa144bd5bf9f6d..51a70026339c98 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { asyncForEach } from '@kbn/std'; +import { omit } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; import { generateUniqueKey } from '../../lib/get_test_data'; @@ -103,8 +104,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('test.always-firing-SelectOption'); } - // FLAKY https://github.com/elastic/kibana/issues/112749 - describe.skip('create alert', function () { + describe('create alert', function () { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('rulesTab'); @@ -154,14 +154,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(toastTitle).to.eql(`Created rule "${alertName}"`); await pageObjects.triggersActionsUI.searchAlerts(alertName); const searchResultsAfterSave = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResultsAfterSave).to.eql([ - { - name: alertName, - tagsText: '', - alertType: 'Index threshold', - interval: '1m', - }, - ]); + const searchResultAfterSave = searchResultsAfterSave[0]; + expect(omit(searchResultAfterSave, 'duration')).to.eql({ + name: `${alertName}Index threshold`, + tags: '', + interval: '1 min', + }); + expect(searchResultAfterSave.duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); // clean up created alert const alertsToDelete = await getAlertsByName(alertName); @@ -205,14 +204,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(toastTitle).to.eql(`Created rule "${alertName}"`); await pageObjects.triggersActionsUI.searchAlerts(alertName); const searchResultsAfterSave = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResultsAfterSave).to.eql([ - { - name: alertName, - tagsText: '', - alertType: 'Always Firing', - interval: '1m', - }, - ]); + const searchResultAfterSave = searchResultsAfterSave[0]; + expect(omit(searchResultAfterSave, 'duration')).to.eql({ + name: `${alertName}Always Firing`, + tags: '', + interval: '1 min', + }); // clean up created alert const alertsToDelete = await getAlertsByName(alertName); @@ -239,14 +236,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await new Promise((resolve) => setTimeout(resolve, 1000)); await pageObjects.triggersActionsUI.searchAlerts(alertName); const searchResultsAfterSave = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResultsAfterSave).to.eql([ - { - name: alertName, - tagsText: '', - alertType: 'Always Firing', - interval: '1m', - }, - ]); + const searchResultAfterSave = searchResultsAfterSave[0]; + expect(omit(searchResultAfterSave, 'duration')).to.eql({ + name: `${alertName}Always Firing`, + tags: '', + interval: '1 min', + }); // clean up created alert const alertsToDelete = await getAlertsByName(alertName); @@ -278,6 +273,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.existOrFail('testQuerySuccess'); await testSubjects.missingOrFail('testQueryError'); + await testSubjects.click('cancelSaveAlertButton'); + await testSubjects.existOrFail('confirmAlertCloseModal'); + await testSubjects.click('confirmAlertCloseModal > confirmModalConfirmButton'); + }); + + it('should show error when es_query is invalid', async () => { + const alertName = generateUniqueKey(); + await defineEsQueryAlert(alertName); + // Invalid query await testSubjects.setValue('queryJsonEditor', '{"query":{"foo":{}}}', { clearWithKeyboard: true, diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_list_items.ts index cb858a4791b718..7588d37d4b111e 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_list_items.ts @@ -27,6 +27,7 @@ import { deleteAllExceptions } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('create_exception_list_items', () => { describe('validation errors', () => { @@ -46,7 +47,7 @@ export default ({ getService }: FtrProviderContext) => { describe('creating exception list items', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should create a simple exception list item with a list item id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_lists.ts index e7f5d96bfb76a2..346e5a1269f3aa 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_exception_lists.ts @@ -21,11 +21,12 @@ import { deleteAllExceptions, removeExceptionListServerGeneratedProperties } fro // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('create_exception_lists', () => { describe('creating exception lists', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should create a simple exception list', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_list_items.ts index b084e423e88eef..29ea19940a480e 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_list_items.ts @@ -27,6 +27,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('create_list_items', () => { describe('validation errors', () => { @@ -46,11 +47,11 @@ export default ({ getService }: FtrProviderContext) => { describe('creating list items', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should create a simple list item with a list item id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_lists.ts index 599eaaa6bd0a48..fce6863d1adbe5 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/create_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/create_lists.ts @@ -24,15 +24,16 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('create_lists', () => { describe('creating lists', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should create a simple list with a list_id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_list_items.ts index acf968c8b78afa..7ff4870174300d 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_list_items.ts @@ -22,11 +22,12 @@ import { deleteAllExceptions, removeExceptionListItemServerGeneratedProperties } // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_exception_list_items', () => { describe('delete exception list items', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should delete a single exception list item by its item_id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_lists.ts index 0f8ca96e5383a0..f1cc67f3dd430b 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_exception_lists.ts @@ -21,11 +21,12 @@ import { deleteAllExceptions, removeExceptionListServerGeneratedProperties } fro // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_exception_lists', () => { describe('delete exception lists', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should delete a single exception list by its list_id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_list_items.ts index 9de5ec575ef327..57ca9a95ae7b78 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_list_items.ts @@ -22,15 +22,16 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_list_items', () => { describe('deleting list items', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should delete a single list item with a list item id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts index 3c01d93380736b..939291d1e4a5dd 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts @@ -33,15 +33,16 @@ import { DETECTION_TYPE, LIST_ID } from '../../../../plugins/lists/common/consta // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('delete_lists', () => { describe('deleting lists', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should delete a single list with a list id', async () => { @@ -116,7 +117,7 @@ export default ({ getService }: FtrProviderContext) => { describe('deleting lists referenced in exceptions', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should return an error when deleting a list referenced within an exception list item', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts index c61f4a2b1d02f8..54bd8f4d740cc4 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts @@ -22,11 +22,12 @@ import { getCreateExceptionListItemMinimalSchemaMock } from '../../../../plugins // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('export_exception_list_route', () => { describe('exporting exception lists', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should set the response content types to be expected', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/export_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_list_items.ts index efcc10518c6c06..ce2663cb96acbc 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/export_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_list_items.ts @@ -18,15 +18,16 @@ import { createListsIndex, deleteListsIndex, binaryToString } from '../../utils' // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('export_list_items', () => { describe('exporting lists', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should set the response content types to be expected', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_list_items.ts index bdacd674d75193..7cfb94451fbb1f 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_list_items.ts @@ -18,11 +18,12 @@ import { deleteAllExceptions, removeExceptionListItemServerGeneratedProperties } // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('find_exception_list_items', () => { describe('find exception list items', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should return an empty find body correctly if no exception list items are loaded', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_lists.ts index 158d951b2bd682..f5ff59de8eeca3 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_exception_lists.ts @@ -17,11 +17,12 @@ import { deleteAllExceptions, removeExceptionListServerGeneratedProperties } fro // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('find_exception_lists', () => { describe('find exception lists', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should return an empty find body correctly if no exception lists are loaded', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_list_items.ts index 9708abba4e2064..20dadcfda6f22c 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_list_items.ts @@ -23,15 +23,16 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('find_list_items', () => { describe('find list items', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should give a validation error if the list_id is not supplied', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_lists.ts index b6677ec09cfeb2..25b8c705633fc0 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/find_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/find_lists.ts @@ -21,15 +21,16 @@ import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugi // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('find_lists', () => { describe('find lists', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should return an empty find body correctly if no lists are loaded', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts index db8b35a805fbc4..894dcdfbea02a9 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts @@ -25,6 +25,7 @@ import { getImportListItemAsBuffer } from '../../../../plugins/lists/common/sche // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const log = getService('log'); describe('import_list_items', () => { describe('importing list items without an index', () => { @@ -46,11 +47,11 @@ export default ({ getService }: FtrProviderContext): void => { describe('importing lists with an index', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should set the response content types to be expected when importing two items', async () => { @@ -88,12 +89,16 @@ export default ({ getService }: FtrProviderContext): void => { // Although we try to be aggressive with waitFor in the lists code base, there is still not guarantees // that we will have the data just yet so we have to do a waitFor here for when it shows up - await waitFor(async () => { - const { status } = await supertest - .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) - .send(); - return status !== 404; - }, `${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`); + await waitFor( + async () => { + const { status } = await supertest + .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) + .send(); + return status !== 404; + }, + `${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`, + log + ); const { body } = await supertest .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) .send() diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_list_items.ts index 9345306e0c3e12..e0035a5ecf079b 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_list_items.ts @@ -22,11 +22,12 @@ import { deleteAllExceptions, removeExceptionListItemServerGeneratedProperties } // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('read_exception_list_items', () => { describe('reading exception list items', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should be able to read a single exception list items using item_id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_lists.ts index b9f2b89a3e0ee6..dd39ad97252875 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_exception_lists.ts @@ -21,11 +21,12 @@ import { deleteAllExceptions, removeExceptionListServerGeneratedProperties } fro // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('read_exception_lists', () => { describe('reading exception lists', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should be able to read a single exception list using list_id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_items.ts index f53e9c7434e357..7292440e1a12a1 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_items.ts @@ -22,15 +22,16 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('read_list_items', () => { describe('reading list items', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should be able to read a single list item using id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_lists.ts index af81801fb5e918..414177d2682138 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_lists.ts @@ -24,15 +24,16 @@ import { getListResponseMockWithoutAutoGeneratedValues } from '../../../../plugi // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('read_lists', () => { describe('reading lists', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should be able to read a single list using id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/summary_exception_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/summary_exception_lists.ts index b71a7dbe768d21..34d959a6f7103b 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/summary_exception_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/summary_exception_lists.ts @@ -21,15 +21,16 @@ interface SummaryResponseType { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('summary_exception_lists', () => { describe('summary exception lists', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); - await deleteAllExceptions(supertest); + await deleteListsIndex(supertest, log); + await deleteAllExceptions(supertest, log); }); it('should give a validation error if the list_id and the id are not supplied', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts index fa0466f14db28f..37c997f686565c 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_list_items.ts @@ -24,11 +24,12 @@ import { getUpdateMinimalExceptionListItemSchemaMock } from '../../../../plugins // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_exception_list_items', () => { describe('update exception list items', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should update a single exception list item property of name using an id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_lists.ts index 2eeaca7f0521b8..6844c847e8d929 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_exception_lists.ts @@ -23,11 +23,12 @@ import { getUpdateMinimalExceptionListSchemaMock } from '../../../../plugins/lis // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_exception_lists', () => { describe('update exception lists', () => { afterEach(async () => { - await deleteAllExceptions(supertest); + await deleteAllExceptions(supertest, log); }); it('should update a single exception list property of name using an id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_list_items.ts index 38d36ba3d7eee6..a4309d07a879d1 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_list_items.ts @@ -28,15 +28,16 @@ import { getUpdateMinimalListItemSchemaMock } from '../../../../plugins/lists/co // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_list_items', () => { describe('update list items', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should update a single list item property of value using an id', async () => { diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_lists.ts index 2e3f48354b22a0..1c3fe393caf0c0 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/update_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/update_lists.ts @@ -23,15 +23,16 @@ import { getUpdateMinimalListSchemaMock } from '../../../../plugins/lists/common // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const log = getService('log'); describe('update_lists', () => { describe('update lists', () => { beforeEach(async () => { - await createListsIndex(supertest); + await createListsIndex(supertest, log); }); afterEach(async () => { - await deleteListsIndex(supertest); + await deleteListsIndex(supertest, log); }); it('should update a single list property of name using an id', async () => { diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts index c8c1acb9f0e876..6e0c13b21596db 100644 --- a/x-pack/test/lists_api_integration/utils.ts +++ b/x-pack/test/lists_api_integration/utils.ts @@ -20,6 +20,7 @@ import { LIST_INDEX, LIST_ITEM_URL, } from '@kbn/securitysolution-list-constants'; +import { ToolingLog } from '@kbn/dev-utils'; import { getImportListItemAsBuffer } from '../../plugins/lists/common/schemas/request/import_list_item_schema.mock'; import { countDownTest } from '../detection_engine_api_integration/utils'; @@ -29,12 +30,17 @@ import { countDownTest } from '../detection_engine_api_integration/utils'; * @param supertest The supertest client library */ export const createListsIndex = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { - return countDownTest(async () => { - await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send(); - return true; - }, 'createListsIndex'); + return countDownTest( + async () => { + await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send(); + return true; + }, + 'createListsIndex', + log + ); }; /** @@ -42,12 +48,17 @@ export const createListsIndex = async ( * @param supertest The supertest client library */ export const deleteListsIndex = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { - return countDownTest(async () => { - await supertest.delete(LIST_INDEX).set('kbn-xsrf', 'true').send(); - return true; - }, 'deleteListsIndex'); + return countDownTest( + async () => { + await supertest.delete(LIST_INDEX).set('kbn-xsrf', 'true').send(); + return true; + }, + 'deleteListsIndex', + log + ); }; /** @@ -56,12 +67,17 @@ export const deleteListsIndex = async ( * @param supertest The supertest client library */ export const createExceptionListsIndex = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { - return countDownTest(async () => { - await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send(); - return true; - }, 'createListsIndex'); + return countDownTest( + async () => { + await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send(); + return true; + }, + 'createListsIndex', + log + ); }; /** @@ -116,20 +132,23 @@ export const removeExceptionListServerGeneratedProperties = ( export const waitFor = async ( functionToTest: () => Promise, functionName: string, - maxTimeout: number = 5000, - timeoutWait: number = 10 + log: ToolingLog, + maxTimeout: number = 800000, + timeoutWait: number = 250 ) => { await new Promise(async (resolve, reject) => { try { let found = false; let numberOfTries = 0; + const maxTries = Math.floor(maxTimeout / timeoutWait); - while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) { + while (!found && numberOfTries < maxTries) { const itPasses = await functionToTest(); if (itPasses) { found = true; } else { + log.debug(`Try number ${numberOfTries} out of ${maxTries} for function ${functionName}`); numberOfTries++; } @@ -169,7 +188,8 @@ export const binaryToString = (res: any, callback: any): void => { * @param supertest The supertest handle */ export const deleteAllExceptions = async ( - supertest: SuperTest.SuperTest + supertest: SuperTest.SuperTest, + log: ToolingLog ): Promise => { await countDownTest( async () => { @@ -189,6 +209,7 @@ export const deleteAllExceptions = async ( return finalCheck.data.length === 0; }, 'deleteAllExceptions', + log, 50, 1000 ); @@ -205,22 +226,30 @@ export const deleteAllExceptions = async ( */ export const importFile = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, type: Type, contents: string[], fileName: string, testValues?: string[] ): Promise => { - await supertest + const response = await supertest .post(`${LIST_ITEM_URL}/_import?type=${type}`) .set('kbn-xsrf', 'true') .attach('file', getImportListItemAsBuffer(contents), fileName) - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); + .expect('Content-Type', 'application/json; charset=utf-8'); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" When importing a file (importFile). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } // although we have pushed the list and its items, it is async so we // have to wait for the contents before continuing const testValuesOrContents = testValues ?? contents; - await waitForListItems(supertest, testValuesOrContents, fileName); + await waitForListItems(supertest, log, testValuesOrContents, fileName); }; /** @@ -234,20 +263,28 @@ export const importFile = async ( */ export const importTextFile = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, type: Type, contents: string[], fileName: string ): Promise => { - await supertest + const response = await supertest .post(`${LIST_ITEM_URL}/_import?type=${type}`) .set('kbn-xsrf', 'true') .attach('file', getImportListItemAsBuffer(contents), fileName) - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); + .expect('Content-Type', 'application/json; charset=utf-8'); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when importing a text file (importTextFile). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } // although we have pushed the list and its items, it is async so we // have to wait for the contents before continuing - await waitForTextListItems(supertest, contents, fileName); + await waitForTextListItems(supertest, log, contents, fileName); }; /** @@ -259,16 +296,27 @@ export const importTextFile = async ( */ export const waitForListItem = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, itemValue: string, fileName: string ): Promise => { - await waitFor(async () => { - const { status } = await supertest - .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${itemValue}`) - .send(); - - return status === 200; - }, `waitForListItem fileName: "${fileName}" itemValue: "${itemValue}"`); + await waitFor( + async () => { + const { status, body } = await supertest + .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${itemValue}`) + .send(); + if (status !== 200) { + log.debug( + `Did not get an expected 200 "ok" when waiting for a list item (waitForListItem) yet. Retrying until we get a 200 "ok". body: ${JSON.stringify( + body + )}, status: ${JSON.stringify(status)}` + ); + } + return status === 200; + }, + `waitForListItem fileName: "${fileName}" itemValue: "${itemValue}"`, + log + ); }; /** @@ -280,10 +328,11 @@ export const waitForListItem = async ( */ export const waitForListItems = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, itemValues: string[], fileName: string ): Promise => { - await Promise.all(itemValues.map((item) => waitForListItem(supertest, item, fileName))); + await Promise.all(itemValues.map((item) => waitForListItem(supertest, log, item, fileName))); }; /** @@ -295,21 +344,33 @@ export const waitForListItems = async ( */ export const waitForTextListItem = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, itemValue: string, fileName: string ): Promise => { const tokens = itemValue.split(' '); - await waitFor(async () => { - const promises = await Promise.all( - tokens.map(async (token) => { - const { status } = await supertest - .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${token}`) - .send(); - return status === 200; - }) - ); - return promises.every((one) => one); - }, `waitForTextListItem fileName: "${fileName}" itemValue: "${itemValue}"`); + await waitFor( + async () => { + const promises = await Promise.all( + tokens.map(async (token) => { + const { status, body } = await supertest + .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${token}`) + .send(); + if (status !== 200) { + log.error( + `Did not get an expected 200 "ok" when waiting for a text list item (waitForTextListItem) yet. Retrying until we get a 200 "ok". body: ${JSON.stringify( + body + )}, status: ${JSON.stringify(status)}` + ); + } + return status === 200; + }) + ); + return promises.every((one) => one); + }, + `waitForTextListItem fileName: "${fileName}" itemValue: "${itemValue}"`, + log + ); }; /** @@ -322,8 +383,9 @@ export const waitForTextListItem = async ( */ export const waitForTextListItems = async ( supertest: SuperTest.SuperTest, + log: ToolingLog, itemValues: string[], fileName: string ): Promise => { - await Promise.all(itemValues.map((item) => waitForTextListItem(supertest, item, fileName))); + await Promise.all(itemValues.map((item) => waitForTextListItem(supertest, log, item, fileName))); }; diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts index 0bea5992f55394..c48a8e33d6eef6 100644 --- a/x-pack/test/load/runner.ts +++ b/x-pack/test/load/runner.ts @@ -28,7 +28,11 @@ if (!Fs.existsSync(gatlingProjectRootPath)) { ); } -const dropEmptyLines = (s: string) => s.split(',').filter((i) => i.length > 0); +const dropEmptyLines = (s: string) => + s + .split(',') + .filter((i) => i.length > 0) + .map((i) => (i.includes('.') ? i : `branch.${i}`)); const simulationClasses = dropEmptyLines(simulationEntry); const simulationsRootPath = resolve(gatlingProjectRootPath, baseSimulationPath); diff --git a/x-pack/test/observability_api_integration/common/ftr_provider_context.ts b/x-pack/test/observability_api_integration/common/ftr_provider_context.ts index 8f59a8d3852815..2ea45b854eb280 100644 --- a/x-pack/test/observability_api_integration/common/ftr_provider_context.ts +++ b/x-pack/test/observability_api_integration/common/ftr_provider_context.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { FtrProviderContext } from '../../api_integration/ftr_provider_context'; +export type { FtrProviderContext } from '../../api_integration/ftr_provider_context'; diff --git a/x-pack/test/observability_functional/apps/observability/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/alerts/index.ts index 112c24f7c3a88c..3190a151cb47ba 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/index.ts @@ -189,19 +189,15 @@ export default ({ getService }: FtrProviderContext) => { await alertStatusCell.moveMouseTo(); await retry.waitFor( 'cell actions visible', - async () => await observability.alerts.common.copyToClipboardButtonExists() + async () => await observability.alerts.common.filterForValueButtonExists() ); }); }); afterEach(async () => { await observability.alerts.common.clearQueryBar(); - }); - - it('Copy button works', async () => { - // NOTE: We don't have access to the clipboard in a headless environment, - // so we'll just check the button is clickable in the functional tests. - await (await observability.alerts.common.getCopyToClipboardButton()).click(); + // Reset the query bar by hiding the dropdown + await observability.alerts.common.submitQuery(''); }); it('Filter for value works', async () => { @@ -218,5 +214,28 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('Actions Button', () => { + before(async () => { + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + observabilityCases: ['read'], + logs: ['read'], + }) + ); + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await observability.alerts.common.navigateToTimeWithData(); + }); + + after(async () => { + await observability.users.restoreDefaultTestUserRole(); + await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + }); + + it('Is disabled when a user has only read privilages', async () => { + const actionsButton = await observability.alerts.common.getActionsButtonByIndex(0); + expect(await actionsButton.getAttribute('disabled')).to.be('true'); + }); + }); }); }; diff --git a/x-pack/test/plugin_functional/plugins/global_search_test/public/index.ts b/x-pack/test/plugin_functional/plugins/global_search_test/public/index.ts index 43adc3f1036e20..9450384458d09e 100644 --- a/x-pack/test/plugin_functional/plugins/global_search_test/public/index.ts +++ b/x-pack/test/plugin_functional/plugins/global_search_test/public/index.ts @@ -21,7 +21,7 @@ export const plugin: PluginInitializer< GlobalSearchTestPluginStartDeps > = () => new GlobalSearchTestPlugin(); -export { +export type { GlobalSearchTestPluginSetup, GlobalSearchTestPluginStart, GlobalSearchTestPluginSetupDeps, diff --git a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx index a37c00144504d1..48a17dc9850035 100644 --- a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx +++ b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx @@ -91,6 +91,7 @@ const AppRoot = React.memo( setRefetch, start: '', rowRenderers: [], + runtimeMappings: {}, filterStatus: 'open', unit: (n: number) => `${n}`, })) ?? diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap index 806fa16f56921f..e54f173bbd04c9 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap +++ b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap @@ -1,68 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Reporting APIs CSV Generation from SearchSource Exports CSV with all fields when using defaults 1`] = ` -"_id,_index,_score,_type,category,category.keyword,currency,customer_first_name,customer_first_name.keyword,customer_full_name,customer_full_name.keyword,customer_gender,customer_id,customer_last_name,customer_last_name.keyword,customer_phone,day_of_week,day_of_week_i,email,geoip.city_name,geoip.continent_name,geoip.country_iso_code,geoip.location,geoip.region_name,manufacturer,manufacturer.keyword,order_date,order_id,products._id,products._id.keyword,products.base_price,products.base_unit_price,products.category,products.category.keyword,products.created_on,products.discount_amount,products.discount_percentage,products.manufacturer,products.manufacturer.keyword,products.min_price,products.price,products.product_id,products.product_name,products.product_name.keyword,products.quantity,products.sku,products.tax_amount,products.taxful_price,products.taxless_price,products.unit_discount_amount,sku,taxful_total_price,taxless_total_price,total_quantity,total_unique_products,type,user -9AMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,Men's Clothing,EUR,Boris,Boris,Boris Bradley,Boris Bradley,MALE,36,Bradley,Bradley,(empty),Wednesday,2,boris@bradley-family.zzz,-,Europe,GB,{ - \\"coordinates\\": [ - -0.1, - 51.5 - ], - \\"type\\": \\"Point\\" -},-,Microlutions, Elitelligence,Microlutions, Elitelligence,Jun 25, 2019 @ 00:00:00.000,568397,sold_product_568397_24419, sold_product_568397_20207,sold_product_568397_24419, sold_product_568397_20207,33, 28.984,33, 28.984,Men's Clothing, Men's Clothing,Men's Clothing, Men's Clothing,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Microlutions, Elitelligence,Microlutions, Elitelligence,17.484, 13.922,33, 28.984,24,419, 20,207,Cargo trousers - oliv, Trousers - black,Cargo trousers - oliv, Trousers - black,1, 1,ZO0112101121, ZO0530405304,0, 0,33, 28.984,33, 28.984,0, 0,ZO0112101121, ZO0530405304,61.969,61.969,2,2,order,boris -9QMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,Men's Clothing,EUR,Oliver,Oliver,Oliver Hubbard,Oliver Hubbard,MALE,7,Hubbard,Hubbard,(empty),Wednesday,2,oliver@hubbard-family.zzz,-,Europe,GB,{ - \\"coordinates\\": [ - -0.1, - 51.5 - ], - \\"type\\": \\"Point\\" -},-,Spritechnologies, Microlutions,Spritechnologies, Microlutions,Jun 25, 2019 @ 00:00:00.000,568044,sold_product_568044_12799, sold_product_568044_18008,sold_product_568044_12799, sold_product_568044_18008,14.992, 16.984,14.992, 16.984,Men's Clothing, Men's Clothing,Men's Clothing, Men's Clothing,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Spritechnologies, Microlutions,Spritechnologies, Microlutions,6.898, 8.828,14.992, 16.984,12,799, 18,008,Undershirt - dark grey multicolor, Long sleeved top - purple,Undershirt - dark grey multicolor, Long sleeved top - purple,1, 1,ZO0630406304, ZO0120201202,0, 0,14.992, 16.984,14.992, 16.984,0, 0,ZO0630406304, ZO0120201202,31.984,31.984,2,2,order,oliver -OAMtOW0BH63Xcmy432HJ,ecommerce,-,-,Women's Accessories,Women's Accessories,EUR,Betty,Betty,Betty Reese,Betty Reese,FEMALE,44,Reese,Reese,(empty),Wednesday,2,betty@reese-family.zzz,New York,North America,US,{ - \\"coordinates\\": [ - -74, - 40.7 - ], - \\"type\\": \\"Point\\" -},New York,Pyramidustries,Pyramidustries,Jun 25, 2019 @ 00:00:00.000,568229,sold_product_568229_24991, sold_product_568229_12039,sold_product_568229_24991, sold_product_568229_12039,11.992, 10.992,11.992, 10.992,Women's Accessories, Women's Accessories,Women's Accessories, Women's Accessories,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Pyramidustries, Pyramidustries,Pyramidustries, Pyramidustries,6.352, 5.82,11.992, 10.992,24,991, 12,039,Scarf - rose/white, Scarf - nude/black/turquoise,Scarf - rose/white, Scarf - nude/black/turquoise,1, 1,ZO0192201922, ZO0192801928,0, 0,11.992, 10.992,11.992, 10.992,0, 0,ZO0192201922, ZO0192801928,22.984,22.984,2,2,order,betty -OQMtOW0BH63Xcmy432HJ,ecommerce,-,-,Men's Clothing, Men's Accessories,Men's Clothing, Men's Accessories,EUR,Recip,Recip,Recip Salazar,Recip Salazar,MALE,10,Salazar,Salazar,(empty),Wednesday,2,recip@salazar-family.zzz,Istanbul,Asia,TR,{ - \\"coordinates\\": [ - 29, - 41 - ], - \\"type\\": \\"Point\\" -},Istanbul,Elitelligence,Elitelligence,Jun 25, 2019 @ 00:00:00.000,568292,sold_product_568292_23627, sold_product_568292_11149,sold_product_568292_23627, sold_product_568292_11149,24.984, 10.992,24.984, 10.992,Men's Clothing, Men's Accessories,Men's Clothing, Men's Accessories,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Elitelligence, Elitelligence,Elitelligence, Elitelligence,12.492, 5.059,24.984, 10.992,23,627, 11,149,Slim fit jeans - grey, Sunglasses - black,Slim fit jeans - grey, Sunglasses - black,1, 1,ZO0534205342, ZO0599605996,0, 0,24.984, 10.992,24.984, 10.992,0, 0,ZO0534205342, ZO0599605996,35.969,35.969,2,2,order,recip -jwMtOW0BH63Xcmy432HJ,ecommerce,-,-,Men's Clothing,Men's Clothing,EUR,Jackson,Jackson,Jackson Harper,Jackson Harper,MALE,13,Harper,Harper,(empty),Wednesday,2,jackson@harper-family.zzz,Los Angeles,North America,US,{ - \\"coordinates\\": [ - -118.2, - 34.1 - ], - \\"type\\": \\"Point\\" -},California,Low Tide Media, Oceanavigations,Low Tide Media, Oceanavigations,Jun 25, 2019 @ 00:00:00.000,568386,sold_product_568386_11959, sold_product_568386_2774,sold_product_568386_11959, sold_product_568386_2774,24.984, 85,24.984, 85,Men's Clothing, Men's Clothing,Men's Clothing, Men's Clothing,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Low Tide Media, Oceanavigations,Low Tide Media, Oceanavigations,12.742, 45.875,24.984, 85,11,959, 2,774,SLIM FIT - Formal shirt - lila, Classic coat - black,SLIM FIT - Formal shirt - lila, Classic coat - black,1, 1,ZO0422404224, ZO0291702917,0, 0,24.984, 85,24.984, 85,0, 0,ZO0422404224, ZO0291702917,110,110,2,2,order,jackson -" -`; - -exports[`Reporting APIs CSV Generation from SearchSource Exports CSV with almost all fields when using fieldsFromSource 1`] = ` -"_id,_index,_score,_type,category,currency,customer_first_name,customer_full_name,customer_gender,customer_id,customer_last_name,customer_phone,day_of_week,day_of_week_i,email,geoip,manufacturer,order_date,order_id,products,products.created_on,sku,taxful_total_price,taxless_total_price,total_quantity,total_unique_products,type,user -9AMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,EUR,Boris,Boris Bradley,MALE,36,Bradley,-,Wednesday,2,boris@bradley-family.zzz,{\\"continent_name\\":\\"Europe\\",\\"country_iso_code\\":\\"GB\\",\\"location\\":{\\"lat\\":51.5,\\"lon\\":-0.1}},Microlutions, Elitelligence,Jun 25, 2019 @ 00:00:00.000,568397,{\\"_id\\":\\"sold_product_568397_24419\\",\\"base_price\\":32.99,\\"base_unit_price\\":32.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Microlutions\\",\\"min_price\\":17.48,\\"price\\":32.99,\\"product_id\\":24419,\\"product_name\\":\\"Cargo trousers - oliv\\",\\"quantity\\":1,\\"sku\\":\\"ZO0112101121\\",\\"tax_amount\\":0,\\"taxful_price\\":32.99,\\"taxless_price\\":32.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568397_20207\\",\\"base_price\\":28.99,\\"base_unit_price\\":28.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Elitelligence\\",\\"min_price\\":13.92,\\"price\\":28.99,\\"product_id\\":20207,\\"product_name\\":\\"Trousers - black\\",\\"quantity\\":1,\\"sku\\":\\"ZO0530405304\\",\\"tax_amount\\":0,\\"taxful_price\\":28.99,\\"taxless_price\\":28.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0112101121, ZO0530405304,61.98,61.98,2,2,order,boris -9QMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,EUR,Oliver,Oliver Hubbard,MALE,7,Hubbard,-,Wednesday,2,oliver@hubbard-family.zzz,{\\"continent_name\\":\\"Europe\\",\\"country_iso_code\\":\\"GB\\",\\"location\\":{\\"lat\\":51.5,\\"lon\\":-0.1}},Spritechnologies, Microlutions,Jun 25, 2019 @ 00:00:00.000,568044,{\\"_id\\":\\"sold_product_568044_12799\\",\\"base_price\\":14.99,\\"base_unit_price\\":14.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Spritechnologies\\",\\"min_price\\":6.9,\\"price\\":14.99,\\"product_id\\":12799,\\"product_name\\":\\"Undershirt - dark grey multicolor\\",\\"quantity\\":1,\\"sku\\":\\"ZO0630406304\\",\\"tax_amount\\":0,\\"taxful_price\\":14.99,\\"taxless_price\\":14.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568044_18008\\",\\"base_price\\":16.99,\\"base_unit_price\\":16.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Microlutions\\",\\"min_price\\":8.83,\\"price\\":16.99,\\"product_id\\":18008,\\"product_name\\":\\"Long sleeved top - purple\\",\\"quantity\\":1,\\"sku\\":\\"ZO0120201202\\",\\"tax_amount\\":0,\\"taxful_price\\":16.99,\\"taxless_price\\":16.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0630406304, ZO0120201202,31.98,31.98,2,2,order,oliver -OAMtOW0BH63Xcmy432HJ,ecommerce,-,-,Women's Accessories,EUR,Betty,Betty Reese,FEMALE,44,Reese,-,Wednesday,2,betty@reese-family.zzz,{\\"city_name\\":\\"New York\\",\\"continent_name\\":\\"North America\\",\\"country_iso_code\\":\\"US\\",\\"location\\":{\\"lat\\":40.7,\\"lon\\":-74},\\"region_name\\":\\"New York\\"},Pyramidustries,Jun 25, 2019 @ 00:00:00.000,568229,{\\"_id\\":\\"sold_product_568229_24991\\",\\"base_price\\":11.99,\\"base_unit_price\\":11.99,\\"category\\":\\"Women's Accessories\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Pyramidustries\\",\\"min_price\\":6.35,\\"price\\":11.99,\\"product_id\\":24991,\\"product_name\\":\\"Scarf - rose/white\\",\\"quantity\\":1,\\"sku\\":\\"ZO0192201922\\",\\"tax_amount\\":0,\\"taxful_price\\":11.99,\\"taxless_price\\":11.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568229_12039\\",\\"base_price\\":10.99,\\"base_unit_price\\":10.99,\\"category\\":\\"Women's Accessories\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Pyramidustries\\",\\"min_price\\":5.82,\\"price\\":10.99,\\"product_id\\":12039,\\"product_name\\":\\"Scarf - nude/black/turquoise\\",\\"quantity\\":1,\\"sku\\":\\"ZO0192801928\\",\\"tax_amount\\":0,\\"taxful_price\\":10.99,\\"taxless_price\\":10.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0192201922, ZO0192801928,22.98,22.98,2,2,order,betty -OQMtOW0BH63Xcmy432HJ,ecommerce,-,-,Men's Clothing, Men's Accessories,EUR,Recip,Recip Salazar,MALE,10,Salazar,-,Wednesday,2,recip@salazar-family.zzz,{\\"city_name\\":\\"Istanbul\\",\\"continent_name\\":\\"Asia\\",\\"country_iso_code\\":\\"TR\\",\\"location\\":{\\"lat\\":41,\\"lon\\":29},\\"region_name\\":\\"Istanbul\\"},Elitelligence,Jun 25, 2019 @ 00:00:00.000,568292,{\\"_id\\":\\"sold_product_568292_23627\\",\\"base_price\\":24.99,\\"base_unit_price\\":24.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Elitelligence\\",\\"min_price\\":12.49,\\"price\\":24.99,\\"product_id\\":23627,\\"product_name\\":\\"Slim fit jeans - grey\\",\\"quantity\\":1,\\"sku\\":\\"ZO0534205342\\",\\"tax_amount\\":0,\\"taxful_price\\":24.99,\\"taxless_price\\":24.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568292_11149\\",\\"base_price\\":10.99,\\"base_unit_price\\":10.99,\\"category\\":\\"Men's Accessories\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Elitelligence\\",\\"min_price\\":5.06,\\"price\\":10.99,\\"product_id\\":11149,\\"product_name\\":\\"Sunglasses - black\\",\\"quantity\\":1,\\"sku\\":\\"ZO0599605996\\",\\"tax_amount\\":0,\\"taxful_price\\":10.99,\\"taxless_price\\":10.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0534205342, ZO0599605996,35.98,35.98,2,2,order,recip -" -`; - -exports[`Reporting APIs CSV Generation from SearchSource date formatting Formatted date_nanos data, UTC timezone 1`] = ` -"date,message -\\"Jan 1, 2015 @ 12:10:30.123456789\\",\\"Hello 2\\" -\\"Jan 1, 2015 @ 12:10:30.000000000\\",\\"Hello 1\\" -" -`; - -exports[`Reporting APIs CSV Generation from SearchSource date formatting Formatted date_nanos data, custom timezone (New York) 1`] = ` -"date,message -\\"Jan 1, 2015 @ 07:10:30.123456789\\",\\"Hello 2\\" -\\"Jan 1, 2015 @ 07:10:30.000000000\\",\\"Hello 1\\" -" -`; - exports[`Reporting APIs CSV Generation from SearchSource date formatting With filters and timebased data, default to UTC 1`] = ` "\\"@timestamp\\",clientip,extension \\"Sep 20, 2015 @ 10:26:48.725\\",\\"74.214.76.90\\",jpg @@ -191,6 +128,20 @@ exports[`Reporting APIs CSV Generation from SearchSource date formatting With fi " `; +exports[`Reporting APIs CSV Generation from SearchSource nanosecond formatting Formatted date_nanos data, UTC timezone 1`] = ` +"date,message +\\"Jan 1, 2015 @ 12:10:30.123456789\\",\\"Hello 2\\" +\\"Jan 1, 2015 @ 12:10:30.000000000\\",\\"Hello 1\\" +" +`; + +exports[`Reporting APIs CSV Generation from SearchSource nanosecond formatting Formatted date_nanos data, custom timezone (New York) 1`] = ` +"date,message +\\"Jan 1, 2015 @ 07:10:30.123456789\\",\\"Hello 2\\" +\\"Jan 1, 2015 @ 07:10:30.000000000\\",\\"Hello 1\\" +" +`; + exports[`Reporting APIs CSV Generation from SearchSource non-timebased Handle _id and _index columns 1`] = ` "date,message,\\"_id\\",\\"_index\\" \\"Jan 1, 2015 @ 12:10:30.123456789\\",\\"Hello 2\\",2,nanos @@ -215,6 +166,55 @@ exports[`Reporting APIs CSV Generation from SearchSource non-timebased With filt " `; +exports[`Reporting APIs CSV Generation from SearchSource unquoted values Exports CSV with all fields when using defaults 1`] = ` +"_id,_index,_score,_type,category,category.keyword,currency,customer_first_name,customer_first_name.keyword,customer_full_name,customer_full_name.keyword,customer_gender,customer_id,customer_last_name,customer_last_name.keyword,customer_phone,day_of_week,day_of_week_i,email,geoip.city_name,geoip.continent_name,geoip.country_iso_code,geoip.location,geoip.region_name,manufacturer,manufacturer.keyword,order_date,order_id,products._id,products._id.keyword,products.base_price,products.base_unit_price,products.category,products.category.keyword,products.created_on,products.discount_amount,products.discount_percentage,products.manufacturer,products.manufacturer.keyword,products.min_price,products.price,products.product_id,products.product_name,products.product_name.keyword,products.quantity,products.sku,products.tax_amount,products.taxful_price,products.taxless_price,products.unit_discount_amount,sku,taxful_total_price,taxless_total_price,total_quantity,total_unique_products,type,user +9AMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,Men's Clothing,EUR,Boris,Boris,Boris Bradley,Boris Bradley,MALE,36,Bradley,Bradley,(empty),Wednesday,2,boris@bradley-family.zzz,-,Europe,GB,{ + \\"coordinates\\": [ + -0.1, + 51.5 + ], + \\"type\\": \\"Point\\" +},-,Microlutions, Elitelligence,Microlutions, Elitelligence,Jun 25, 2019 @ 00:00:00.000,568397,sold_product_568397_24419, sold_product_568397_20207,sold_product_568397_24419, sold_product_568397_20207,33, 28.984,33, 28.984,Men's Clothing, Men's Clothing,Men's Clothing, Men's Clothing,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Microlutions, Elitelligence,Microlutions, Elitelligence,17.484, 13.922,33, 28.984,24,419, 20,207,Cargo trousers - oliv, Trousers - black,Cargo trousers - oliv, Trousers - black,1, 1,ZO0112101121, ZO0530405304,0, 0,33, 28.984,33, 28.984,0, 0,ZO0112101121, ZO0530405304,61.969,61.969,2,2,order,boris +9QMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,Men's Clothing,EUR,Oliver,Oliver,Oliver Hubbard,Oliver Hubbard,MALE,7,Hubbard,Hubbard,(empty),Wednesday,2,oliver@hubbard-family.zzz,-,Europe,GB,{ + \\"coordinates\\": [ + -0.1, + 51.5 + ], + \\"type\\": \\"Point\\" +},-,Spritechnologies, Microlutions,Spritechnologies, Microlutions,Jun 25, 2019 @ 00:00:00.000,568044,sold_product_568044_12799, sold_product_568044_18008,sold_product_568044_12799, sold_product_568044_18008,14.992, 16.984,14.992, 16.984,Men's Clothing, Men's Clothing,Men's Clothing, Men's Clothing,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Spritechnologies, Microlutions,Spritechnologies, Microlutions,6.898, 8.828,14.992, 16.984,12,799, 18,008,Undershirt - dark grey multicolor, Long sleeved top - purple,Undershirt - dark grey multicolor, Long sleeved top - purple,1, 1,ZO0630406304, ZO0120201202,0, 0,14.992, 16.984,14.992, 16.984,0, 0,ZO0630406304, ZO0120201202,31.984,31.984,2,2,order,oliver +OAMtOW0BH63Xcmy432HJ,ecommerce,-,-,Women's Accessories,Women's Accessories,EUR,Betty,Betty,Betty Reese,Betty Reese,FEMALE,44,Reese,Reese,(empty),Wednesday,2,betty@reese-family.zzz,New York,North America,US,{ + \\"coordinates\\": [ + -74, + 40.7 + ], + \\"type\\": \\"Point\\" +},New York,Pyramidustries,Pyramidustries,Jun 25, 2019 @ 00:00:00.000,568229,sold_product_568229_24991, sold_product_568229_12039,sold_product_568229_24991, sold_product_568229_12039,11.992, 10.992,11.992, 10.992,Women's Accessories, Women's Accessories,Women's Accessories, Women's Accessories,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Pyramidustries, Pyramidustries,Pyramidustries, Pyramidustries,6.352, 5.82,11.992, 10.992,24,991, 12,039,Scarf - rose/white, Scarf - nude/black/turquoise,Scarf - rose/white, Scarf - nude/black/turquoise,1, 1,ZO0192201922, ZO0192801928,0, 0,11.992, 10.992,11.992, 10.992,0, 0,ZO0192201922, ZO0192801928,22.984,22.984,2,2,order,betty +OQMtOW0BH63Xcmy432HJ,ecommerce,-,-,Men's Clothing, Men's Accessories,Men's Clothing, Men's Accessories,EUR,Recip,Recip,Recip Salazar,Recip Salazar,MALE,10,Salazar,Salazar,(empty),Wednesday,2,recip@salazar-family.zzz,Istanbul,Asia,TR,{ + \\"coordinates\\": [ + 29, + 41 + ], + \\"type\\": \\"Point\\" +},Istanbul,Elitelligence,Elitelligence,Jun 25, 2019 @ 00:00:00.000,568292,sold_product_568292_23627, sold_product_568292_11149,sold_product_568292_23627, sold_product_568292_11149,24.984, 10.992,24.984, 10.992,Men's Clothing, Men's Accessories,Men's Clothing, Men's Accessories,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Elitelligence, Elitelligence,Elitelligence, Elitelligence,12.492, 5.059,24.984, 10.992,23,627, 11,149,Slim fit jeans - grey, Sunglasses - black,Slim fit jeans - grey, Sunglasses - black,1, 1,ZO0534205342, ZO0599605996,0, 0,24.984, 10.992,24.984, 10.992,0, 0,ZO0534205342, ZO0599605996,35.969,35.969,2,2,order,recip +jwMtOW0BH63Xcmy432HJ,ecommerce,-,-,Men's Clothing,Men's Clothing,EUR,Jackson,Jackson,Jackson Harper,Jackson Harper,MALE,13,Harper,Harper,(empty),Wednesday,2,jackson@harper-family.zzz,Los Angeles,North America,US,{ + \\"coordinates\\": [ + -118.2, + 34.1 + ], + \\"type\\": \\"Point\\" +},California,Low Tide Media, Oceanavigations,Low Tide Media, Oceanavigations,Jun 25, 2019 @ 00:00:00.000,568386,sold_product_568386_11959, sold_product_568386_2774,sold_product_568386_11959, sold_product_568386_2774,24.984, 85,24.984, 85,Men's Clothing, Men's Clothing,Men's Clothing, Men's Clothing,Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,0, 0,0, 0,Low Tide Media, Oceanavigations,Low Tide Media, Oceanavigations,12.742, 45.875,24.984, 85,11,959, 2,774,SLIM FIT - Formal shirt - lila, Classic coat - black,SLIM FIT - Formal shirt - lila, Classic coat - black,1, 1,ZO0422404224, ZO0291702917,0, 0,24.984, 85,24.984, 85,0, 0,ZO0422404224, ZO0291702917,110,110,2,2,order,jackson +" +`; + +exports[`Reporting APIs CSV Generation from SearchSource unquoted values Exports CSV with almost all fields when using fieldsFromSource 1`] = ` +"_id,_index,_score,_type,category,currency,customer_first_name,customer_full_name,customer_gender,customer_id,customer_last_name,customer_phone,day_of_week,day_of_week_i,email,geoip,manufacturer,order_date,order_id,products,products.created_on,sku,taxful_total_price,taxless_total_price,total_quantity,total_unique_products,type,user +9AMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,EUR,Boris,Boris Bradley,MALE,36,Bradley,-,Wednesday,2,boris@bradley-family.zzz,{\\"continent_name\\":\\"Europe\\",\\"country_iso_code\\":\\"GB\\",\\"location\\":{\\"lat\\":51.5,\\"lon\\":-0.1}},Microlutions, Elitelligence,Jun 25, 2019 @ 00:00:00.000,568397,{\\"_id\\":\\"sold_product_568397_24419\\",\\"base_price\\":32.99,\\"base_unit_price\\":32.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Microlutions\\",\\"min_price\\":17.48,\\"price\\":32.99,\\"product_id\\":24419,\\"product_name\\":\\"Cargo trousers - oliv\\",\\"quantity\\":1,\\"sku\\":\\"ZO0112101121\\",\\"tax_amount\\":0,\\"taxful_price\\":32.99,\\"taxless_price\\":32.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568397_20207\\",\\"base_price\\":28.99,\\"base_unit_price\\":28.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Elitelligence\\",\\"min_price\\":13.92,\\"price\\":28.99,\\"product_id\\":20207,\\"product_name\\":\\"Trousers - black\\",\\"quantity\\":1,\\"sku\\":\\"ZO0530405304\\",\\"tax_amount\\":0,\\"taxful_price\\":28.99,\\"taxless_price\\":28.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0112101121, ZO0530405304,61.98,61.98,2,2,order,boris +9QMtOW0BH63Xcmy432DJ,ecommerce,-,-,Men's Clothing,EUR,Oliver,Oliver Hubbard,MALE,7,Hubbard,-,Wednesday,2,oliver@hubbard-family.zzz,{\\"continent_name\\":\\"Europe\\",\\"country_iso_code\\":\\"GB\\",\\"location\\":{\\"lat\\":51.5,\\"lon\\":-0.1}},Spritechnologies, Microlutions,Jun 25, 2019 @ 00:00:00.000,568044,{\\"_id\\":\\"sold_product_568044_12799\\",\\"base_price\\":14.99,\\"base_unit_price\\":14.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Spritechnologies\\",\\"min_price\\":6.9,\\"price\\":14.99,\\"product_id\\":12799,\\"product_name\\":\\"Undershirt - dark grey multicolor\\",\\"quantity\\":1,\\"sku\\":\\"ZO0630406304\\",\\"tax_amount\\":0,\\"taxful_price\\":14.99,\\"taxless_price\\":14.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568044_18008\\",\\"base_price\\":16.99,\\"base_unit_price\\":16.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Microlutions\\",\\"min_price\\":8.83,\\"price\\":16.99,\\"product_id\\":18008,\\"product_name\\":\\"Long sleeved top - purple\\",\\"quantity\\":1,\\"sku\\":\\"ZO0120201202\\",\\"tax_amount\\":0,\\"taxful_price\\":16.99,\\"taxless_price\\":16.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0630406304, ZO0120201202,31.98,31.98,2,2,order,oliver +OAMtOW0BH63Xcmy432HJ,ecommerce,-,-,Women's Accessories,EUR,Betty,Betty Reese,FEMALE,44,Reese,-,Wednesday,2,betty@reese-family.zzz,{\\"city_name\\":\\"New York\\",\\"continent_name\\":\\"North America\\",\\"country_iso_code\\":\\"US\\",\\"location\\":{\\"lat\\":40.7,\\"lon\\":-74},\\"region_name\\":\\"New York\\"},Pyramidustries,Jun 25, 2019 @ 00:00:00.000,568229,{\\"_id\\":\\"sold_product_568229_24991\\",\\"base_price\\":11.99,\\"base_unit_price\\":11.99,\\"category\\":\\"Women's Accessories\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Pyramidustries\\",\\"min_price\\":6.35,\\"price\\":11.99,\\"product_id\\":24991,\\"product_name\\":\\"Scarf - rose/white\\",\\"quantity\\":1,\\"sku\\":\\"ZO0192201922\\",\\"tax_amount\\":0,\\"taxful_price\\":11.99,\\"taxless_price\\":11.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568229_12039\\",\\"base_price\\":10.99,\\"base_unit_price\\":10.99,\\"category\\":\\"Women's Accessories\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Pyramidustries\\",\\"min_price\\":5.82,\\"price\\":10.99,\\"product_id\\":12039,\\"product_name\\":\\"Scarf - nude/black/turquoise\\",\\"quantity\\":1,\\"sku\\":\\"ZO0192801928\\",\\"tax_amount\\":0,\\"taxful_price\\":10.99,\\"taxless_price\\":10.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0192201922, ZO0192801928,22.98,22.98,2,2,order,betty +OQMtOW0BH63Xcmy432HJ,ecommerce,-,-,Men's Clothing, Men's Accessories,EUR,Recip,Recip Salazar,MALE,10,Salazar,-,Wednesday,2,recip@salazar-family.zzz,{\\"city_name\\":\\"Istanbul\\",\\"continent_name\\":\\"Asia\\",\\"country_iso_code\\":\\"TR\\",\\"location\\":{\\"lat\\":41,\\"lon\\":29},\\"region_name\\":\\"Istanbul\\"},Elitelligence,Jun 25, 2019 @ 00:00:00.000,568292,{\\"_id\\":\\"sold_product_568292_23627\\",\\"base_price\\":24.99,\\"base_unit_price\\":24.99,\\"category\\":\\"Men's Clothing\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Elitelligence\\",\\"min_price\\":12.49,\\"price\\":24.99,\\"product_id\\":23627,\\"product_name\\":\\"Slim fit jeans - grey\\",\\"quantity\\":1,\\"sku\\":\\"ZO0534205342\\",\\"tax_amount\\":0,\\"taxful_price\\":24.99,\\"taxless_price\\":24.99,\\"unit_discount_amount\\":0}, {\\"_id\\":\\"sold_product_568292_11149\\",\\"base_price\\":10.99,\\"base_unit_price\\":10.99,\\"category\\":\\"Men's Accessories\\",\\"created_on\\":\\"2016-12-14T00:00:00+00:00\\",\\"discount_amount\\":0,\\"discount_percentage\\":0,\\"manufacturer\\":\\"Elitelligence\\",\\"min_price\\":5.06,\\"price\\":10.99,\\"product_id\\":11149,\\"product_name\\":\\"Sunglasses - black\\",\\"quantity\\":1,\\"sku\\":\\"ZO0599605996\\",\\"tax_amount\\":0,\\"taxful_price\\":10.99,\\"taxless_price\\":10.99,\\"unit_discount_amount\\":0},Dec 14, 2016 @ 00:00:00.000, Dec 14, 2016 @ 00:00:00.000,ZO0534205342, ZO0599605996,35.98,35.98,2,2,order,recip +" +`; + exports[`Reporting APIs CSV Generation from SearchSource validation Searches large amount of data, stops at Max Size Reached 1`] = ` "\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user 3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,\\"(empty)\\",Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{ diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts b/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts index 3515602342db55..9e99f5886894e4 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts @@ -38,171 +38,173 @@ export default function ({ getService }: FtrProviderContext) { describe('CSV Generation from SearchSource', () => { before(async () => { + await reportingAPI.initEcommerce(); + await reportingAPI.initLogs(); await kibanaServer.uiSettings.update({ - 'csv:quoteValues': false, + 'csv:quoteValues': true, 'dateFormat:tz': 'UTC', - defaultIndex: 'logstash-*', }); - await reportingAPI.initEcommerce(); }); + after(async () => { await reportingAPI.teardownEcommerce(); + await reportingAPI.teardownLogs(); await reportingAPI.deleteAllReports(); }); - it('Exports CSV with almost all fields when using fieldsFromSource', async () => { - const { - status: resStatus, - text: resText, - type: resType, - } = (await generateAPI.getCSVFromSearchSource( - getMockJobParams({ - searchSource: { - query: { query: '', language: 'kuery' }, - index: '5193f870-d861-11e9-a311-0fa548c5f953', - sort: [{ order_date: 'desc' }], - fieldsFromSource: [ - '_id', - '_index', - '_score', - '_source', - '_type', - 'category', - 'category.keyword', - 'currency', - 'customer_birth_date', - 'customer_first_name', - 'customer_first_name.keyword', - 'customer_full_name', - 'customer_full_name.keyword', - 'customer_gender', - 'customer_id', - 'customer_last_name', - 'customer_last_name.keyword', - 'customer_phone', - 'day_of_week', - 'day_of_week_i', - 'email', - 'geoip.city_name', - 'geoip.continent_name', - 'geoip.country_iso_code', - 'geoip.location', - 'geoip.region_name', - 'manufacturer', - 'manufacturer.keyword', - 'order_date', - 'order_id', - 'products._id', - 'products._id.keyword', - 'products.base_price', - 'products.base_unit_price', - 'products.category', - 'products.category.keyword', - 'products.created_on', - 'products.discount_amount', - 'products.discount_percentage', - 'products.manufacturer', - 'products.manufacturer.keyword', - 'products.min_price', - 'products.price', - 'products.product_id', - 'products.product_name', - 'products.product_name.keyword', - 'products.quantity', - 'products.sku', - 'products.tax_amount', - 'products.taxful_price', - 'products.taxless_price', - 'products.unit_discount_amount', - 'sku', - 'taxful_total_price', - 'taxless_total_price', - 'total_quantity', - 'total_unique_products', - 'type', - 'user', - ], - filter: [], - parent: { - query: { language: 'kuery', query: '' }, + describe('unquoted values', () => { + before(async () => { + await kibanaServer.uiSettings.update({ 'csv:quoteValues': false }); + }); + + after(async () => { + await kibanaServer.uiSettings.update({ 'csv:quoteValues': true }); + }); + + it('Exports CSV with almost all fields when using fieldsFromSource', async () => { + const { + status: resStatus, + text: resText, + type: resType, + } = (await generateAPI.getCSVFromSearchSource( + getMockJobParams({ + searchSource: { + query: { query: '', language: 'kuery' }, + index: '5193f870-d861-11e9-a311-0fa548c5f953', + sort: [{ order_date: 'desc' }], + fieldsFromSource: [ + '_id', + '_index', + '_score', + '_source', + '_type', + 'category', + 'category.keyword', + 'currency', + 'customer_birth_date', + 'customer_first_name', + 'customer_first_name.keyword', + 'customer_full_name', + 'customer_full_name.keyword', + 'customer_gender', + 'customer_id', + 'customer_last_name', + 'customer_last_name.keyword', + 'customer_phone', + 'day_of_week', + 'day_of_week_i', + 'email', + 'geoip.city_name', + 'geoip.continent_name', + 'geoip.country_iso_code', + 'geoip.location', + 'geoip.region_name', + 'manufacturer', + 'manufacturer.keyword', + 'order_date', + 'order_id', + 'products._id', + 'products._id.keyword', + 'products.base_price', + 'products.base_unit_price', + 'products.category', + 'products.category.keyword', + 'products.created_on', + 'products.discount_amount', + 'products.discount_percentage', + 'products.manufacturer', + 'products.manufacturer.keyword', + 'products.min_price', + 'products.price', + 'products.product_id', + 'products.product_name', + 'products.product_name.keyword', + 'products.quantity', + 'products.sku', + 'products.tax_amount', + 'products.taxful_price', + 'products.taxless_price', + 'products.unit_discount_amount', + 'sku', + 'taxful_total_price', + 'taxless_total_price', + 'total_quantity', + 'total_unique_products', + 'type', + 'user', + ], filter: [], parent: { - filter: [ - { - meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, - range: { - order_date: { - gte: fromTime, - lte: toTime, - format: 'strict_date_optional_time', + query: { language: 'kuery', query: '' }, + filter: [], + parent: { + filter: [ + { + meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, + range: { + order_date: { + gte: fromTime, + lte: toTime, + format: 'strict_date_optional_time', + }, }, }, - }, - ], + ], + }, }, }, - }, - browserTimezone: 'UTC', - title: 'testfooyu78yt90-', - }) - )) as supertest.Response; - expect(resStatus).to.eql(200); - expect(resType).to.eql('text/csv'); - expectSnapshot(resText).toMatch(); - }); + browserTimezone: 'UTC', + title: 'testfooyu78yt90-', + }) + )) as supertest.Response; + expect(resStatus).to.eql(200); + expect(resType).to.eql('text/csv'); + expectSnapshot(resText).toMatch(); + }); - it('Exports CSV with all fields when using defaults', async () => { - const { - status: resStatus, - text: resText, - type: resType, - } = await generateAPI.getCSVFromSearchSource( - getMockJobParams({ - searchSource: { - query: { query: '', language: 'kuery' }, - index: '5193f870-d861-11e9-a311-0fa548c5f953', - sort: [{ order_date: 'desc' }], - fields: ['*'], - filter: [], - parent: { - query: { language: 'kuery', query: '' }, + it('Exports CSV with all fields when using defaults', async () => { + const { + status: resStatus, + text: resText, + type: resType, + } = await generateAPI.getCSVFromSearchSource( + getMockJobParams({ + searchSource: { + query: { query: '', language: 'kuery' }, + index: '5193f870-d861-11e9-a311-0fa548c5f953', + sort: [{ order_date: 'desc' }], + fields: ['*'], filter: [], parent: { - filter: [ - { - meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, - range: { - order_date: { - gte: fromTime, - lte: toTime, - format: 'strict_date_optional_time', + query: { language: 'kuery', query: '' }, + filter: [], + parent: { + filter: [ + { + meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, + range: { + order_date: { + gte: fromTime, + lte: toTime, + format: 'strict_date_optional_time', + }, }, }, - }, - ], + ], + }, }, }, - }, - browserTimezone: 'UTC', - title: 'testfooyu78yt90-', - }) - ); - expect(resStatus).to.eql(200); - expect(resType).to.eql('text/csv'); - expectSnapshot(resText).toMatch(); + browserTimezone: 'UTC', + title: 'testfooyu78yt90-', + }) + ); + expect(resStatus).to.eql(200); + expect(resType).to.eql('text/csv'); + expectSnapshot(resText).toMatch(); + }); }); describe('date formatting', () => { - before(async () => { - // load test data that contains a saved search and documents - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - }); - it('With filters and timebased data, default to UTC', async () => { const res = (await generateAPI.getCSVFromSearchSource( getMockJobParams({ @@ -277,10 +279,18 @@ export default function ({ getService }: FtrProviderContext) { expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); }); + }); - it('Formatted date_nanos data, UTC timezone', async () => { + describe('nanosecond formatting', () => { + before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/reporting/nanos'); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/reporting/nanos'); + }); + + it('Formatted date_nanos data, UTC timezone', async () => { const res = await generateAPI.getCSVFromSearchSource( getMockJobParams({ searchSource: { @@ -298,13 +308,9 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/nanos'); }); it('Formatted date_nanos data, custom timezone (New York)', async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/nanos'); - const res = await generateAPI.getCSVFromSearchSource( getMockJobParams({ browserTimezone: 'America/New_York', @@ -323,8 +329,6 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/nanos'); }); }); @@ -354,7 +358,6 @@ export default function ({ getService }: FtrProviderContext) { }); it('With filters and non-timebased data', async () => { - // load test data that contains a saved search and documents await esArchiver.load('x-pack/test/functional/es_archives/reporting/sales'); const { @@ -405,8 +408,6 @@ export default function ({ getService }: FtrProviderContext) { // NOTE: this test requires having the test server run with `xpack.reporting.csv.maxSizeBytes=6000` it(`Searches large amount of data, stops at Max Size Reached`, async () => { - await reportingAPI.initEcommerce(); - const { status: resStatus, text: resText, @@ -447,8 +448,6 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await reportingAPI.teardownEcommerce(); }); }); }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts b/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts index 9e3ddfaf57b39b..bd662fb391f152 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts @@ -12,7 +12,6 @@ import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../services/fixtures'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const supertestSvc = getService('supertest'); const reportingAPI = getService('reportingAPI'); @@ -32,13 +31,11 @@ export default function ({ getService }: FtrProviderContext) { describe('Generation from Legacy Job Params', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.initLogs(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.teardownLogs(); await reportingAPI.deleteAllReports(); }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts index d1dc091992dd64..af6afe99e8c9d7 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts @@ -13,7 +13,6 @@ import { ILM_POLICY_NAME } from '../../../plugins/reporting/common/constants'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const es = getService('es'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); @@ -22,13 +21,12 @@ export default function ({ getService }: FtrProviderContext) { describe('ILM policy migration APIs', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.initLogs(); + await reportingAPI.migrateReportingIndices(); // ensure that the ILM policy exists for the first test }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.teardownLogs(); }); afterEach(async () => { diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts index f6654ff5a6b1d4..6ea6de34825012 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts @@ -14,6 +14,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { const reportingAPI = getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); await reportingAPI.createDataAnalystRole(); await reportingAPI.createTestReportingUserRole(); await reportingAPI.createDataAnalyst(); 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 f0972086584672..842cfbcf7c1e1f 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 @@ -10,11 +10,9 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const reportingAPI = getService('reportingAPI'); const retry = getService('retry'); const supertest = getService('supertest'); - const archive = 'x-pack/test/functional/es_archives/reporting/canvas_disallowed_url'; /* * The Reporting API Functional Test config implements a network policy that @@ -22,11 +20,11 @@ export default function ({ getService }: FtrProviderContext) { */ describe('Network Policy', () => { before(async () => { - await esArchiver.load(archive); // includes a canvas worksheet with an offending image URL + await reportingAPI.initLogs(); // includes a canvas worksheet with an offending image URL }); after(async () => { - await esArchiver.unload(archive); + await reportingAPI.teardownLogs(); }); it('should fail job when page voilates the network policy', async () => { diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts index e61195e2f95c88..e1ca664122c763 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts @@ -38,18 +38,19 @@ export default function ({ getService }: FtrProviderContext) { ); }; + const spacesSharedObjectsArchive = + 'x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces'; + describe('Exports and Spaces', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce'); - await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces'); // multiple spaces with different config settings + await esArchiver.load(spacesSharedObjectsArchive); // multiple spaces with different config settings + await reportingAPI.initEcommerce(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce'); - await esArchiver.unload( - 'x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces' - ); + await reportingAPI.teardownEcommerce(); await reportingAPI.deleteAllReports(); + await esArchiver.unload(spacesSharedObjectsArchive); }); describe('CSV saved search export', () => { diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts index 81ca3e05e4dd0d..258ae814f5789a 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts @@ -8,8 +8,12 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Reporting API Integration Tests with Security disabled', function () { + before(async () => { + const reportingAPI = getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); + }); this.tags('ciGroup13'); loadTestFile(require.resolve('./job_apis_csv')); loadTestFile(require.resolve('./job_apis_csv_deprecated')); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts index 06f3756593d767..e1935c2617f416 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts @@ -49,12 +49,12 @@ export default function ({ getService }: FtrProviderContext) { describe('Job Listing APIs', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); + await reportingAPI.initLogs(); await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); + await reportingAPI.teardownLogs(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); }); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts index 6ff8946d48c5bc..5cd60653526492 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts @@ -27,19 +27,16 @@ const parseApiJSON = (apiResponseText: string): { job: ReportApiJSON; path: stri // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const supertestNoAuth = getService('supertestWithoutAuth'); const reportingAPI = getService('reportingAPI'); describe('Job Listing APIs: Deprecated CSV Export', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.initLogs(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.teardownLogs(); }); afterEach(async () => { diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts index e39a3e2e5954b0..6af60018d01dad 100644 --- a/x-pack/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -29,13 +29,29 @@ export function createScenarios({ getService }: Pick { + // Check task manager health for analyzing test failures. See https://github.com/elastic/kibana/issues/114946 + const tmHealth = await supertest.get(`/api/task_manager/_health`); + const driftValues = tmHealth.body?.stats?.runtime?.value; + + log.info(`Task Manager status: "${tmHealth.body?.status}"`); + log.info(`Task Manager overall drift rankings: "${JSON.stringify(driftValues?.drift)}"`); + log.info( + `Task Manager drift rankings for "report:execute": "${JSON.stringify( + driftValues?.drift_by_type?.['report:execute'] + )}"` + ); + }; + const initEcommerce = async () => { await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce'); await kibanaServer.importExport.load(ecommerceSOPath); @@ -46,6 +62,15 @@ export function createScenarios({ getService }: Pick { + await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.load(logsSOPath); + }; + const teardownLogs = async () => { + await kibanaServer.importExport.unload(logsSOPath); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + }; + const createDataAnalystRole = async () => { await security.role.create('data_analyst', { metadata: {}, @@ -132,7 +157,11 @@ export function createScenarios({ getService }: Pick { + const generateCsv = async ( + job: JobParamsCSV, + username = 'elastic', + password = process.env.TEST_KIBANA_PASS || 'changeme' + ) => { const jobParams = rison.encode(job as object as RisonValue); return await supertestWithoutAuth .post(`/api/reporting/generate/csv_searchsource`) @@ -201,8 +230,11 @@ export function createScenarios({ getService }: Pick { + const reportingAPI = context.getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); await createDataAnalystRole(); await createDataAnalyst(); await createReportingUser(); diff --git a/x-pack/test/reporting_functional/reporting_and_security/index.ts b/x-pack/test/reporting_functional/reporting_and_security/index.ts index be0e76a28bd0b3..22057c9be77dc6 100644 --- a/x-pack/test/reporting_functional/reporting_and_security/index.ts +++ b/x-pack/test/reporting_functional/reporting_and_security/index.ts @@ -14,6 +14,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { const reportingFunctional = getService('reportingFunctional'); + await reportingFunctional.logTaskManagerHealth(); await reportingFunctional.createDataAnalystRole(); await reportingFunctional.createDataAnalyst(); await reportingFunctional.createTestReportingUserRole(); diff --git a/x-pack/test/reporting_functional/reporting_without_security/index.ts b/x-pack/test/reporting_functional/reporting_without_security/index.ts index d1801b7e3e2e6c..fecc0e97daac04 100644 --- a/x-pack/test/reporting_functional/reporting_without_security/index.ts +++ b/x-pack/test/reporting_functional/reporting_without_security/index.ts @@ -11,6 +11,12 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Reporting Functional Tests with Security disabled', function () { this.tags('ciGroup2'); + + before(async () => { + const reportingAPI = getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); + }); + loadTestFile(require.resolve('./management')); }); } diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index 84c31dc2b7db60..e7d2c630fc130d 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -57,11 +57,12 @@ "index": ".kibana", "source": { "index-pattern": { - "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,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"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,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"xss\"}}}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, + "namespaces": ["default"], "type": "index-pattern", + "migrationVersion": { "index-pattern": "8.0.0" }, "updated_at": "2017-09-21T18:49:16.270Z" }, "type": "doc" @@ -145,16 +146,16 @@ { "type": "doc", "value": { - "id": "space_1:index-pattern:space1-index-pattern-id", + "id": "index-pattern:space1-index-pattern-id", "index": ".kibana", "source": { "index-pattern": { - "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,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"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,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"xss\"}}}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, - "namespace": "space_1", + "namespaces": ["space_1"], "type": "index-pattern", + "migrationVersion": { "index-pattern": "8.0.0" }, "updated_at": "2017-09-21T18:49:16.270Z" }, "type": "doc" @@ -240,16 +241,16 @@ { "type": "doc", "value": { - "id": "space_2:index-pattern:space2-index-pattern-id", + "id": "index-pattern:space2-index-pattern-id", "index": ".kibana", "source": { "index-pattern": { - "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,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"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,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"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,\"subType\":{\"multi\":{\"parent\": \"xss\"}}}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, - "namespace": "space_2", + "namespaces": ["space_2"], "type": "index-pattern", + "migrationVersion": { "index-pattern": "8.0.0" }, "updated_at": "2017-09-21T18:49:16.270Z" }, "type": "doc" @@ -554,6 +555,22 @@ } } +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "resolvetype:all_spaces", + "source": { + "type": "resolvetype", + "updated_at": "2017-09-21T18:51:23.794Z", + "resolvetype": { + "title": "This is used to test that 1. the 'disabled' alias does not resolve to this target (because the alias is disabled), and 2. when this object that exists in all spaces is deleted, the alias that targets it is deleted too (even though the alias is disabled)" + }, + "namespaces": ["*"] + } + } +} + { "type": "doc", "value": { @@ -566,7 +583,7 @@ "sourceId": "disabled", "targetNamespace": "space_1", "targetType": "resolvetype", - "targetId": "alias-match-newid", + "targetId": "disabled-newid", "disabled": true } } @@ -585,7 +602,7 @@ "sourceId": "alias-match", "targetNamespace": "space_x", "targetType": "resolvetype", - "targetId": "doesnt-matter" + "targetId": "alias-match-newid" } } } @@ -603,7 +620,7 @@ "sourceId": "alias-match", "targetNamespace": "space_y", "targetType": "resolvetype", - "targetId": "doesnt-matter", + "targetId": "alias-match-newid", "disabled": true } } diff --git a/x-pack/test/saved_object_api_integration/common/suites/delete.ts b/x-pack/test/saved_object_api_integration/common/suites/delete.ts index 844da51d16e80b..4dbd7901a05c4c 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/delete.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/delete.ts @@ -6,7 +6,9 @@ */ import { SuperTest } from 'supertest'; +import type { Client } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; +import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; import { SPACES } from '../lib/spaces'; import { expectResponses, getUrlPrefix, getTestTitle } from '../lib/saved_object_test_utils'; @@ -21,9 +23,13 @@ export interface DeleteTestCase extends TestCase { failure?: 400 | 403 | 404; } +const ALIAS_DELETE_INCLUSIVE = Object.freeze({ type: 'resolvetype', id: 'alias-match-newid' }); // exists in three specific spaces; deleting this should also delete the alias that targets it in space 1 +const ALIAS_DELETE_EXCLUSIVE = Object.freeze({ type: 'resolvetype', id: 'all_spaces' }); // exists in all spaces; deleting this should also delete the alias that targets it in space 1 const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); export const TEST_CASES: Record = Object.freeze({ ...CASES, + ALIAS_DELETE_INCLUSIVE, + ALIAS_DELETE_EXCLUSIVE, DOES_NOT_EXIST, }); @@ -32,7 +38,7 @@ export const TEST_CASES: Record = Object.freeze({ */ const createRequest = ({ type, id, force }: DeleteTestCase) => ({ type, id, force }); -export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest) { +export function deleteTestSuiteFactory(es: Client, esArchiver: any, supertest: SuperTest) { const expectSavedObjectForbidden = expectResponses.forbiddenTypes('delete'); const expectResponseBody = (testCase: DeleteTestCase): ExpectResponseBody => @@ -47,6 +53,25 @@ export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest testCase.type === type && testCase.id === id + ); + expect((searchResponse.hits.total as SearchTotalHits).value).to.eql( + // Five aliases exist but only one should be deleted in each case (for the "inclusive" case, this asserts that the aliases + // targeting that object in space x and space y were *not* deleted) + expectAliasWasDeleted ? 4 : 5 + ); } } }; diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts index 6a6fc8a15decfd..8970070645f4df 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts @@ -51,6 +51,8 @@ const createTestCases = (spaceId: string) => { }, { ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail404(spaceId !== SPACE_1_ID) }, CASES.NAMESPACE_AGNOSTIC, + { ...CASES.ALIAS_DELETE_INCLUSIVE, force: true }, + { ...CASES.ALIAS_DELETE_EXCLUSIVE, force: true }, { ...CASES.DOES_NOT_EXIST, ...fail404() }, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }]; @@ -61,8 +63,9 @@ const createTestCases = (spaceId: string) => { export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); + const es = getService('es'); - const { addTests, createTestDefinitions } = deleteTestSuiteFactory(esArchiver, supertest); + const { addTests, createTestDefinitions } = deleteTestSuiteFactory(es, esArchiver, supertest); const createTests = (spaceId: string) => { const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); return { diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/delete.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/delete.ts index 1a168bac948bef..28674e8fd45aa5 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/delete.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/delete.ts @@ -45,6 +45,8 @@ const createTestCases = (spaceId: string) => [ }, { ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail404(spaceId !== SPACE_1_ID) }, CASES.NAMESPACE_AGNOSTIC, + { ...CASES.ALIAS_DELETE_INCLUSIVE, force: true }, + { ...CASES.ALIAS_DELETE_EXCLUSIVE, force: true }, { ...CASES.HIDDEN, ...fail404() }, { ...CASES.DOES_NOT_EXIST, ...fail404() }, ]; @@ -52,8 +54,9 @@ const createTestCases = (spaceId: string) => [ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const es = getService('es'); - const { addTests, createTestDefinitions } = deleteTestSuiteFactory(esArchiver, supertest); + const { addTests, createTestDefinitions } = deleteTestSuiteFactory(es, esArchiver, supertest); const createTests = (spaceId: string) => { const testCases = createTestCases(spaceId); return createTestDefinitions(testCases, false, { spaceId }); diff --git a/x-pack/test/saved_object_tagging/common/lib/index.ts b/x-pack/test/saved_object_tagging/common/lib/index.ts index ae662def2459c5..9d23dc2541f8c6 100644 --- a/x-pack/test/saved_object_tagging/common/lib/index.ts +++ b/x-pack/test/saved_object_tagging/common/lib/index.ts @@ -5,6 +5,6 @@ * 2.0. */ -export { Role, User, ExpectedResponse } from './types'; +export type { Role, User, ExpectedResponse } from './types'; export { ROLES, USERS } from './authentication'; export { createUsersAndRoles } from './create_users_and_roles'; diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts index b6dfc29bb1c6bc..1027a44cd8b91a 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts @@ -9,24 +9,28 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardAddPanel = getService('dashboardAddPanel'); const find = getService('find'); const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['header', 'common', 'dashboard', 'timePicker', 'lens']); // Dashboard shares a search session with lens when navigating to and from by value lens to hit search cache // https://github.com/elastic/kibana/issues/99310 describe('Search session sharing with lens', () => { before(async () => { - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); // NOTE: This test doesn't check if the cache was actually hit, but just checks if the same search session id is used diff --git a/x-pack/test/search_sessions_integration/tests/apps/lens/search_sessions.ts b/x-pack/test/search_sessions_integration/tests/apps/lens/search_sessions.ts index d95e117d1b0330..9690f9be99fc60 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/lens/search_sessions.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/lens/search_sessions.ts @@ -13,14 +13,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const searchSession = getService('searchSessions'); const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'timePicker', 'header']); const listingTable = getService('listingTable'); + const kibanaServer = getService('kibanaServer'); describe('lens search sessions', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); }); it("doesn't shows search sessions indicator UI", async () => { diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 4283b85af0c179..a111c327b1ac6a 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -117,7 +117,6 @@ export async function SecuritySolutionCypressUpgradeCliTestRunner({ getService, }: FtrProviderContext) { const log = getService('log'); - const config = getService('config'); await withProcRunner(log, async (procs) => { await procs.run('cypress', { @@ -126,10 +125,10 @@ export async function SecuritySolutionCypressUpgradeCliTestRunner({ cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', - CYPRESS_BASE_URL: Url.format(config.get('servers.kibana')), - CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), - CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), - CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + CYPRESS_BASE_URL: process.env.TEST_KIBANA_URL, + CYPRESS_ELASTICSEARCH_URL: process.env.TEST_ES_URL, + CYPRESS_ELASTICSEARCH_USERNAME: process.env.TEST_ES_USER, + CYPRESS_ELASTICSEARCH_PASSWORD: process.env.TEST_ES_PASS, ...process.env, }, wait: true, 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 323b08dd88be13..6ac54750c6ec8e 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 @@ -6,12 +6,19 @@ */ import expect from '@kbn/expect'; +import { DeepPartial } from 'utility-types'; +import { merge } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../services/endpoint_policy'; import { IndexedHostsAndAlertsResponse } from '../../../../plugins/security_solution/common/endpoint/index_data'; +import { FullAgentPolicyInput } from '../../../../plugins/fleet/common'; +import { PolicyConfig } from '../../../../plugins/security_solution/common/endpoint/types'; +import { ManifestSchema } from '../../../../plugins/security_solution/common/endpoint/schema/manifest'; +import { policyFactory } from '../../../../plugins/security_solution/common/endpoint/models/policy_config'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const browser = getService('browser'); + const retryService = getService('retry'); const pageObjects = getPageObjects([ 'common', 'endpoint', @@ -24,18 +31,224 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const policyTestResources = getService('policyTestResources'); const endpointTestResources = getService('endpointTestResources'); - // FLAKY https://github.com/elastic/kibana/issues/100296 - describe.skip('When on the Endpoint Policy Details Page', function () { + type FullAgentPolicyEndpointInput = Omit & { + policy: PolicyConfig; + artifact_manifest: ManifestSchema; + }; + + /** + * Returns the Fleet Agent Policy Input that represents an Endpoint Policy. Use it to + * validate expecte output when looking at the Fleet Agent policy to validate that updates + * to the Endpoint Policy are making it through to the overall Fleet Agent Policy + * + * @param overrides + */ + const getExpectedAgentPolicyEndpointInput = ( + overrides: DeepPartial = {} + ): FullAgentPolicyInput => { + return merge( + { + id: '123', + revision: 2, + data_stream: { namespace: 'default' }, + name: 'Protect East Coast', + meta: { + package: { + name: 'endpoint', + version: '1.0', + }, + }, + artifact_manifest: { + artifacts: { + 'endpoint-exceptionlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-exceptionlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-exceptionlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-hostisolationexceptionlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-hostisolationexceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-hostisolationexceptionlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-hostisolationexceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-hostisolationexceptionlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-hostisolationexceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + }, + manifest_version: '1', + schema_version: 'v1', + }, + policy: merge(policyFactory(), { + windows: { + popup: { + malware: { + message: 'Elastic Security {action} {filename}', + }, + ransomware: { + message: 'Elastic Security {action} {filename}', + }, + memory_protection: { + message: 'Elastic Security {action} {rule}', + }, + behavior_protection: { + message: 'Elastic Security {action} {rule}', + }, + }, + }, + mac: { + popup: { + malware: { + message: 'Elastic Security {action} {filename}', + }, + behavior_protection: { + message: 'Elastic Security {action} {rule}', + }, + memory_protection: { + message: 'Elastic Security {action} {rule}', + }, + }, + }, + linux: { + popup: { + malware: { + message: 'Elastic Security {action} {filename}', + }, + behavior_protection: { + message: 'Elastic Security {action} {rule}', + }, + memory_protection: { + message: 'Elastic Security {action} {rule}', + }, + }, + }, + }), + type: 'endpoint', + use_output: 'default', + }, + overrides + ); + }; + + describe('When on the Endpoint Policy Details Page', function () { let indexedData: IndexedHostsAndAlertsResponse; + before(async () => { const endpointPackage = await policyTestResources.getEndpointPackage(); await endpointTestResources.setMetadataTransformFrequency('1s', endpointPackage.version); indexedData = await endpointTestResources.loadEndpointData(); await browser.refresh(); }); + after(async () => { await endpointTestResources.unloadEndpointData(indexedData); }); + describe('with an invalid policy id', () => { it('should display an error', async () => { await pageObjects.policy.navigateToPolicyDetails('invalid-id'); @@ -69,13 +282,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('and the show advanced settings button is clicked', async () => { await testSubjects.missingOrFail('advancedPolicyPanel'); - let advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); - + // Expand + await pageObjects.policy.showAdvancedSettingsSection(); await testSubjects.existOrFail('advancedPolicyPanel'); - advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); + // Collapse + await pageObjects.policy.hideAdvancedSettingsSection(); await testSubjects.missingOrFail('advancedPolicyPanel'); }); }); @@ -99,11 +311,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(await testSubjects.isChecked('malwareUserNotificationCheckbox')).to.be(true); await testSubjects.existOrFail('malwareUserNotificationCustomMessage'); }); + it('should not show the custom message text area when the Notify User checkbox is unchecked', async () => { await pageObjects.endpointPageUtils.clickOnEuiCheckbox('malwareUserNotificationCheckbox'); expect(await testSubjects.isChecked('malwareUserNotificationCheckbox')).to.be(false); await testSubjects.missingOrFail('malwareUserNotificationCustomMessage'); }); + it('should preserve a custom notification message upon saving', async () => { const customMessage = await testSubjects.find('malwareUserNotificationCustomMessage'); await customMessage.clearValue(); @@ -139,11 +353,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { `Integration ${policyInfo.packagePolicy.name} has been updated.` ); }); + it('should persist update on the screen', async () => { await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_process'); await pageObjects.policy.confirmAndSave(); await testSubjects.existOrFail('policyDetailsSuccessMessage'); + await pageObjects.common.closeToast(); await pageObjects.endpoint.navigateToEndpointList(); await pageObjects.policy.navigateToPolicyDetails(policyInfo.packagePolicy.id); @@ -151,6 +367,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { false ); }); + it('should have updated policy data in overall Agent Policy', async () => { // This test ensures that updates made to the Endpoint Policy are carried all the way through // to the generated Agent Policy that is dispatch down to the Elastic Agent. @@ -161,8 +378,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyMacEvent_file'), ]); - const advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); + await pageObjects.policy.showAdvancedSettingsSection(); const advancedPolicyField = await pageObjects.policy.findAdvancedPolicyField(); await advancedPolicyField.clearValue(); @@ -177,226 +393,38 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(agentFullPolicy.inputs).to.eql([ - { + getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, - revision: 2, - data_stream: { namespace: 'default' }, - name: 'Protect East Coast', meta: { package: { - name: 'endpoint', version: policyInfo.packageInfo.version, }, }, artifact_manifest: { - artifacts: { - 'endpoint-exceptionlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - }, - // The manifest version could have changed when the Policy was updated because the - // policy details page ensures that a save action applies the udpated policy on top - // of the latest Package Policy. So we just ignore the check against this value by - // forcing it to be the same as the value returned in the full agent policy. manifest_version: agentFullPolicy.inputs[0].artifact_manifest.manifest_version, - schema_version: 'v1', }, policy: { linux: { - events: { file: false, network: true, process: true }, - logging: { file: 'info' }, - advanced: { agent: { connection_delay: 'true' } }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - mac: { - events: { file: false, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - windows: { events: { - dll_and_driver_load: true, - dns: true, file: false, - network: true, - process: true, - registry: true, - security: true, }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - memory_protection: { mode: 'prevent', supported: true }, - behavior_protection: { mode: 'prevent', supported: true }, - ransomware: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', + advanced: { + agent: { + connection_delay: 'true', }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - }, - antivirus_registration: { - enabled: false, }, }, + mac: { + events: { file: false }, + }, + windows: { events: { file: false } }, }, - type: 'endpoint', - use_output: 'default', - }, + }), ]); }); it('should have cleared the advanced section when the user deletes the value', async () => { - const advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); + await pageObjects.policy.showAdvancedSettingsSection(); const advancedPolicyField = await pageObjects.policy.findAdvancedPolicyField(); await advancedPolicyField.clearValue(); @@ -411,220 +439,26 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(agentFullPolicy.inputs).to.eql([ - { + getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, - revision: 2, - data_stream: { namespace: 'default' }, - name: 'Protect East Coast', meta: { package: { - name: 'endpoint', version: policyInfo.packageInfo.version, }, }, artifact_manifest: { - artifacts: { - 'endpoint-exceptionlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - }, - // The manifest version could have changed when the Policy was updated because the - // policy details page ensures that a save action applies the udpated policy on top - // of the latest Package Policy. So we just ignore the check against this value by - // forcing it to be the same as the value returned in the full agent policy. manifest_version: agentFullPolicy.inputs[0].artifact_manifest.manifest_version, - schema_version: 'v1', }, policy: { linux: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - advanced: { agent: { connection_delay: 'true' } }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', + advanced: { + agent: { + connection_delay: 'true', }, }, }, - mac: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - windows: { - events: { - dll_and_driver_load: true, - dns: true, - file: true, - network: true, - process: true, - registry: true, - security: true, - }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - memory_protection: { mode: 'prevent', supported: true }, - behavior_protection: { mode: 'prevent', supported: true }, - ransomware: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - }, - antivirus_registration: { - enabled: false, - }, - }, }, - type: 'endpoint', - use_output: 'default', - }, + }), ]); // Clear the value @@ -643,225 +477,25 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(agentFullPolicyUpdated.inputs).to.eql([ - { + getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, revision: 3, - data_stream: { namespace: 'default' }, - name: 'Protect East Coast', meta: { package: { - name: 'endpoint', version: policyInfo.packageInfo.version, }, }, artifact_manifest: { - artifacts: { - 'endpoint-exceptionlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - }, - // The manifest version could have changed when the Policy was updated because the - // policy details page ensures that a save action applies the udpated policy on top - // of the latest Package Policy. So we just ignore the check against this value by - // forcing it to be the same as the value returned in the full agent policy. manifest_version: agentFullPolicy.inputs[0].artifact_manifest.manifest_version, - schema_version: 'v1', }, - policy: { - linux: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - mac: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - windows: { - events: { - dll_and_driver_load: true, - dns: true, - file: true, - network: true, - process: true, - registry: true, - security: true, - }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - memory_protection: { mode: 'prevent', supported: true }, - behavior_protection: { mode: 'prevent', supported: true }, - ransomware: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - }, - antivirus_registration: { - enabled: false, - }, - }, - }, - type: 'endpoint', - use_output: 'default', - }, + }), ]); }); }); describe('when on Ingest Policy Edit Package Policy page', async () => { let policyInfo: PolicyTestResourceInfo; + beforeEach(async () => { // Create a policy and navigate to Ingest app policyInfo = await policyTestResources.createPolicy(); @@ -871,6 +505,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await testSubjects.existOrFail('endpointIntegrationPolicyForm'); }); + afterEach(async () => { if (policyInfo) { await policyInfo.cleanup(); @@ -888,10 +523,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(await winDnsEventingCheckbox.isSelected()).to.be(true); await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns'); - expect(await winDnsEventingCheckbox.isSelected()).to.be(false); + await pageObjects.policy.waitForCheckboxSelectionChange('policyWindowsEvent_dns', false); }); it('should preserve updates done from the Fleet form', async () => { + // Fleet has its own form inputs, like description. When those are updated, the changes + // are also dispatched to the embedded endpoint Policy form. Update to the Endpoint Policy + // form after that should preserve the changes done on the Fleet form + + // Wait for the endpoint form to load and then update the policy description + await testSubjects.existOrFail('endpointIntegrationPolicyForm'); await pageObjects.ingestManagerCreatePackagePolicy.setPackagePolicyDescription( 'protect everything' ); @@ -902,18 +543,22 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns'); - expect( - await pageObjects.ingestManagerCreatePackagePolicy.getPackagePolicyDescriptionValue() - ).to.be('protect everything'); + await retryService.try(async () => { + expect( + await pageObjects.ingestManagerCreatePackagePolicy.getPackagePolicyDescriptionValue() + ).to.be('protect everything'); + }); }); it('should include updated endpoint data when saved', async () => { - const winDnsEventingCheckbox = await testSubjects.find('policyWindowsEvent_dns'); await pageObjects.ingestManagerCreatePackagePolicy.scrollToCenterOfWindow( - winDnsEventingCheckbox + await testSubjects.find('policyWindowsEvent_dns') ); await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns'); - const wasSelected = await winDnsEventingCheckbox.isSelected(); + const updatedCheckboxValue = await testSubjects.isSelected('policyWindowsEvent_dns'); + + await pageObjects.policy.waitForCheckboxSelectionChange('policyWindowsEvent_dns', false); + await (await pageObjects.ingestManagerCreatePackagePolicy.findSaveButton(true)).click(); await pageObjects.ingestManagerCreatePackagePolicy.waitForSaveSuccessNotification(true); @@ -921,7 +566,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { policyInfo.agentPolicy.id, policyInfo.packagePolicy.id ); - expect(await testSubjects.isSelected('policyWindowsEvent_dns')).to.be(wasSelected); + + await pageObjects.policy.waitForCheckboxSelectionChange( + 'policyWindowsEvent_dns', + updatedCheckboxValue + ); }); it('should show trusted apps card and link should go back to policy', async () => { diff --git a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts index d1a037a47ff08f..b5eccd0ef11472 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts @@ -5,11 +5,13 @@ * 2.0. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'header']); const testSubjects = getService('testSubjects'); + const retryService = getService('retry'); return { /** @@ -49,6 +51,34 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr return await testSubjects.find('advancedPolicyButton'); }, + async isAdvancedSettingsExpanded() { + return await testSubjects.exists('advancedPolicyPanel'); + }, + + /** + * shows the advanced settings section and scrolls it into view + */ + async showAdvancedSettingsSection() { + if (!(await this.isAdvancedSettingsExpanded())) { + const expandButton = await this.findAdvancedPolicyButton(); + await expandButton.click(); + } + + await testSubjects.existOrFail('advancedPolicyPanel'); + await testSubjects.scrollIntoView('advancedPolicyPanel'); + }, + + /** + * Hides the advanced settings section + */ + async hideAdvancedSettingsSection() { + if (await this.isAdvancedSettingsExpanded()) { + const expandButton = await this.findAdvancedPolicyButton(); + await expandButton.click(); + } + await testSubjects.missingOrFail('advancedPolicyPanel'); + }, + /** * Finds and returns the linux connection_delay Advanced Policy field */ @@ -69,7 +99,17 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr */ async confirmAndSave() { await this.ensureIsOnDetailsPage(); - await (await this.findSaveButton()).click(); + + const saveButton = await this.findSaveButton(); + + // Sometimes, data retrieval errors may have been encountered by other security solution processes + // (ex. index fields search here: `x-pack/plugins/security_solution/public/common/containers/source/index.tsx:181`) + // which are displayed using one or more Toast messages. This in turn prevents the user from + // actually clicking the Save button. Because those errors are not associated with Policy details, + // we'll first check that all toasts are cleared + await pageObjects.common.clearAllToasts(); + + await saveButton.click(); await testSubjects.existOrFail('policyDetailsConfirmModal'); await pageObjects.common.clickConfirmOnModal(); }, @@ -93,5 +133,19 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr async findPackagePolicyEndpointCustomConfiguration(onEditPage: boolean = false) { return await testSubjects.find(`endpointPackagePolicy_${onEditPage ? 'edit' : 'create'}`); }, + + /** + * Waits for a Checkbox/Radiobutton to have its `isSelected()` value match the provided expected value + * @param selector + * @param expectedSelectedValue + */ + async waitForCheckboxSelectionChange( + selector: string, + expectedSelectedValue: boolean + ): Promise { + await retryService.try(async () => { + expect(await testSubjects.isSelected(selector)).to.be(expectedSelectedValue); + }); + }, }; } diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index c9b09456a9a492..c5dc147b45123c 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -582,6 +582,94 @@ } } +{ + "type": "doc", + "value": { + "id": "sharedtype:alias_delete_inclusive", + "index": ".kibana", + "source": { + "sharedtype": { + "title": "This is used to test that when an object is unshared from a space, inbound aliases for just those spaces are removed" + }, + "type": "sharedtype", + "namespaces": ["default", "space_1", "space_2"], + "updated_at": "2017-09-21T18:59:16.270Z" + }, + "type": "doc" + } +} + +{ + "type": "doc", + "value": { + "id": "legacy-url-alias:space_1:sharedtype:doesnt-matter", + "index": ".kibana", + "source": { + "type": "legacy-url-alias", + "updated_at": "2017-09-21T18:51:23.794Z", + "legacy-url-alias": { + "sourceId": "doesnt-matter", + "targetNamespace": "space_1", + "targetType": "sharedtype", + "targetId": "alias_delete_inclusive" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "legacy-url-alias:space_2:sharedtype:doesnt-matter", + "index": ".kibana", + "source": { + "type": "legacy-url-alias", + "updated_at": "2017-09-21T18:51:23.794Z", + "legacy-url-alias": { + "sourceId": "doesnt-matter", + "targetNamespace": "space_2", + "targetType": "sharedtype", + "targetId": "alias_delete_inclusive" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "sharedtype:alias_delete_exclusive", + "index": ".kibana", + "source": { + "sharedtype": { + "title": "This is used to test that when an object is unshared from all space, inbound aliases for all spaces are removed" + }, + "type": "sharedtype", + "namespaces": ["*"], + "updated_at": "2017-09-21T18:59:16.270Z" + }, + "type": "doc" + } +} + +{ + "type": "doc", + "value": { + "id": "legacy-url-alias:other_space:sharedtype:doesnt-matter", + "index": ".kibana", + "source": { + "type": "legacy-url-alias", + "updated_at": "2017-09-21T18:51:23.794Z", + "legacy-url-alias": { + "sourceId": "doesnt-matter", + "targetNamespace": "other_space", + "targetType": "sharedtype", + "targetId": "alias_delete_exclusive" + } + } + } +} + { "type": "doc", "value": { diff --git a/x-pack/test/spaces_api_integration/common/lib/saved_object_test_cases.ts b/x-pack/test/spaces_api_integration/common/lib/saved_object_test_cases.ts index ddbcf8f5f31c17..8d9af1170f288f 100644 --- a/x-pack/test/spaces_api_integration/common/lib/saved_object_test_cases.ts +++ b/x-pack/test/spaces_api_integration/common/lib/saved_object_test_cases.ts @@ -38,6 +38,14 @@ export const MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES = Object.freeze({ id: 'all_spaces', existingNamespaces: ['*'], // all current and future spaces }), + ALIAS_DELETE_INCLUSIVE: Object.freeze({ + id: 'alias_delete_inclusive', + existingNamespaces: ['default', 'space_1', 'space_2'], // each individual space + }), + ALIAS_DELETE_EXCLUSIVE: Object.freeze({ + id: 'alias_delete_exclusive', + existingNamespaces: ['*'], // all current and future spaces + }), DOES_NOT_EXIST: Object.freeze({ id: 'does_not_exist', existingNamespaces: [] as string[], diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index e0f222af707c54..ae8b73535c2c65 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -53,8 +53,8 @@ export function deleteTestSuiteFactory(es: Client, esArchiver: any, supertest: S // @ts-expect-error @elastic/elasticsearch doesn't defined `count.buckets`. const buckets = response.aggregations?.count.buckets; - // The test fixture contains three legacy URL aliases: - // (1) one for "space_1", (2) one for "space_2", and (3) one for "other_space", which is a non-existent space. + // The test fixture contains six legacy URL aliases: + // (1) two for "space_1", (2) two for "space_2", and (3) two for "other_space", which is a non-existent space. // Each test deletes "space_2", so the agg buckets should reflect that aliases (1) and (3) still exist afterwards. // Space 2 deleted, all others should exist @@ -75,26 +75,26 @@ export function deleteTestSuiteFactory(es: Client, esArchiver: any, supertest: S }, }, { - doc_count: 6, + doc_count: 7, key: 'space_1', countByType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { key: 'visualization', doc_count: 3 }, + { key: 'legacy-url-alias', doc_count: 2 }, // aliases (1) { key: 'dashboard', doc_count: 1 }, { key: 'index-pattern', doc_count: 1 }, - { key: 'legacy-url-alias', doc_count: 1 }, // alias (1) ], }, }, { - doc_count: 1, + doc_count: 2, key: 'other_space', countByType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, - buckets: [{ key: 'legacy-url-alias', doc_count: 1 }], // alias (3) + buckets: [{ key: 'legacy-url-alias', doc_count: 2 }], // aliases (3) }, }, ]; @@ -110,8 +110,8 @@ export function deleteTestSuiteFactory(es: Client, esArchiver: any, supertest: S body: { query: { terms: { type: ['sharedtype'] } } }, }); const docs = multiNamespaceResponse.hits.hits; - // Just 12 results, since spaces_2_only, conflict_1_space_2 and conflict_2_space_2 got deleted. - expect(docs).length(12); + // Just 14 results, since spaces_2_only, conflict_1_space_2 and conflict_2_space_2 got deleted. + expect(docs).length(14); docs.forEach((doc) => () => { const containsSpace2 = doc?._source?.namespaces.includes('space_2'); expect(containsSpace2).to.eql(false); diff --git a/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts b/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts index ecd0d15b522e17..3b795ae719db8d 100644 --- a/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts +++ b/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts @@ -6,6 +6,8 @@ */ import expect from '@kbn/expect'; +import type { Client } from '@elastic/elasticsearch'; +import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; import { without, uniq } from 'lodash'; import { SuperTest } from 'supertest'; import { @@ -35,6 +37,7 @@ export interface UpdateObjectsSpacesTestCase { objects: Array<{ id: string; existingNamespaces: string[]; + expectAliasDifference?: number; failure?: 400 | 404; }>; spacesToAdd: string[]; @@ -54,7 +57,11 @@ const getTestTitle = ({ objects, spacesToAdd, spacesToRemove }: UpdateObjectsSpa return `{objects: [${objStr}], spacesToAdd: [${addStr}], spacesToRemove: [${remStr}]}`; }; -export function updateObjectsSpacesTestSuiteFactory(esArchiver: any, supertest: SuperTest) { +export function updateObjectsSpacesTestSuiteFactory( + es: Client, + esArchiver: any, + supertest: SuperTest +) { const expectForbidden = expectResponses.forbiddenTypes('share_to_space'); const expectResponseBody = ( @@ -68,7 +75,10 @@ export function updateObjectsSpacesTestSuiteFactory(esArchiver: any, supertest: } else { const { objects, spacesToAdd, spacesToRemove } = testCase; const apiResponse = response.body as SavedObjectsUpdateObjectsSpacesResponse; - objects.forEach(({ id, existingNamespaces, failure }, i) => { + + let hasRefreshed = false; + for (let i = 0; i < objects.length; i++) { + const { id, existingNamespaces, expectAliasDifference, failure } = objects[i]; const object = apiResponse.objects[i]; if (failure === 404) { const error = SavedObjectsErrorHelpers.createGenericNotFoundError(TYPE, id); @@ -84,8 +94,28 @@ export function updateObjectsSpacesTestSuiteFactory(esArchiver: any, supertest: expect(result.type).to.eql(TYPE); expect(result.id).to.eql(id); expect(result.spaces.sort()).to.eql(expectedSpaces.sort()); + + if (expectAliasDifference !== undefined) { + // if we deleted an object that had an alias pointing to it, the alias should have been deleted as well + if (!hasRefreshed) { + await es.indices.refresh({ index: '.kibana' }); // alias deletion uses refresh: false, so we need to manually refresh the index before searching + hasRefreshed = true; + } + const searchResponse = await es.search({ + index: '.kibana', + body: { + size: 0, + query: { terms: { type: ['legacy-url-alias'] } }, + track_total_hits: true, + }, + }); + expect((searchResponse.hits.total as SearchTotalHits).value).to.eql( + // Six aliases exist in the test fixtures + 6 + expectAliasDifference + ); + } } - }); + } } }; const createTestDefinitions = ( diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts index 36f50aa165e72d..c6a97337e6ad93 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts @@ -28,6 +28,8 @@ const { fail404 } = testCaseFailures; const createTestCases = (spaceId: string): UpdateObjectsSpacesTestCase[] => { const eachSpace = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]; + // Note: we intentionally exclude ALIAS_DELETION test cases because they are already covered in spaces_only test suite, and there is no + // authZ-specific logic that affects alias deletion, all of that happens at the Saved Objects Repository level. return [ // Test case to check adding and removing all spaces ("*") to a saved object { @@ -125,8 +127,10 @@ const calculateSingleSpaceAuthZ = (testCases: UpdateObjectsSpacesTestCase[], spa export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); + const es = getService('es'); const { addTests, createTestDefinitions } = updateObjectsSpacesTestSuiteFactory( + es, esArchiver, supertest ); diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts index 865d5eca22cbd8..fc95f513f55195 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts @@ -11,6 +11,7 @@ import { getTestScenarios, } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../../common/lib/saved_object_test_cases'; +import type { UpdateObjectsSpacesTestCase } from '../../common/suites/update_objects_spaces'; import { updateObjectsSpacesTestSuiteFactory } from '../../common/suites/update_objects_spaces'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -51,7 +52,55 @@ const createSinglePartTestCases = (spaceId: string) => { const createMultiPartTestCases = () => { const nonExistentSpace = 'does_not_exist'; // space that doesn't exist const eachSpace = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]; - const group1 = [ + const group1: UpdateObjectsSpacesTestCase[] = [ + // These test cases ensure that aliases are deleted when objects are unshared. + // For simplicity these are done separately, before the others. + { + objects: [ + { + id: CASES.ALIAS_DELETE_INCLUSIVE.id, + existingNamespaces: eachSpace, + expectAliasDifference: -1, // one alias should have been deleted from space_2 + }, + ], + spacesToAdd: [], + spacesToRemove: [SPACE_2_ID], + }, + { + objects: [ + { + id: CASES.ALIAS_DELETE_INCLUSIVE.id, + existingNamespaces: [DEFAULT_SPACE_ID, SPACE_1_ID], + expectAliasDifference: -2, // one alias should have been deleted from space_1 + }, + ], + spacesToAdd: [], + spacesToRemove: [SPACE_1_ID], + }, + { + objects: [ + { + id: CASES.ALIAS_DELETE_INCLUSIVE.id, + existingNamespaces: [DEFAULT_SPACE_ID], + expectAliasDifference: -2, // no aliases can exist in the default space, so no aliases were deleted + }, + ], + spacesToAdd: [], + spacesToRemove: [DEFAULT_SPACE_ID], + }, + { + objects: [ + { + id: CASES.ALIAS_DELETE_EXCLUSIVE.id, + existingNamespaces: [SPACE_1_ID], + expectAliasDifference: -3, // one alias should have been deleted from other_space + }, + ], + spacesToAdd: [SPACE_1_ID], + spacesToRemove: ['*'], + }, + ]; + const group2 = [ // first, add this object to each space and remove it from nonExistentSpace // this will succeed even though the object already exists in the default space and it doesn't exist in nonExistentSpace { objects: [CASES.DEFAULT_ONLY], spacesToAdd: eachSpace, spacesToRemove: [nonExistentSpace] }, @@ -87,7 +136,7 @@ const createMultiPartTestCases = () => { spacesToRemove: [SPACE_1_ID], }, ]; - const group2 = [ + const group3 = [ // first, add this object to space_2 and remove it from space_1 { objects: [CASES.DEFAULT_AND_SPACE_1], @@ -111,15 +160,17 @@ const createMultiPartTestCases = () => { spacesToRemove: [], }, ]; - return [...group1, ...group2]; + return [...group1, ...group2, ...group3]; }; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const es = getService('es'); const { addTests, createTestDefinitions } = updateObjectsSpacesTestSuiteFactory( + es, esArchiver, supertest ); diff --git a/x-pack/test/stack_functional_integration/apps/apm/apm_smoke_test.js b/x-pack/test/stack_functional_integration/apps/apm/apm_smoke_test.js index c7809e6abbf4ab..6a653cc95921aa 100644 --- a/x-pack/test/stack_functional_integration/apps/apm/apm_smoke_test.js +++ b/x-pack/test/stack_functional_integration/apps/apm/apm_smoke_test.js @@ -9,20 +9,32 @@ export default function ({ getService, getPageObjects }) { describe('APM smoke test', function ampsmokeTest() { const browser = getService('browser'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'timePicker']); + const PageObjects = getPageObjects(['common', 'timePicker', 'header']); const find = getService('find'); const log = getService('log'); + const retry = getService('retry'); before(async () => { await browser.setWindowSize(1400, 1400); await PageObjects.common.navigateToApp('apm'); + await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); + await PageObjects.header.waitUntilLoadingHasFinished(); }); it('can navigate to APM app', async () => { await testSubjects.existOrFail('apmMainContainer', { timeout: 10000, }); + // wait for this last change on the page; + // This table contains 1 rows out of 1 rows; Page 1 of 1. + // but "" always exists so we have to wait until there's text + await retry.waitForWithTimeout('The APM table has a caption', 5000, async () => { + return (await (await find.byCssSelector('caption')).getAttribute('innerHTML')).includes( + 'This table contains ' + ); + }); + await find.clickByDisplayedLinkText('apm-a-rum-test-e2e-general-usecase'); log.debug('### apm smoke test passed'); await find.clickByLinkText('general-usecase-initial-p-load'); diff --git a/x-pack/test/timeline/common/config.ts b/x-pack/test/timeline/common/config.ts index 211f380b133a55..75f0eb24a6cd9a 100644 --- a/x-pack/test/timeline/common/config.ts +++ b/x-pack/test/timeline/common/config.ts @@ -7,6 +7,7 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrConfigProviderContext } from '@kbn/test'; +import { resolve } from 'path'; import { services } from './services'; import { getAllExternalServiceSimulatorPaths } from '../../alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin'; @@ -40,6 +41,7 @@ const enabledActionTypes = [ export function createTestConfig(name: string, options: CreateTestConfigOptions) { const { license = 'trial', disabledPlugins = [], ssl = false, testFiles = [] } = options; + const auditLogPath = resolve(__dirname, './audit.log'); return async ({ readConfigFile }: FtrConfigProviderContext) => { const xPackApiIntegrationTestsConfig = await readConfigFile( @@ -85,6 +87,10 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) // TO DO: Remove feature flags once we're good to go '--xpack.securitySolution.enableExperimental=["ruleRegistryEnabled"]', '--xpack.ruleRegistry.write.enabled=true', + '--xpack.security.audit.enabled=true', + '--xpack.security.audit.appender.type=file', + `--xpack.security.audit.appender.fileName=${auditLogPath}`, + '--xpack.security.audit.appender.layout.type=json', `--server.xsrf.allowlist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, ...(ssl ? [ diff --git a/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts b/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts index 91ad87737805f7..a9ba7bd908a9e4 100644 --- a/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts +++ b/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts @@ -5,9 +5,11 @@ * 2.0. */ +import Path from 'path'; +import Fs from 'fs'; import { JsonObject } from '@kbn/utility-types'; import expect from '@kbn/expect'; -import { ALERT_UUID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils'; +import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils'; import { User } from '../../../../rule_registry/common/lib/authentication/types'; import { TimelineEdges, TimelineNonEcsData } from '../../../../../plugins/timelines/common/'; @@ -18,6 +20,7 @@ import { obsMinReadAlertsReadSpacesAll, obsMinRead, obsMinReadSpacesAll, + superUser, } from '../../../../rule_registry/common/lib/authentication/users'; import { Direction, @@ -25,6 +28,28 @@ import { } from '../../../../../plugins/security_solution/common/search_strategy'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; +class FileWrapper { + constructor(private readonly path: string) {} + async reset() { + // "touch" each file to ensure it exists and is empty before each test + await Fs.promises.writeFile(this.path, ''); + } + async read() { + const content = await Fs.promises.readFile(this.path, { encoding: 'utf8' }); + return content.trim().split('\n'); + } + async readJSON() { + const content = await this.read(); + return content.map((l) => JSON.parse(l)); + } + // writing in a file is an async operation. we use this method to make sure logs have been written. + async isNotEmpty() { + const content = await this.read(); + const line = content[0]; + return line.length > 0; + } +} + interface TestCase { /** The space where the alert exists */ space?: string; @@ -44,6 +69,7 @@ const TO = '3000-01-01T00:00:00.000Z'; const FROM = '2000-01-01T00:00:00.000Z'; const TEST_URL = '/internal/search/timelineSearchStrategy/'; const SPACE_1 = 'space1'; +const SPACE_2 = 'space2'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { @@ -56,18 +82,9 @@ export default ({ getService }: FtrProviderContext) => { { field: '@timestamp', }, - { - field: ALERT_RULE_CONSUMER, - }, - { - field: ALERT_UUID, - }, - { - field: 'event.kind', - }, ], factoryQueryType: TimelineEventsQueries.all, - fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_UUID, 'event.kind'], + fieldRequested: ['@timestamp'], fields: [], filterQuery: { bool: { @@ -98,6 +115,10 @@ export default ({ getService }: FtrProviderContext) => { }); describe('Timeline - Events', () => { + const logFilePath = Path.resolve(__dirname, '../../../common/audit.log'); + const logFile = new FileWrapper(logFilePath); + const retry = getService('retry'); + before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts'); }); @@ -162,14 +183,15 @@ export default ({ getService }: FtrProviderContext) => { }); } - describe('alerts authentication', () => { + // TODO - tests need to be updated with new table logic + describe.skip('alerts authentication', () => { addTests({ space: SPACE_1, featureIds: ['apm'], expectedNumberAlerts: 2, body: { ...getPostBody(), - defaultIndex: ['.alerts-*'], + defaultIndex: ['.alerts*'], entityType: 'alerts', alertConsumers: ['apm'], }, @@ -177,5 +199,81 @@ export default ({ getService }: FtrProviderContext) => { unauthorizedUsers: [obsMinRead, obsMinReadSpacesAll], }); }); + + // FLAKY: https://github.com/elastic/kibana/issues/117462 + describe.skip('logging', () => { + beforeEach(async () => { + await logFile.reset(); + }); + + afterEach(async () => { + await logFile.reset(); + }); + + it('logs success events when reading alerts', async () => { + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE_1)}${TEST_URL}`) + .auth(superUser.username, superUser.password) + .set('kbn-xsrf', 'true') + .set('Content-Type', 'application/json') + .send({ + ...getPostBody(), + defaultIndex: ['.alerts-*'], + entityType: 'alerts', + alertConsumers: ['apm'], + }) + .expect(200); + await retry.waitFor('logs event in the dest file', async () => await logFile.isNotEmpty()); + + const content = await logFile.readJSON(); + + const httpEvent = content.find((c) => c.event.action === 'http_request'); + expect(httpEvent).to.be.ok(); + expect(httpEvent.trace.id).to.be.ok(); + expect(httpEvent.user.name).to.be(superUser.username); + expect(httpEvent.kibana.space_id).to.be('space1'); + expect(httpEvent.http.request.method).to.be('post'); + expect(httpEvent.url.path).to.be('/s/space1/internal/search/timelineSearchStrategy/'); + + const findEvents = content.filter((c) => c.event.action === 'alert_find'); + expect(findEvents[0].trace.id).to.be.ok(); + expect(findEvents[0].event.outcome).to.be('success'); + expect(findEvents[0].user.name).to.be(superUser.username); + expect(findEvents[0].kibana.space_id).to.be('space1'); + }); + + it('logs failure events when unauthorized to read alerts', async () => { + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE_2)}${TEST_URL}`) + .auth(obsMinRead.username, obsMinRead.password) + .set('kbn-xsrf', 'true') + .set('Content-Type', 'application/json') + .send({ + ...getPostBody(), + defaultIndex: ['.alerts-*'], + entityType: 'alerts', + alertConsumers: ['apm'], + }) + .expect(500); + await retry.waitFor('logs event in the dest file', async () => await logFile.isNotEmpty()); + + const content = await logFile.readJSON(); + + const httpEvent = content.find((c) => c.event.action === 'http_request'); + expect(httpEvent).to.be.ok(); + expect(httpEvent.trace.id).to.be.ok(); + expect(httpEvent.user.name).to.be(obsMinRead.username); + expect(httpEvent.kibana.space_id).to.be(SPACE_2); + expect(httpEvent.http.request.method).to.be('post'); + expect(httpEvent.url.path).to.be('/s/space2/internal/search/timelineSearchStrategy/'); + + const findEvents = content.filter((c) => c.event.action === 'alert_find'); + expect(findEvents.length).to.equal(1); + expect(findEvents[0].trace.id).to.be.ok(); + expect(findEvents[0].event.outcome).to.be('failure'); + expect(findEvents[0].user.name).to.be(obsMinRead.username); + expect(findEvents[0].kibana.space_id).to.be(SPACE_2); + }); + }); }); }; diff --git a/yarn.lock b/yarn.lock index 56d5cf17791eaf..805235f9e2ddd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2337,10 +2337,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@38.1.0": - version "38.1.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-38.1.0.tgz#52146564c0e399da2267c10ec4536c33e50969e4" - integrity sha512-u2hQ8+daCvqapKQiFqN8QHWTz3OXby5Xf/ta1Dv59KTDTFIinCZD/M8PyD3MapbKx4b67hL7UxbErqr4rZ8jeA== +"@elastic/charts@38.1.3": + version "38.1.3" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-38.1.3.tgz#0bf27021c54176e87d38269613205f3d6219da96" + integrity sha512-p6bJQWmfJ5SLkcgAoAMB3eTah/2a3/r3uo3ZskEN/xdPiqU8P+GANF8+6F4dWNfejbrpSUyCUldl7S4nWFGg3Q== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" @@ -2411,10 +2411,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@40.0.0": - version "40.0.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-40.0.0.tgz#9556a87fa5eb7d9061e85f71ea9d3e6a9022dc3e" - integrity sha512-Zsz8eczEjthMgU00YhnsNmkKA8j4hxQpWNnrgecMgpcFEIj+Nn5WBofL/TJux/latS/mB4WWmrq4FTiSIyv/+Q== +"@elastic/eui@40.1.0": + version "40.1.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-40.1.0.tgz#d5ad63e9ed4ae482037a637e9b3f2598712c9e94" + integrity sha512-xlmbu4ZjdKtpgkQZ6GNMWCyyjSJaNFPMj97pLs11UEag2L1W0IwISlGF9+K45Qp4KLatR6iphwiyLFGmqGOOTA== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" @@ -2444,6 +2444,7 @@ rehype-raw "^5.0.0" rehype-react "^6.0.0" rehype-stringify "^8.0.0" + remark-breaks "^2.0.2" remark-emoji "^2.1.0" remark-parse "^8.0.3" remark-rehype "^8.0.0" @@ -2553,10 +2554,10 @@ history "^4.9.0" qs "^6.7.0" -"@elastic/synthetics@^1.0.0-beta.12": - version "1.0.0-beta.13" - resolved "https://registry.yarnpkg.com/@elastic/synthetics/-/synthetics-1.0.0-beta.13.tgz#84b3353b6bfff5623613016d8ed3d47e48ed17ea" - integrity sha512-CXpdfq/E6sVwDU6aGkH9mvcBPimQvR3/2QfBS5U4J58145m7YRPhJzaPJqXVApKomYcE/yzN49zOTIDsMcdOkg== +"@elastic/synthetics@^1.0.0-beta.16": + version "1.0.0-beta.16" + resolved "https://registry.yarnpkg.com/@elastic/synthetics/-/synthetics-1.0.0-beta.16.tgz#3d670cf29019e2be356592f2a7871594a3b0ce68" + integrity sha512-Ke8MO1lbddZjncPuY2IzZ1qKwePVD1hn9JtSUMv+7zJmczasrqcDKS2QCeFdt12kvFNe/IE60PStwfc9AD7j9w== dependencies: commander "^7.0.0" deepmerge "^4.2.2" @@ -2591,7 +2592,7 @@ dependencies: "@babel/plugin-syntax-jsx" "^7.2.0" -"@emotion/babel-plugin@^11.2.0": +"@emotion/babel-plugin@^11.0.0", "@emotion/babel-plugin@^11.2.0": version "11.2.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.2.0.tgz#f25c6df8ec045dad5ae6ca63df0791673b98c920" integrity sha512-lsnQBnl3l4wu/FJoyHnYRpHJeIPNkOBMbtDUIXcO8luulwRKZXPvA10zd2eXVN6dABIWNX4E34en/jkejIg/yA== @@ -2652,6 +2653,17 @@ "@emotion/weak-memoize" "^0.2.5" stylis "^4.0.3" +"@emotion/cache@^11.5.0": + version "11.5.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.5.0.tgz#a5eb78cbef8163939ee345e3ddf0af217b845e62" + integrity sha512-mAZ5QRpLriBtaj/k2qyrXwck6yeoz1V5lMt/jfj6igWU35yYlNKs2LziXVgvH81gnJZ+9QQNGelSsnuoAy6uIw== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.0.3" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "^4.0.10" + "@emotion/core@^10.0.9", "@emotion/core@^10.1.1": version "10.1.1" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3" @@ -2681,6 +2693,17 @@ "@emotion/utils" "0.11.3" babel-plugin-emotion "^10.0.27" +"@emotion/css@^11.4.0": + version "11.5.0" + resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.5.0.tgz#0a80017080cb44d47994fe576b9923bfc8b0f6ad" + integrity sha512-mqjz/3aqR9rp40M+pvwdKYWxlQK4Nj3cnNjo3Tx6SM14dSsEn7q/4W2/I7PlgG+mb27iITHugXuBIHH/QwUBVQ== + dependencies: + "@emotion/babel-plugin" "^11.0.0" + "@emotion/cache" "^11.5.0" + "@emotion/serialize" "^1.0.0" + "@emotion/sheet" "^1.0.3" + "@emotion/utils" "^1.0.0" + "@emotion/hash@0.8.0", "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" @@ -2779,6 +2802,11 @@ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698" integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g== +"@emotion/sheet@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.3.tgz#00c326cd7985c5ccb8fe2c1b592886579dcfab8f" + integrity sha512-YoX5GyQ4db7LpbmXHMuc8kebtBGP6nZfRC5Z13OKJMixBEwdZrJ914D6yJv/P+ZH/YY3F5s89NYX2hlZAf3SRQ== + "@emotion/styled-base@^10.0.27": version "10.0.31" resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" @@ -6409,6 +6437,11 @@ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== +"@types/js-levenshtein@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz#9541eec4ad6e3ec5633270a3a2b55d981edc44a9" + integrity sha512-14t0v1ICYRtRVcHASzes0v/O+TIeASb8aD55cWF1PidtInhFWSXcmhzhHqGjUWf9SUq1w70cvd1cWKUULubAfQ== + "@types/js-search@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@types/js-search/-/js-search-1.4.0.tgz#f2d4afa176a4fc7b17fb46a1593847887fa1fb7b" @@ -6428,15 +6461,10 @@ "@types/parse5" "*" "@types/tough-cookie" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.4": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" - integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7", "@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== "@types/json-stable-stringify@^1.0.32": version "1.0.32" @@ -6938,6 +6966,13 @@ "@types/prop-types" "*" "@types/react" "*" +"@types/react-vis@^1.11.9": + version "1.11.9" + resolved "https://registry.yarnpkg.com/@types/react-vis/-/react-vis-1.11.9.tgz#7d1534cf491d4563dd18c5ad0251d9ae66549323" + integrity sha512-n6sbTQuxpIzjf1FTIPhMdwBG0VNU96Oon7z3ASRSCnWT7ehL/zYJ/np0WAYFR/+c8OwNoSzny0OWlUmfvr/YCA== + dependencies: + "@types/react" "*" + "@types/react-window@^1.8.2": version "1.8.2" resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.2.tgz#a5a6b2762ce73ffaab7911ee1397cf645f2459fe" @@ -7349,20 +7384,33 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.2.tgz#9f41efaee32cdab7ace94b15bd19b756dd099b0a" - integrity sha512-w63SCQ4bIwWN/+3FxzpnWrDjQRXVEGiTt9tJTRptRXeFvdZc/wLiz3FQUwNQ2CVoRGI6KUWMNUj/pk63noUfcA== +"@typescript-eslint/eslint-plugin@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.2.0.tgz#2bdb247cc2e2afce7efbce09afb9a6f0a8a08434" + integrity sha512-qQwg7sqYkBF4CIQSyRQyqsYvP+g/J0To9ZPVNJpfxfekl5RmdvQnFFTVVwpRtaUDFNvjfe/34TgY/dpc3MgNTw== dependencies: - "@typescript-eslint/experimental-utils" "4.31.2" - "@typescript-eslint/scope-manager" "4.31.2" - debug "^4.3.1" + "@typescript-eslint/experimental-utils" "5.2.0" + "@typescript-eslint/scope-manager" "5.2.0" + debug "^4.3.2" functional-red-black-tree "^1.0.1" - regexpp "^3.1.0" + ignore "^5.1.8" + regexpp "^3.2.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.31.2", "@typescript-eslint/experimental-utils@^4.0.1": +"@typescript-eslint/experimental-utils@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.2.0.tgz#e3b2cb9cd0aff9b50f68d9a414c299fd26b067e6" + integrity sha512-fWyT3Agf7n7HuZZRpvUYdFYbPk3iDCq6fgu3ulia4c7yxmPnwVBovdSOX7RL+k8u6hLbrXcdAehlWUVpGh6IEw== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.2.0" + "@typescript-eslint/types" "5.2.0" + "@typescript-eslint/typescript-estree" "5.2.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/experimental-utils@^4.0.1": version "4.31.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.2.tgz#98727a9c1e977dd5d20c8705e69cd3c2a86553fa" integrity sha512-3tm2T4nyA970yQ6R3JZV9l0yilE2FedYg8dcXrTar34zC9r6JB7WyBQbpIVongKPlhEMjhQ01qkwrzWy38Bk1Q== @@ -7374,15 +7422,15 @@ eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@^4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.2.tgz#54aa75986e3302d91eff2bbbaa6ecfa8084e9c34" - integrity sha512-EcdO0E7M/sv23S/rLvenHkb58l3XhuSZzKf6DBvLgHqOYdL6YFMYVtreGFWirxaU2mS1GYDby3Lyxco7X5+Vjw== +"@typescript-eslint/parser@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.2.0.tgz#dc081aa89de16b5676b10215519af3aa7b58fb72" + integrity sha512-Uyy4TjJBlh3NuA8/4yIQptyJb95Qz5PX//6p8n7zG0QnN4o3NF9Je3JHbVU7fxf5ncSXTmnvMtd/LDQWDk0YqA== dependencies: - "@typescript-eslint/scope-manager" "4.31.2" - "@typescript-eslint/types" "4.31.2" - "@typescript-eslint/typescript-estree" "4.31.2" - debug "^4.3.1" + "@typescript-eslint/scope-manager" "5.2.0" + "@typescript-eslint/types" "5.2.0" + "@typescript-eslint/typescript-estree" "5.2.0" + debug "^4.3.2" "@typescript-eslint/scope-manager@4.31.2": version "4.31.2" @@ -7392,12 +7440,25 @@ "@typescript-eslint/types" "4.31.2" "@typescript-eslint/visitor-keys" "4.31.2" +"@typescript-eslint/scope-manager@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.2.0.tgz#7ce8e4ab2baaa0ad5282913ea8e13ce03ec6a12a" + integrity sha512-RW+wowZqPzQw8MUFltfKYZfKXqA2qgyi6oi/31J1zfXJRpOn6tCaZtd9b5u9ubnDG2n/EMvQLeZrsLNPpaUiFQ== + dependencies: + "@typescript-eslint/types" "5.2.0" + "@typescript-eslint/visitor-keys" "5.2.0" + "@typescript-eslint/types@4.31.2": version "4.31.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.2.tgz#2aea7177d6d744521a168ed4668eddbd912dfadf" integrity sha512-kWiTTBCTKEdBGrZKwFvOlGNcAsKGJSBc8xLvSjSppFO88AqGxGNYtF36EuEYG6XZ9vT0xX8RNiHbQUKglbSi1w== -"@typescript-eslint/typescript-estree@4.31.2", "@typescript-eslint/typescript-estree@^4.31.2": +"@typescript-eslint/types@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.2.0.tgz#7ad32d15abddb0ee968a330f0ea182ea544ef7cf" + integrity sha512-cTk6x08qqosps6sPyP2j7NxyFPlCNsJwSDasqPNjEQ8JMD5xxj2NHxcLin5AJQ8pAVwpQ8BMI3bTxR0zxmK9qQ== + +"@typescript-eslint/typescript-estree@4.31.2": version "4.31.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.2.tgz#abfd50594d8056b37e7428df3b2d185ef2d0060c" integrity sha512-ieBq8U9at6PvaC7/Z6oe8D3czeW5d//Fo1xkF/s9394VR0bg/UaMYPdARiWyKX+lLEjY3w/FNZJxitMsiWv+wA== @@ -7410,6 +7471,19 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@5.2.0", "@typescript-eslint/typescript-estree@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.2.0.tgz#c22e0ff6f8a4a3f78504a80ebd686fe2870a68ae" + integrity sha512-RsdXq2XmVgKbm9nLsE3mjNUM7BTr/K4DYR9WfFVMUuozHWtH5gMpiNZmtrMG8GR385EOSQ3kC9HiEMJWimxd/g== + dependencies: + "@typescript-eslint/types" "5.2.0" + "@typescript-eslint/visitor-keys" "5.2.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/visitor-keys@4.31.2": version "4.31.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.2.tgz#7d5b4a4705db7fe59ecffb273c1d082760f635cc" @@ -7418,6 +7492,14 @@ "@typescript-eslint/types" "4.31.2" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.2.0.tgz#03522d35df98474f08e0357171a7d1b259a88f55" + integrity sha512-Nk7HizaXWWCUBfLA/rPNKMzXzWS8Wg9qHMuGtT+v2/YpPij4nVXrVJc24N/r5WrrmqK31jCrZxeHqIgqRzs0Xg== + dependencies: + "@typescript-eslint/types" "5.2.0" + eslint-visitor-keys "^3.0.0" + "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" @@ -10273,36 +10355,24 @@ check-more-types@2.24.0, check-more-types@^2.24.0: resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= -cheerio-select@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.4.0.tgz#3a16f21e37a2ef0f211d6d1aa4eff054bb22cdc9" - integrity sha512-sobR3Yqz27L553Qa7cK6rtJlMDbiKPdNywtR95Sj/YgfpLfy0u6CGJuaBKe5YE/vTc23SCRKxWSdlon/w6I/Ew== +cheerio-select@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" + integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== dependencies: - css-select "^4.1.2" - css-what "^5.0.0" + css-select "^4.1.3" + css-what "^5.0.1" domelementtype "^2.2.0" domhandler "^4.2.0" - domutils "^2.6.0" + domutils "^2.7.0" -cheerio@^1.0.0-rc.3: - version "1.0.0-rc.3" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" - integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.1" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash "^4.15.0" - parse5 "^3.0.1" - -cheerio@^1.0.0-rc.9: - version "1.0.0-rc.9" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.9.tgz#a3ae6b7ce7af80675302ff836f628e7cb786a67f" - integrity sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng== - dependencies: - cheerio-select "^1.4.0" - dom-serializer "^1.3.1" +cheerio@^1.0.0-rc.10, cheerio@^1.0.0-rc.3: + version "1.0.0-rc.10" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" + integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== + dependencies: + cheerio-select "^1.5.0" + dom-serializer "^1.3.2" domhandler "^4.2.0" htmlparser2 "^6.1.0" parse5 "^6.0.1" @@ -11492,7 +11562,7 @@ css-select-base-adapter@^0.1.1: resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== -css-select@^1.1.0, css-select@~1.2.0: +css-select@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= @@ -11512,10 +11582,10 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" -css-select@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286" - integrity sha512-nu5ye2Hg/4ISq4XqdLY2bEatAcLIdt3OYGFc9Tm9n7VSlFBcfRv0gBNksHRgSdUDQGtN3XrZ94ztW+NfzkFSUw== +css-select@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" + integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== dependencies: boolbase "^1.0.0" css-what "^5.0.0" @@ -11558,10 +11628,10 @@ css-what@^3.2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== -css-what@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" - integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== +css-what@^5.0.0, css-what@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== css.escape@^1.5.1: version "1.5.1" @@ -11805,6 +11875,11 @@ cypress-cucumber-preprocessor@^2.5.2: minimist "^1.2.0" through "^2.3.8" +cypress-file-upload@^5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1" + integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== + cypress-multi-reporters@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.5.0.tgz#fff2758c082b49e8b91fed39f9650c70bc06de0d" @@ -12887,15 +12962,15 @@ dom-helpers@^5.0.0, dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^2.6.7" -dom-serializer@0, dom-serializer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" - integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== +dom-serializer@0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb" + integrity sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q== dependencies: - domelementtype "^1.3.0" - entities "^1.1.1" + domelementtype "^2.0.1" + entities "^2.0.0" -dom-serializer@^1.0.1, dom-serializer@^1.3.1: +dom-serializer@^1.0.1, dom-serializer@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== @@ -12914,7 +12989,7 @@ domain-browser@^1.1.1, domain-browser@^1.2.0: resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: +domelementtype@1, domelementtype@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== @@ -12982,10 +13057,10 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7" - integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA== +domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== dependencies: dom-serializer "^1.0.1" domelementtype "^2.2.0" @@ -13384,7 +13459,7 @@ enquirer@^2.3.5, enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" -entities@^1.1.1, entities@^1.1.2, entities@~1.1.1: +entities@^1.1.1, entities@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -13968,16 +14043,16 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint-visitor-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" - integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== - -eslint-visitor-keys@^2.1.0: +eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +eslint-visitor-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" + integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== + eslint@^7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -15665,10 +15740,10 @@ globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.1, globby@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" - integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== +globby@^11.0.1, globby@^11.0.3, globby@^11.0.4: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -16459,7 +16534,7 @@ htmlescape@^1.1.0: resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E= -htmlparser2@^3.10.0, htmlparser2@^3.9.1: +htmlparser2@^3.10.0: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -17295,10 +17370,10 @@ is-glob@^3.0.0, is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" @@ -19559,7 +19634,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@>4.17.4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.10, lodash@~4.17.15: +lodash@>4.17.4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.10, lodash@~4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -21321,9 +21396,9 @@ nth-check@^1.0.2, nth-check@~1.0.1: boolbase "~1.0.0" nth-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" - integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== dependencies: boolbase "^1.0.0" @@ -22162,13 +22237,6 @@ parse5@5.1.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parse5@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" - integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== - dependencies: - "@types/node" "*" - parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" @@ -24819,10 +24887,10 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0, regexp.prototype.f call-bind "^1.0.2" define-properties "^1.1.3" -regexpp@^3.0.0, regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +regexpp@^3.0.0, regexpp@^3.1.0, regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^4.7.1: version "4.7.1" @@ -24915,6 +24983,13 @@ release-zalgo@^1.0.0: dependencies: es6-error "^4.0.1" +remark-breaks@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/remark-breaks/-/remark-breaks-2.0.2.tgz#55fdec6c7da84f659aa7fdb1aa95b632870cee8d" + integrity sha512-LsQnPPQ7Fzp9RTjj4IwdEmjPOr9bxe9zYKWhs9ZQOg9hMg8rOfeeqQ410cvVdIK87Famqza1CKRxNkepp2EvUA== + dependencies: + unist-util-visit "^2.0.0" + remark-emoji@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.1.0.tgz#69165d1181b98a54ad5d9ef811003d53d7ebc7db" @@ -27235,6 +27310,11 @@ stylis@^3.5.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== +stylis@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" + integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg== + stylis@^4.0.3: version "4.0.7" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.7.tgz#412a90c28079417f3d27c028035095e4232d2904"
Overflow button @@ -1368,7 +1368,7 @@ exports[`AlertSummaryView Memory event code renders additional summary rows 1`]
Overflow button diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx index 327d8d963c75ed..74b366ffc1b3ea 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip } from '@elast import { isEmpty } from 'lodash'; import * as i18n from '../translations'; import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; -import { IndexPatternField } from '../../../../../../../../src/plugins/data/public'; +import { DataViewField } from '../../../../../../../../src/plugins/data_views/common'; import { getExampleText } from '../helpers'; import { BrowserField } from '../../../containers/source'; import { EventFieldsData } from '../types'; @@ -20,7 +20,7 @@ export interface FieldNameCellProps { data: EventFieldsData; field: string; fieldFromBrowserField: BrowserField; - fieldMapping?: IndexPatternField; + fieldMapping?: DataViewField; scripted?: boolean; } export const FieldNameCell = React.memo( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx index d703e736d8b61e..710cf5fceeb213 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx @@ -30,6 +30,7 @@ const hostIpData: EventFieldsData = { isObjectArray: false, name: 'host.ip', originalValue: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], + readFromDocValues: false, searchable: true, type: 'ip', values: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], @@ -101,6 +102,7 @@ describe('FieldValueCell', () => { isObjectArray: false, name: 'message', originalValue: ['Endpoint network event'], + readFromDocValues: false, searchable: true, type: 'string', values: ['Endpoint network event'], @@ -117,6 +119,7 @@ describe('FieldValueCell', () => { format: '', indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], name: 'message', + readFromDocValues: false, searchable: true, type: 'string', }; @@ -156,6 +159,7 @@ describe('FieldValueCell', () => { format: '', indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], name: 'host.ip', + readFromDocValues: false, searchable: true, type: 'ip', }; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 7b7a1ead5d702e..a611d140f61d13 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -16,8 +16,12 @@ import { mockEventViewerResponse, mockEventViewerResponseWithEvents } from './mo import { StatefulEventsViewer } from '.'; import { EventsViewer } from './events_viewer'; import { defaultHeaders } from './default_headers'; -import { useSourcererScope } from '../../containers/sourcerer'; -import { mockBrowserFields, mockDocValueFields } from '../../containers/source/mock'; +import { useSourcererDataView } from '../../containers/sourcerer'; +import { + mockBrowserFields, + mockDocValueFields, + mockRuntimeMappings, +} from '../../containers/source/mock'; import { eventsDefaultModel } from './default_model'; import { useMountAppended } from '../../utils/use_mount_appended'; import { inputsModel } from '../../store/inputs'; @@ -91,7 +95,7 @@ jest.mock('../../../timelines/containers', () => ({ jest.mock('../../components/url_state/normalize_time_range.ts'); -const mockUseSourcererScope: jest.Mock = useSourcererScope as jest.Mock; +const mockUseSourcererDataView: jest.Mock = useSourcererDataView as jest.Mock; jest.mock('../../containers/sourcerer'); const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; @@ -107,6 +111,7 @@ const to = '2019-08-27T22:10:56.794Z'; const defaultMocks = { browserFields: mockBrowserFields, docValueFields: mockDocValueFields, + runtimeMappings: mockRuntimeMappings, indexPattern: mockIndexPattern, loading: false, selectedPatterns: mockIndexNames, @@ -139,6 +144,7 @@ const eventsViewerDefaultProps = { }, renderCellValue: DefaultCellRenderer, rowRenderers: defaultRowRenderers, + runtimeMappings: {}, start: from, sort: [ { @@ -169,7 +175,7 @@ describe('EventsViewer', () => { mockUseTimelineEvents.mockReset(); }); beforeAll(() => { - mockUseSourcererScope.mockImplementation(() => defaultMocks); + mockUseSourcererDataView.mockImplementation(() => defaultMocks); }); describe('event details', () => { @@ -278,7 +284,7 @@ describe('EventsViewer', () => { describe('loading', () => { beforeAll(() => { - mockUseSourcererScope.mockImplementation(() => ({ ...defaultMocks, loading: true })); + mockUseSourcererDataView.mockImplementation(() => ({ ...defaultMocks, loading: true })); }); beforeEach(() => { mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index fd644d1380ddbc..5a3aa2e6dc38a6 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -11,6 +11,8 @@ import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; import { useDispatch } from 'react-redux'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Direction } from '../../../../common/search_strategy'; import { BrowserFields, DocValueFields } from '../../containers/source'; import { useTimelineEvents } from '../../../timelines/containers'; @@ -30,12 +32,7 @@ import { import { TimelineRefetch } from '../../../timelines/components/timeline/refetch_timeline'; import { EventDetailsWidthProvider } from './event_details_width_context'; import * as i18n from './translations'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../store'; import { ExitFullScreen } from '../exit_full_screen'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; @@ -123,7 +120,7 @@ interface Props { headerFilterGroup?: React.ReactNode; id: TimelineId; indexNames: string[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isLive: boolean; isLoadingIndexPattern: boolean; itemsPerPage: number; @@ -133,6 +130,7 @@ interface Props { onRuleChange?: () => void; renderCellValue: (props: CellValueElementProps) => React.ReactNode; rowRenderers: RowRenderer[]; + runtimeMappings: MappingRuntimeFields; start: string; sort: Sort[]; showTotalCount?: boolean; @@ -162,6 +160,7 @@ const EventsViewerComponent: React.FC = ({ query, renderCellValue, rowRenderers, + runtimeMappings, start, sort, showTotalCount = true, @@ -240,6 +239,7 @@ const EventsViewerComponent: React.FC = ({ id, indexNames, limit: itemsPerPage, + runtimeMappings, sort: sortField, startDate: start, endDate: end, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 1e61e69180f917..5be966ab649809 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -22,7 +22,7 @@ import { InspectButtonContainer } from '../inspect'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useSourcererScope } from '../../containers/sourcerer'; +import { useSourcererDataView } from '../../containers/sourcerer'; import type { EntityType } from '../../../../../timelines/common'; import { TGridCellAction } from '../../../../../timelines/common/types'; import { DetailsPanel } from '../../../timelines/components/side_panel'; @@ -117,9 +117,12 @@ const StatefulEventsViewerComponent: React.FC = ({ browserFields, docValueFields, indexPattern, + runtimeMappings, selectedPatterns, + dataViewId: selectedDataViewId, loading: isLoadingIndexPattern, - } = useSourcererScope(scopeId); + } = useSourcererDataView(scopeId); + const { globalFullScreen } = useGlobalFullScreen(); // TODO: Once we are past experimental phase this code should be removed const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled'); @@ -129,14 +132,15 @@ const StatefulEventsViewerComponent: React.FC = ({ useEffect(() => { if (createTimeline != null) { createTimeline({ - id, columns, + dataViewId: selectedDataViewId, defaultColumns, excludedRowRendererIds, + id, indexNames: selectedPatterns, - sort, itemsPerPage, showCheckboxes, + sort, }); } return () => { @@ -206,6 +210,7 @@ const StatefulEventsViewerComponent: React.FC = ({ query, renderCellValue, rowRenderers, + runtimeMappings, setQuery, sort, start, @@ -235,6 +240,7 @@ const StatefulEventsViewerComponent: React.FC = ({ onRuleChange={onRuleChange} renderCellValue={renderCellValue} rowRenderers={rowRenderers} + runtimeMappings={runtimeMappings} start={start} sort={sort} showTotalCount={isEmpty(graphEventId) ? true : false} @@ -249,6 +255,7 @@ const StatefulEventsViewerComponent: React.FC = ({ entityType={entityType} docValueFields={docValueFields} isFlyoutView + runtimeMappings={runtimeMappings} timelineId={id} /> diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index a90ec21f992f89..37f250ffd2d4d4 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -42,13 +42,13 @@ import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/ import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { ENTRIES, OLD_DATE_RELATIVE_TO_DATE_NOW } from '../../../../../lists/common/constants.mock'; import { CodeSignature } from '../../../../common/ecs/file'; -import { IndexPatternBase } from '@kbn/es-query'; +import { DataViewBase } from '@kbn/es-query'; jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('123'), })); -const getMockIndexPattern = (): IndexPatternBase => ({ +const getMockIndexPattern = (): DataViewBase => ({ fields, id: '1234', title: 'logstash-*', @@ -364,7 +364,7 @@ describe('Exception helpers', () => { name: 'nested.field', }, ], - } as IndexPatternBase; + } as DataViewBase; test('it should return false with an empty array', () => { const payload: ExceptionListItemSchema[] = []; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 58da977fcb8f0a..e14c151ed367c3 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -33,7 +33,7 @@ import { addIdToEntries, ExceptionsBuilderExceptionItem, } from '@kbn/securitysolution-list-utils'; -import { IndexPatternBase } from '@kbn/es-query'; +import { DataViewBase } from '@kbn/es-query'; import * as i18n from './translations'; import { AlertData, Flattened } from './types'; @@ -46,10 +46,10 @@ import exceptionableEndpointFields from './exceptionable_endpoint_fields.json'; import exceptionableEndpointEventFields from './exceptionable_endpoint_event_fields.json'; export const filterIndexPatterns = ( - patterns: IndexPatternBase, + patterns: DataViewBase, type: ExceptionListType, osTypes?: OsTypeArray -): IndexPatternBase => { +): DataViewBase => { switch (type) { case 'endpoint': const osFilterForEndpoint: (name: string) => boolean = osTypes?.includes('linux') @@ -752,7 +752,7 @@ export const getPrepopulatedBehaviorException = ({ */ export const entryHasNonEcsType = ( exceptionItems: Array, - indexPatterns: IndexPatternBase + indexPatterns: DataViewBase ): boolean => { const doesFieldNameExist = (exceptionEntry: Entry): boolean => { return indexPatterns.fields.some(({ name }) => name === exceptionEntry.field); diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap index fe589b1e253a9f..7fe76e05b2f027 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap @@ -6,14 +6,14 @@ exports[`HeaderSection it renders 1`] = ` > @@ -23,7 +23,11 @@ exports[`HeaderSection it renders 1`] = `

- Test title + + Test title +

= ({ hideSubtitle = false, }) => (
- + - +

- {title} + {title} {tooltip && ( <> {' '} diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx index 738103f02dcdf6..40b978cfe20fbf 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx @@ -17,7 +17,7 @@ import { i18n } from '@kbn/i18n'; import { StatefulTopN } from '../../top_n'; import { TimelineId } from '../../../../../common/types/timeline'; import { SourcererScopeName } from '../../../store/sourcerer/model'; -import { useSourcererScope } from '../../../containers/sourcerer'; +import { useSourcererDataView } from '../../../containers/sourcerer'; import { TooltipWithKeyboardShortcut } from '../../accessibility'; import { getAdditionalScreenReaderOnlyContext } from '../utils'; import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from '../keyboard_shortcut_constants'; @@ -85,7 +85,8 @@ export const ShowTopNButton: React.FC = React.memo( ) ? SourcererScopeName.detections : SourcererScopeName.default; - const { browserFields, indexPattern } = useSourcererScope(activeScope); + const { browserFields, indexPattern } = useSourcererDataView(activeScope); + const icon = iconType ?? 'visBarVertical'; const side = iconSide ?? 'left'; const buttonTitle = title ?? SHOW_TOP(field); diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx index 0abcbefc719540..1f6436f59602c5 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx @@ -13,7 +13,7 @@ import { DataProvider } from '../../../../common/types/timeline'; jest.mock('../../lib/kibana'); jest.mock('../../hooks/use_selector'); jest.mock('../../containers/sourcerer', () => ({ - useSourcererScope: jest.fn().mockReturnValue({ browserFields: {} }), + useSourcererDataView: jest.fn().mockReturnValue({ browserFields: {} }), })); describe('useHoverActionItems', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx index 61cef7930a3a5f..44321d68467695 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx @@ -17,7 +17,7 @@ import { allowTopN } from '../drag_and_drop/helpers'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { ColumnHeaderOptions, DataProvider, TimelineId } from '../../../../common/types/timeline'; import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useSourcererScope } from '../../containers/sourcerer'; +import { useSourcererDataView } from '../../containers/sourcerer'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { ShowTopNButton } from './actions/show_top_n'; import { FilterManager } from '../../../../../../../src/plugins/data/public'; @@ -116,7 +116,7 @@ export const useHoverActionItems = ({ ) ? SourcererScopeName.detections : SourcererScopeName.default; - const { browserFields } = useSourcererScope(activeScope); + const { browserFields } = useSourcererDataView(activeScope); /* * In the case of `DisableOverflowButton`, we show filters only when topN is NOT opened. As after topN button is clicked, the chart panel replace current hover actions in the hover actions' popover, so we have to hide all the actions. diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx index 84f61c89c4d2f3..c4ae10b732ad1b 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx @@ -19,7 +19,7 @@ describe('ImportDataModal', () => { importComplete={jest.fn()} checkBoxLabel="checkBoxLabel" description="description" - errorMessage="errorMessage" + errorMessage={jest.fn()} failedDetailed={jest.fn()} importData={jest.fn()} showCheckBox={true} diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx index 4c3dc2a249b4ff..5eeff40fe9ed66 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx @@ -23,23 +23,16 @@ import React, { useCallback, useState } from 'react'; import { ImportDataResponse, ImportDataProps, - ImportRulesResponseError, - ImportResponseError, } from '../../../detections/containers/detection_engine/rules'; -import { - displayErrorToast, - displaySuccessToast, - useStateToaster, - errorToToaster, -} from '../toasters'; +import { useAppToasts } from '../../hooks/use_app_toasts'; import * as i18n from './translations'; interface ImportDataModalProps { checkBoxLabel: string; closeModal: () => void; description: string; - errorMessage: string; - failedDetailed: (id: string, statusCode: number, message: string) => string; + errorMessage: (totalCount: number) => string; + failedDetailed: (message: string) => string; importComplete: () => void; importData: (arg: ImportDataProps) => Promise; showCheckBox: boolean; @@ -50,12 +43,6 @@ interface ImportDataModalProps { title: string; } -const isImportRulesResponseError = ( - error: ImportRulesResponseError | ImportResponseError -): error is ImportRulesResponseError => { - return (error as ImportRulesResponseError).rule_id !== undefined; -}; - /** * Modal component for importing Rules from a json file */ @@ -77,7 +64,7 @@ export const ImportDataModalComponent = ({ const [selectedFiles, setSelectedFiles] = useState(null); const [isImporting, setIsImporting] = useState(false); const [overwrite, setOverwrite] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError, addSuccess } = useAppToasts(); const cleanupAndCloseModal = useCallback(() => { setIsImporting(false); @@ -97,31 +84,39 @@ export const ImportDataModalComponent = ({ signal: abortCtrl.signal, }); - // TODO: Improve error toast details for better debugging failed imports - // e.g. When success == true && success_count === 0 that means no rules were overwritten, etc if (importResponse.success) { - displaySuccessToast(successMessage(importResponse.success_count), dispatchToaster); + addSuccess(successMessage(importResponse.success_count)); } if (importResponse.errors.length > 0) { - const formattedErrors = importResponse.errors.map((e) => - failedDetailed( - isImportRulesResponseError(e) ? e.rule_id : e.id, - e.error.status_code, - e.error.message - ) + const formattedErrors = importResponse.errors.map((e) => failedDetailed(e.error.message)); + const error: Error & { raw_network_error?: object } = new Error( + formattedErrors.join('. ') ); - displayErrorToast(errorMessage, formattedErrors, dispatchToaster); + error.stack = undefined; + error.name = 'Network errors'; + error.raw_network_error = importResponse; + addError(error, { title: errorMessage(importResponse.errors.length) }); } importComplete(); cleanupAndCloseModal(); } catch (error) { cleanupAndCloseModal(); - errorToToaster({ title: errorMessage, error, dispatchToaster }); + addError(error, { title: errorMessage(1) }); } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedFiles, overwrite]); + }, [ + selectedFiles, + overwrite, + addError, + addSuccess, + cleanupAndCloseModal, + errorMessage, + failedDetailed, + importComplete, + importData, + successMessage, + ]); const handleCloseModal = useCallback(() => { setSelectedFiles(null); diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx index 965167f2c945ef..0c077aaea81a87 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx @@ -46,6 +46,7 @@ jest.mock('../../lib/kibana', () => { describe('Custom Links', () => { const hostName = 'Host Name'; const ipv4 = '192.0.2.255'; + const ipv4a = '192.0.2.266'; const ipv6 = '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff'; const ipv6Encoded = encodeIpv6(ipv6); @@ -64,6 +65,16 @@ describe('Custom Links', () => { }); describe('NetworkDetailsLink', () => { + test('can handle array of ips', () => { + const wrapper = mount(); + expect(wrapper.find('EuiLink').first().prop('href')).toEqual( + `/ip/${encodeURIComponent(ipv4)}/source` + ); + expect(wrapper.text()).toEqual(`${ipv4}${ipv4a}`); + expect(wrapper.find('EuiLink').last().prop('href')).toEqual( + `/ip/${encodeURIComponent(ipv4a)}/source` + ); + }); test('should render valid link to IP Details with ipv4 as the display text', () => { const wrapper = mount(); expect(wrapper.find('EuiLink').prop('href')).toEqual( diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 8b9188af7725cf..c5f6f096767496 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -14,7 +14,7 @@ import { EuiToolTip, } from '@elastic/eui'; import React, { useMemo, useCallback, SyntheticEvent } from 'react'; -import { isNil } from 'lodash/fp'; +import { isArray, isNil } from 'lodash/fp'; import { IP_REPUTATION_LINKS_SETTING, APP_UI_ID } from '../../../../common/constants'; import { @@ -172,7 +172,7 @@ const NetworkDetailsLinkComponent: React.FC<{ children?: React.ReactNode; /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; - ip: string; + ip: string | string[]; flowTarget?: FlowTarget | FlowTargetSourceDest; isButton?: boolean; onClick?: (e: SyntheticEvent) => void | undefined; @@ -181,39 +181,46 @@ const NetworkDetailsLinkComponent: React.FC<{ const { formatUrl, search } = useFormatUrl(SecurityPageName.network); const { navigateToApp } = useKibana().services.application; const goToNetworkDetails = useCallback( - (ev) => { + (ev, cIp: string) => { ev.preventDefault(); navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.network, - path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget, search), + path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(cIp)), flowTarget, search), }); }, - [flowTarget, ip, navigateToApp, search] + [flowTarget, navigateToApp, search] ); - const href = useMemo( - () => formatUrl(getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)))), - [formatUrl, ip] + const getHref = useCallback( + (cIp: string) => formatUrl(getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(cIp)))), + [formatUrl] ); - return isButton ? ( - - {children} - - ) : ( - - {children ? children : ip} - + const getLink = useCallback( + (cIp: string, i: number) => + isButton ? ( + goToNetworkDetails(e, cIp))} + href={getHref(cIp)} + title={title ?? cIp} + > + {children} + + ) : ( + goToNetworkDetails(e, cIp))} + href={getHref(cIp)} + data-test-subj="network-details" + > + {children ? children : cIp} + + ), + [Component, children, getHref, goToNetworkDetails, isButton, onClick, title] ); + return isArray(ip) ? <>{ip.map(getLink)} : getLink(ip, 0); }; export const NetworkDetailsLink = React.memo(NetworkDetailsLinkComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts index 2674150ff5d1cb..610a3c086fc2cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts @@ -20,7 +20,7 @@ import { import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { SearchNavTab } from './types'; -import { SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SourcererUrlState } from '../../store/sourcerer/model'; export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { if (tab && tab.urlKey != null && !isAdministration(tab.urlKey)) { @@ -29,7 +29,7 @@ export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { let urlStateToReplace: | Filter[] | Query - | SourcererScopePatterns + | SourcererUrlState | TimelineUrl | UrlInputsModel | string = ''; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts index c99d50698db2b4..9a066037b2768a 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts @@ -7,7 +7,7 @@ import { UrlInputsModel } from '../../../store/inputs/model'; import { CONSTANTS } from '../../url_state/constants'; -import { SourcererScopePatterns } from '../../../store/sourcerer/model'; +import { SourcererUrlState } from '../../../store/sourcerer/model'; import { TimelineUrl } from '../../../../timelines/store/timeline/model'; import { Filter, Query } from '../../../../../../../../src/plugins/data/public'; @@ -21,7 +21,7 @@ export interface TabNavigationProps extends SecuritySolutionTabNavigationProps { [CONSTANTS.appQuery]?: Query; [CONSTANTS.filters]?: Filter[]; [CONSTANTS.savedQuery]?: string; - [CONSTANTS.sourcerer]: SourcererScopePatterns; + [CONSTANTS.sourcerer]: SourcererUrlState; [CONSTANTS.timerange]: UrlInputsModel; [CONSTANTS.timeline]: TimelineUrl; } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index 3db485f87a68f4..62f2cf56b7a120 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -266,7 +266,8 @@ describe('useSecuritySolutionNavigation', () => { { wrapper: TestProviders } ); - // @ts-ignore possibly undefined, but if undefined we want this test to fail + // possibly undefined, but if undefined we want this test to fail + // @ts-expect-error TS2532 expect(result.current.items[2].items[2].id).toEqual(SecurityPageName.ueba); }); diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx index f8738d827e73f5..253f2c3862e0c7 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx @@ -8,11 +8,9 @@ import React, { memo, useMemo, useCallback } from 'react'; import deepEqual from 'fast-deep-equal'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { - Filter, - IIndexPattern, FilterManager, - Query, TimeHistory, TimeRange, SavedQuery, @@ -26,7 +24,7 @@ export interface QueryBarComponentProps { dateRangeFrom?: string; dateRangeTo?: string; hideSavedQuery?: boolean; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isLoading?: boolean; isRefreshPaused?: boolean; filterQuery: Query; diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx index 8f827333143614..749156425bb1ba 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx @@ -6,13 +6,42 @@ */ import React from 'react'; -import { mount } from 'enzyme'; - +import { render, fireEvent } from '@testing-library/react'; import { InputsModelId } from '../../store/inputs/constants'; import { SearchBarComponent } from '.'; import { TestProviders } from '../../mock'; +import { FilterManager } from '../../../../../../../src/plugins/data/public'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; -jest.mock('../../lib/kibana'); +const mockFilterManager = new FilterManager(coreMock.createStart().uiSettings); +jest.mock('../../lib/kibana', () => { + const original = jest.requireActual('../../lib/kibana'); + return { + useKibana: () => ({ + services: { + ...original.useKibana().services, + data: { + ...original.useKibana().services.data, + query: { + ...original.useKibana().services.data.query, + filterManager: mockFilterManager, + }, + ui: { + SearchBar: jest.fn().mockImplementation((props) => ( + + )), + }, + }, + }, + }), + }; +}); describe('SearchBarComponent', () => { const props = { @@ -37,9 +66,38 @@ describe('SearchBarComponent', () => { savedQuery: undefined, }; + const pollForSignalIndex = jest.fn(); + beforeEach(() => { + jest.resetAllMocks(); + }); + it('calls setSearchBarFilter on mount', () => { - mount(, { wrappingComponent: TestProviders }); + render( + + + + ); expect(props.setSearchBarFilter).toHaveBeenCalled(); }); + + it('calls pollForSignalIndex on Refresh button click', () => { + const { getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('querySubmitButton')); + expect(pollForSignalIndex).toHaveBeenCalled(); + }); + + it('does not call pollForSignalIndex on Refresh button click if pollForSignalIndex not passed', () => { + const { getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('querySubmitButton')); + expect(pollForSignalIndex).not.toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx index baaffdf239dc86..9531443e985140 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx @@ -13,14 +13,9 @@ import { Dispatch } from 'redux'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import { - FilterManager, - IIndexPattern, - TimeRange, - Query, - Filter, - SavedQuery, -} from 'src/plugins/data/public'; + +import { DataViewBase, Filter, Query } from '@kbn/es-query'; +import { FilterManager, TimeRange, SavedQuery } from 'src/plugins/data/public'; import { OnTimeChangeProps } from '@elastic/eui'; @@ -48,7 +43,8 @@ const APP_STATE_STORAGE_KEY = 'securitySolution.searchBar.appState'; interface SiemSearchBarProps { id: InputsModelId; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; + pollForSignalIndex?: () => void; timelineId?: string; dataTestSubj?: string; } @@ -67,6 +63,7 @@ export const SearchBarComponent = memo( id, indexPattern, isLoading = false, + pollForSignalIndex, queries, savedQuery, setSavedQuery, @@ -100,6 +97,11 @@ export const SearchBarComponent = memo( const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { + // if the function is there, call it to check if the signals index exists yet + // in order to update the index fields + if (pollForSignalIndex != null) { + pollForSignalIndex(); + } const isQuickSelection = payload.dateRange.from.includes('now') || payload.dateRange.to.includes('now'); let updateSearchBar: UpdateReduxSearchBar = { @@ -144,7 +146,18 @@ export const SearchBarComponent = memo( window.setTimeout(() => updateSearch(updateSearchBar), 0); }, - [id, toStr, end, fromStr, start, filterManager, filterQuery, queries, updateSearch] + [ + id, + pollForSignalIndex, + toStr, + end, + fromStr, + start, + filterManager, + filterQuery, + queries, + updateSearch, + ] ); const onRefresh = useCallback( diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx index 591cba6b193810..1b23d23c5eb62e 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx @@ -6,13 +6,9 @@ */ import React from 'react'; -import { mount } from 'enzyme'; -import { waitFor } from '@testing-library/react'; - -import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { mount, ReactWrapper } from 'enzyme'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { Sourcerer } from './index'; -import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { sourcererActions, sourcererModel } from '../../store/sourcerer'; import { createSecuritySolutionStorageMock, @@ -22,6 +18,7 @@ import { TestProviders, } from '../../mock'; import { createStore, State } from '../../store'; +import { EuiSuperSelectOption } from '@elastic/eui/src/components/form/super_select/super_select_control'; const mockDispatch = jest.fn(); jest.mock('react-redux', () => { @@ -49,16 +46,28 @@ const defaultProps = { }; describe('Sourcerer component', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); - }); const state: State = mockGlobalState; + const { id, patternList, title } = state.sourcerer.defaultDataView; + const patternListNoSignals = patternList + .filter((p) => p !== state.sourcerer.signalIndexName) + .sort(); + const checkOptionsAndSelections = (wrapper: ReactWrapper, patterns: string[]) => ({ + availableOptionCount: wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).length, + optionsSelected: patterns.every((pattern) => + wrapper + .find(`[data-test-subj="sourcerer-combo-box"] span[title="${pattern}"]`) + .first() + .exists() + ), + }); + const { storage } = createSecuritySolutionStorageMock(); let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); beforeEach(() => { store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + jest.clearAllMocks(); + jest.restoreAllMocks(); }); it('renders tooltip', () => { @@ -99,20 +108,145 @@ describe('Sourcerer component', () => { ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); expect( - wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions') - ).toEqual(mockOptions); + wrapper.find(`[data-test-subj="sourcerer-combo-box"]`).first().prop('selectedOptions') + ).toEqual( + patternListNoSignals.map((p) => ({ + label: p, + value: p, + })) + ); + }); + it('Removes duplicate options from title', () => { + store = createStore( + { + ...state, + sourcerer: { + ...state.sourcerer, + defaultDataView: { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', + patternList: ['filebeat-*', 'auditbeat-*'], + }, + kibanaDataViews: [ + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', + patternList: ['filebeat-*', 'auditbeat-*'], + }, + ], + sourcererScopes: { + ...state.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + selectedDataViewId: '1234', + selectedPatterns: ['filebeat-*'], + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper + .find(`[data-test-subj="sourcerer-combo-box"] [data-test-subj="comboBoxToggleListButton"]`) + .first() + .simulate('click'); + const options: Array> = wrapper + .find(`[data-test-subj="sourcerer-combo-box"]`) + .first() + .prop('options'); + expect(options.length).toEqual(2); + }); + it('Disables options with no data', () => { + store = createStore( + { + ...state, + sourcerer: { + ...state.sourcerer, + defaultDataView: { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*,auditbeat-*,fakebeat-*', + patternList: ['filebeat-*', 'auditbeat-*'], + }, + kibanaDataViews: [ + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*,auditbeat-*,fakebeat-*', + patternList: ['filebeat-*', 'auditbeat-*'], + }, + ], + sourcererScopes: { + ...state.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + selectedDataViewId: '1234', + selectedPatterns: ['filebeat-*'], + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper + .find(`[data-test-subj="sourcerer-combo-box"] [data-test-subj="comboBoxToggleListButton"]`) + .first() + .simulate('click'); + const options: Array> = wrapper + .find(`[data-test-subj="sourcerer-combo-box"]`) + .first() + .prop('options'); + const disabledOption = options.find((o) => o.disabled); + expect(disabledOption?.value).toEqual('fakebeat-*'); }); - it('Mounts with some options selected', () => { + it('Mounts with multiple options selected - default', () => { const state2 = { ...mockGlobalState, sourcerer: { ...mockGlobalState.sourcerer, + kibanaDataViews: [ + state.sourcerer.defaultDataView, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, + ], sourcererScopes: { ...mockGlobalState.sourcerer.sourcererScopes, [SourcererScopeName.default]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], loading: false, - selectedPatterns: [DEFAULT_INDEX_PATTERN[0]], + patternList, + selectedDataViewId: id, + selectedPatterns: patternList.slice(0, 2), }, }, }, @@ -125,46 +259,84 @@ describe('Sourcerer component', () => { ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect( - wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions') - ).toEqual([mockOptions[0]]); + wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); + expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 2))).toEqual({ + // should hide signal index + availableOptionCount: title.split(',').length - 3, + optionsSelected: true, + }); }); - it('onChange calls updateSourcererScopeIndices', async () => { + it('Mounts with multiple options selected - timeline', () => { + const state2 = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + kibanaDataViews: [ + state.sourcerer.defaultDataView, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, + ], + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.timeline]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], + loading: false, + patternList, + selectedDataViewId: id, + selectedPatterns: patternList.slice(0, 2), + }, + }, + }, + }; + + store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage); const wrapper = mount( - + ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect( - wrapper.find(`[data-test-subj="sourcerer-popover"]`).first().prop('isOpen') - ).toBeTruthy(); - await waitFor(() => { - ( - wrapper.find(EuiComboBox).props() as unknown as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - } - ).onChange([mockOptions[0], mockOptions[1]]); - wrapper.update(); + wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); + expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 2))).toEqual({ + // should show every option except fakebeat-* + availableOptionCount: title.split(',').length - 2, + optionsSelected: true, }); - wrapper.find(`[data-test-subj="add-index"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="sourcerer-popover"]`).first().prop('isOpen')).toBeFalsy(); - - expect(mockDispatch).toHaveBeenCalledWith( - sourcererActions.setSelectedIndexPatterns({ - id: SourcererScopeName.default, - selectedPatterns: [mockOptions[0].value, mockOptions[1].value], - }) - ); }); - it('resets to config index patterns', async () => { + it('onSave dispatches setSelectedDataView', async () => { store = createStore( { ...state, sourcerer: { ...state.sourcerer, - kibanaIndexPatterns: [{ id: '1234', title: 'auditbeat-*' }], - configIndexPatterns: ['packetbeat-*'], + kibanaDataViews: [ + state.sourcerer.defaultDataView, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*', + patternList: ['filebeat-*'], + }, + ], + sourcererScopes: { + ...state.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + selectedDataViewId: id, + selectedPatterns: patternList.slice(0, 2), + }, + }, }, }, SUB_PLUGINS_REDUCER, @@ -177,16 +349,60 @@ describe('Sourcerer component', () => { ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="config-option"]`).first().exists()).toBeFalsy(); + wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); + expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 2))).toEqual({ + availableOptionCount: title.split(',').length - 3, + optionsSelected: true, + }); + + wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).first().simulate('click'); + expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 3))).toEqual({ + availableOptionCount: title.split(',').length - 4, + optionsSelected: true, + }); + wrapper.find(`[data-test-subj="sourcerer-save"]`).first().simulate('click'); + expect(wrapper.find(`[data-test-subj="sourcerer-popover"]`).first().prop('isOpen')).toBeFalsy(); + + expect(mockDispatch).toHaveBeenCalledWith( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.default, + selectedDataViewId: id, + selectedPatterns: patternList.slice(0, 3), + }) + ); + }); + it('resets to default index pattern', async () => { + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); + + expect(checkOptionsAndSelections(wrapper, patternListNoSignals)).toEqual({ + availableOptionCount: 1, + optionsSelected: true, + }); + wrapper .find( - `[data-test-subj="indexPattern-switcher"] [title="packetbeat-*"] button.euiBadge__iconButton` + `[data-test-subj="sourcerer-combo-box"] [title="${patternList[0]}"] button.euiBadge__iconButton` ) .first() .simulate('click'); - expect(wrapper.find(`[data-test-subj="config-option"]`).first().exists()).toBeTruthy(); + expect( + checkOptionsAndSelections(wrapper, patternListNoSignals.slice(1, patternListNoSignals.length)) + ).toEqual({ + availableOptionCount: 2, + optionsSelected: true, + }); + wrapper.find(`[data-test-subj="sourcerer-reset"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="config-option"]`).first().exists()).toBeFalsy(); + expect(checkOptionsAndSelections(wrapper, patternListNoSignals)).toEqual({ + availableOptionCount: 1, + optionsSelected: true, + }); }); it('disables saving when no index patterns are selected', () => { store = createStore( @@ -194,7 +410,15 @@ describe('Sourcerer component', () => { ...state, sourcerer: { ...state.sourcerer, - kibanaIndexPatterns: [{ id: '1234', title: 'auditbeat-*' }], + kibanaDataViews: [ + state.sourcerer.defaultDataView, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + ], }, }, SUB_PLUGINS_REDUCER, @@ -208,78 +432,152 @@ describe('Sourcerer component', () => { ); wrapper.find('[data-test-subj="sourcerer-trigger"]').first().simulate('click'); wrapper.find('[data-test-subj="comboBoxClearButton"]').first().simulate('click'); - expect(wrapper.find('[data-test-subj="add-index"]').first().prop('disabled')).toBeTruthy(); + expect(wrapper.find('[data-test-subj="sourcerer-save"]').first().prop('disabled')).toBeTruthy(); }); - it('returns index pattern options for kibanaIndexPatterns and configIndexPatterns', () => { - store = createStore( - { - ...state, - sourcerer: { - ...state.sourcerer, - kibanaIndexPatterns: [{ id: '1234', title: 'auditbeat-*' }], - configIndexPatterns: ['packetbeat-*'], + it('Selects a different index pattern', async () => { + const state2 = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + kibanaDataViews: [ + state.sourcerer.defaultDataView, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'fakebeat-*,neatbeat-*', + patternList: ['fakebeat-*'], + }, + ], + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + patternList, + selectedDataViewId: id, + selectedPatterns: patternList.slice(0, 2), + }, }, }, - SUB_PLUGINS_REDUCER, - kibanaObservable, - storage - ); + }; + + store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage); const wrapper = mount( ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="config-option"]`).first().exists()).toBeFalsy(); - wrapper - .find( - `[data-test-subj="indexPattern-switcher"] [title="auditbeat-*"] button.euiBadge__iconButton` - ) - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.find(`[data-test-subj="kip-option"]`).first().text()).toEqual(' auditbeat-*'); - wrapper - .find( - `[data-test-subj="indexPattern-switcher"] [title="packetbeat-*"] button.euiBadge__iconButton` - ) - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.find(`[data-test-subj="config-option"]`).first().text()).toEqual('packetbeat-*'); + wrapper.find(`button[data-test-subj="sourcerer-select"]`).first().simulate('click'); + + wrapper.find(`[data-test-subj="dataView-option-super"]`).first().simulate('click'); + expect(checkOptionsAndSelections(wrapper, ['fakebeat-*'])).toEqual({ + availableOptionCount: 0, + optionsSelected: true, + }); + wrapper.find(`[data-test-subj="sourcerer-save"]`).first().simulate('click'); + + expect(mockDispatch).toHaveBeenCalledWith( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.default, + selectedDataViewId: '1234', + selectedPatterns: ['fakebeat-*'], + }) + ); }); - it('combines index pattern options for kibanaIndexPatterns and configIndexPatterns', () => { - store = createStore( - { - ...state, - sourcerer: { - ...state.sourcerer, - kibanaIndexPatterns: [ - { id: '1234', title: 'auditbeat-*' }, - { id: '5678', title: 'packetbeat-*' }, - ], - configIndexPatterns: ['packetbeat-*'], + it('Does display signals index on timeline sourcerer', () => { + const state2 = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + kibanaDataViews: [ + state.sourcerer.defaultDataView, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, + ], + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.timeline]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], + loading: false, + patternList, + selectedDataViewId: id, + selectedPatterns: patternList.slice(0, 2), + }, }, }, - SUB_PLUGINS_REDUCER, - kibanaObservable, - storage + }; + + store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="comboBoxToggleListButton"]`).first().simulate('click'); + expect(wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).at(6).text()).toEqual( + mockGlobalState.sourcerer.signalIndexName ); + }); + it('Does not display signals index on default sourcerer', () => { + const state2 = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + kibanaDataViews: [ + state.sourcerer.defaultDataView, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, + ], + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + patternList, + selectedDataViewId: id, + selectedPatterns: patternList.slice(0, 2), + }, + }, + }, + }; + + store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage); const wrapper = mount( ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - wrapper - .find( - `[data-test-subj="indexPattern-switcher"] [title="packetbeat-*"] button.euiBadge__iconButton` - ) - .first() - .simulate('click'); - wrapper.update(); + wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); expect( - wrapper.find(`[title="packetbeat-*"] [data-test-subj="kip-option"]`).first().text() - ).toEqual(' packetbeat-*'); + wrapper + .find( + `[data-test-subj="sourcerer-combo-box"] span[title="${mockGlobalState.sourcerer.signalIndexName}"]` + ) + .first() + .exists() + ).toBeFalsy(); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx index 3e922176f99826..6f32282c530408 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx @@ -10,24 +10,26 @@ import { EuiButtonEmpty, EuiComboBox, EuiComboBoxOptionOption, - EuiIcon, EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiPopover, EuiPopoverTitle, EuiSpacer, + EuiSuperSelect, EuiText, EuiToolTip, } from '@elastic/eui'; import deepEqual from 'fast-deep-equal'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import * as i18n from './translations'; -import { sourcererActions, sourcererModel } from '../../store/sourcerer'; -import { State } from '../../store'; -import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors'; +import { sourcererActions, sourcererModel, sourcererSelectors } from '../../store/sourcerer'; +import { getScopePatternListSelection } from '../../store/sourcerer/helpers'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { SourcererScopeName } from '../../store/sourcerer/model'; const PopoverContent = styled.div` width: 600px; @@ -40,30 +42,77 @@ interface SourcererComponentProps { scope: sourcererModel.SourcererScopeName; } +const getPatternListWithoutSignals = ( + patternList: string[], + signalIndexName: string | null +): string[] => patternList.filter((p) => p !== signalIndexName); + export const Sourcerer = React.memo(({ scope: scopeId }) => { const dispatch = useDispatch(); - const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); - const { configIndexPatterns, kibanaIndexPatterns, sourcererScope } = useSelector< - State, - SourcererScopeSelector - >((state) => sourcererScopeSelector(state, scopeId), deepEqual); - const { selectedPatterns, loading } = sourcererScope; + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); + const { + defaultDataView, + kibanaDataViews, + signalIndexName, + sourcererScope: { selectedDataViewId, selectedPatterns, loading }, + } = useDeepEqualSelector((state) => sourcererScopeSelector(state, scopeId)); + const [isPopoverOpen, setPopoverIsOpen] = useState(false); + + const [dataViewId, setDataViewId] = useState(selectedDataViewId ?? defaultDataView.id); + + const { patternList, selectablePatterns } = useMemo(() => { + const theDataView = kibanaDataViews.find((dataView) => dataView.id === dataViewId); + return theDataView != null + ? scopeId === SourcererScopeName.default + ? { + patternList: getPatternListWithoutSignals( + theDataView.title + .split(',') + // remove duplicates patterns from selector + .filter((pattern, i, self) => self.indexOf(pattern) === i), + signalIndexName + ), + selectablePatterns: getPatternListWithoutSignals( + theDataView.patternList, + signalIndexName + ), + } + : { + patternList: theDataView.title + .split(',') + // remove duplicates patterns from selector + .filter((pattern, i, self) => self.indexOf(pattern) === i), + selectablePatterns: theDataView.patternList, + } + : { patternList: [], selectablePatterns: [] }; + }, [kibanaDataViews, scopeId, signalIndexName, dataViewId]); + + const selectableOptions = useMemo( + () => + patternList.map((indexName) => ({ + label: indexName, + value: indexName, + disabled: !selectablePatterns.includes(indexName), + })), + [selectablePatterns, patternList] + ); + const [selectedOptions, setSelectedOptions] = useState>>( - selectedPatterns.map((indexSelected) => ({ - label: indexSelected, - value: indexSelected, + selectedPatterns.map((indexName) => ({ + label: indexName, + value: indexName, })) ); const isSavingDisabled = useMemo(() => selectedOptions.length === 0, [selectedOptions]); const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []); - - const onChangeIndexPattern = useCallback( - (newSelectedPatterns: string[]) => { + const onChangeDataView = useCallback( + (newSelectedDataView: string, newSelectedPatterns: string[]) => { dispatch( - sourcererActions.setSelectedIndexPatterns({ + sourcererActions.setSelectedDataView({ id: scopeId, + selectedDataViewId: newSelectedDataView, selectedPatterns: newSelectedPatterns, }) ); @@ -72,52 +121,55 @@ export const Sourcerer = React.memo(({ scope: scopeId } ); const renderOption = useCallback( - ({ value }) => - kibanaIndexPatterns.some((kip) => kip.title === value) ? ( - - {value} - - ) : ( - {value} - ), - [kibanaIndexPatterns] + ({ value }) => {value}, + [] ); const onChangeCombo = useCallback((newSelectedOptions) => { setSelectedOptions(newSelectedOptions); }, []); + const onChangeSuper = useCallback( + (newSelectedOption) => { + setDataViewId(newSelectedOption); + setSelectedOptions( + getScopePatternListSelection( + kibanaDataViews.find((dataView) => dataView.id === newSelectedOption), + scopeId, + signalIndexName, + newSelectedOption === defaultDataView.id + ).map((indexSelected: string) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + }, + [defaultDataView.id, kibanaDataViews, scopeId, signalIndexName] + ); + const resetDataSources = useCallback(() => { + setDataViewId(defaultDataView.id); setSelectedOptions( - configIndexPatterns.map((indexSelected) => ({ - label: indexSelected, - value: indexSelected, - })) + getScopePatternListSelection(defaultDataView, scopeId, signalIndexName, true).map( + (indexSelected: string) => ({ + label: indexSelected, + value: indexSelected, + }) + ) ); - }, [configIndexPatterns]); + }, [defaultDataView, scopeId, signalIndexName]); const handleSaveIndices = useCallback(() => { - onChangeIndexPattern(selectedOptions.map((so) => so.label)); + onChangeDataView( + dataViewId, + selectedOptions.map((so) => so.label) + ); setPopoverIsOpen(false); - }, [onChangeIndexPattern, selectedOptions]); + }, [onChangeDataView, dataViewId, selectedOptions]); const handleClosePopOver = useCallback(() => { setPopoverIsOpen(false); }, []); - - const indexesPatternOptions = useMemo( - () => - [...configIndexPatterns, ...kibanaIndexPatterns.map((kip) => kip.title)].reduce< - Array> - >((acc, index) => { - if (index != null && !acc.some((o) => o.label.includes(index))) { - return [...acc, { label: index, value: index }]; - } - return acc; - }, []), - [configIndexPatterns, kibanaIndexPatterns] - ); - const trigger = useMemo( () => ( (({ scope: scopeId } [setPopoverIsOpenCb, loading] ); - const comboBox = useMemo( - () => ( - - ), - [indexesPatternOptions, onChangeCombo, renderOption, selectedOptions] + const dataViewSelectOptions = useMemo( + () => + kibanaDataViews.map(({ title, id }) => ({ + inputDisplay: + id === defaultDataView.id ? ( + + {i18n.SIEM_DATA_VIEW_LABEL} + + ) : ( + + {title} + + ), + value: id, + })), + [defaultDataView.id, kibanaDataViews] ); useEffect(() => { - const newSelecteOptions = selectedPatterns.map((indexSelected) => ({ - label: indexSelected, - value: indexSelected, - })); - setSelectedOptions((prevSelectedOptions) => { - if (!deepEqual(newSelecteOptions, prevSelectedOptions)) { - return newSelecteOptions; - } - return prevSelectedOptions; - }); + setDataViewId((prevSelectedOption) => + selectedDataViewId != null && !deepEqual(selectedDataViewId, prevSelectedOption) + ? selectedDataViewId + : prevSelectedOption + ); + }, [selectedDataViewId]); + useEffect(() => { + setSelectedOptions( + selectedPatterns.map((indexName) => ({ + label: indexName, + value: indexName, + })) + ); }, [selectedPatterns]); const tooltipContent = useMemo( - () => (isPopoverOpen ? null : sourcererScope.selectedPatterns.sort().join(', ')), - [isPopoverOpen, sourcererScope.selectedPatterns] + () => (isPopoverOpen ? null : selectedPatterns.join(', ')), + [selectedPatterns, isPopoverOpen] ); const buttonWithTooptip = useMemo(() => { @@ -196,7 +254,24 @@ export const Sourcerer = React.memo(({ scope: scopeId } {i18n.INDEX_PATTERNS_SELECTION_LABEL} - {comboBox} + + + @@ -214,7 +289,7 @@ export const Sourcerer = React.memo(({ scope: scopeId } { - const getKibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); - const getScopesSelector = sourcererSelectors.scopesSelector(); - const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector(); - - const mapStateToProps = (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { - const kibanaIndexPatterns = getKibanaIndexPatternsSelector(state); - const scope = getScopesSelector(state)[scopeId]; - const configIndexPatterns = getConfigIndexPatternsSelector(state); - - return { - kibanaIndexPatterns, - configIndexPatterns, - sourcererScope: scope, - }; - }; - - return mapStateToProps; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts index 45299afad99d1a..03fdc5d191719b 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts @@ -11,9 +11,12 @@ export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.da defaultMessage: 'Data sources', }); -export const ALL_DEFAULT = i18n.translate('xpack.securitySolution.indexPatterns.allDefault', { - defaultMessage: 'All default', -}); +export const SIEM_DATA_VIEW_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.kipLabel', + { + defaultMessage: 'Default Security Data View', + } +); export const SELECT_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', { defaultMessage: 'Data sources selection', diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx index 4b153f0c75775c..0550ef389585e1 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx @@ -11,7 +11,7 @@ import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { EntryItem } from './entry_item'; import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; jest.mock('../../../common/lib/kibana'); @@ -31,7 +31,7 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } showLabel={true} onChange={jest.fn()} @@ -40,7 +40,7 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } /> ); @@ -64,14 +64,14 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } showLabel={false} onChange={mockOnChange} @@ -111,14 +111,14 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } showLabel={false} onChange={mockOnChange} diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx index 99c5b37046505c..777ef2069ff4f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx @@ -10,16 +10,15 @@ import { EuiFormRow, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { FieldComponent } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternFieldBase } from '@kbn/es-query'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { FormattedEntry, Entry } from './types'; import * as i18n from './translations'; import { getEntryOnFieldChange, getEntryOnThreatFieldChange } from './helpers'; interface EntryItemProps { entry: FormattedEntry; - indexPattern: IndexPattern; - threatIndexPatterns: IndexPattern; + indexPattern: DataViewBase; + threatIndexPatterns: DataViewBase; showLabel: boolean; onChange: (arg: Entry, i: number) => void; } @@ -41,7 +40,7 @@ export const EntryItem: React.FC = ({ onChange, }): JSX.Element => { const handleFieldChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { const { updatedEntry, index } = getEntryOnFieldChange(entry, newField); onChange(updatedEntry, index); }, @@ -49,7 +48,7 @@ export const EntryItem: React.FC = ({ ); const handleThreatFieldChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { const { updatedEntry, index } = getEntryOnThreatFieldChange(entry, newField); onChange(updatedEntry, index); }, diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx index 0e5f379a18767a..9a5051fe0cafbc 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx @@ -7,7 +7,8 @@ import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { Entry, EmptyEntry, ThreatMapEntries, FormattedEntry } from './types'; -import { FieldSpec, IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { FieldSpec } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase } from '@kbn/es-query'; import moment from 'moment-timezone'; import { @@ -24,12 +25,12 @@ jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('123'), })); -const getMockIndexPattern = (): IndexPattern => +const getMockIndexPattern = (): DataViewBase => ({ id: '1234', title: 'logstash-*', fields, - } as IndexPattern); + } as DataViewBase); const getMockEntry = (): FormattedEntry => ({ id: '123', @@ -51,7 +52,7 @@ describe('Helpers', () => { describe('#getFormattedEntry', () => { test('it returns entry with a value when "item.field" is of type "text" and matching keyword field exists', () => { - const payloadIndexPattern: IndexPattern = { + const payloadIndexPattern: DataViewBase = { ...getMockIndexPattern(), fields: [ ...fields, @@ -66,7 +67,7 @@ describe('Helpers', () => { readFromDocValues: true, }, ], - } as IndexPattern; + } as DataViewBase; const payloadItem: Entry = { field: 'machine.os.raw.text', type: 'mapping', @@ -171,7 +172,7 @@ describe('Helpers', () => { }); test('it returns formatted entries', () => { - const payloadIndexPattern: IndexPattern = getMockIndexPattern(); + const payloadIndexPattern: DataViewBase = getMockIndexPattern(); const payloadItems: Entry[] = [ { field: 'machine.os', type: 'mapping', value: 'machine.os' }, { field: 'ip', type: 'mapping', value: 'ip' }, diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx index 3f8e5b1602db85..7b0385ab6902f5 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx @@ -10,8 +10,7 @@ import { i18n } from '@kbn/i18n'; import { addIdToItem } from '@kbn/securitysolution-utils'; import { ThreatMap, threatMap, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; -import { IndexPatternFieldBase } from '@kbn/es-query'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { Entry, FormattedEntry, ThreatMapEntries, EmptyEntry } from './types'; import { ValidationFunc } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { ERROR_CODE } from '../../../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; @@ -19,13 +18,13 @@ import { ERROR_CODE } from '../../../../../../../src/plugins/es_ui_shared/static /** * Formats the entry into one that is easily usable for the UI. * - * @param patterns IndexPattern containing available fields on rule index + * @param patterns DataViewBase containing available fields on rule index * @param item item entry * @param itemIndex entry index */ export const getFormattedEntry = ( - indexPattern: IndexPattern, - threatIndexPatterns: IndexPattern, + indexPattern: DataViewBase, + threatIndexPatterns: DataViewBase, item: Entry, itemIndex: number, uuidGen: () => string = uuid.v4 @@ -51,12 +50,12 @@ export const getFormattedEntry = ( /** * Formats the entries to be easily usable for the UI * - * @param patterns IndexPattern containing available fields on rule index + * @param patterns DataViewBase containing available fields on rule index * @param entries item entries */ export const getFormattedEntries = ( - indexPattern: IndexPattern, - threatIndexPatterns: IndexPattern, + indexPattern: DataViewBase, + threatIndexPatterns: DataViewBase, entries: Entry[] ): FormattedEntry[] => { return entries.reduce((acc, item, index) => { @@ -91,7 +90,7 @@ export const getUpdatedEntriesOnDelete = ( */ export const getEntryOnFieldChange = ( item: FormattedEntry, - newField: IndexPatternFieldBase + newField: DataViewFieldBase ): { updatedEntry: Entry; index: number } => { const { entryIndex } = item; return { @@ -114,7 +113,7 @@ export const getEntryOnFieldChange = ( */ export const getEntryOnThreatFieldChange = ( item: FormattedEntry, - newField: IndexPatternFieldBase + newField: DataViewFieldBase ): { updatedEntry: Entry; index: number } => { const { entryIndex } = item; return { diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx index 872f78d91eebb0..e26c889b7e94a0 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx @@ -16,7 +16,7 @@ import { useKibana } from '../../../common/lib/kibana'; import { ThreatMatchComponent } from './'; import { ThreatMapEntries } from './types'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { getMockTheme } from '../../lib/kibana/kibana_react.mock'; const mockTheme = getMockTheme({ @@ -65,14 +65,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -94,14 +94,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -123,14 +123,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -151,14 +151,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -188,14 +188,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -225,14 +225,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -255,14 +255,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -286,14 +286,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx index 3e4f0283145e47..5ea17939ed780b 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx @@ -8,10 +8,9 @@ import React, { useCallback, useEffect, useReducer } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; - +import { DataViewBase } from '@kbn/es-query'; import { ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { ListItemComponent } from './list_item'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { AndOrBadge } from '../and_or_badge'; import { LogicButtons } from './logic_buttons'; import { ThreatMapEntries } from './types'; @@ -45,8 +44,8 @@ interface OnChangeProps { interface ThreatMatchComponentProps { listItems: ThreatMapEntries[]; - indexPatterns: IndexPattern; - threatIndexPatterns: IndexPattern; + indexPatterns: DataViewBase; + threatIndexPatterns: DataViewBase; onChange: (arg: OnChangeProps) => void; } diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx index d6704b3a9cf176..e2325ace265918 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx @@ -14,7 +14,7 @@ import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { ListItemComponent } from './list_item'; import { ThreatMapEntries } from './types'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { getMockTheme } from '../../lib/kibana/kibana_react.mock'; const mockTheme = getMockTheme({ @@ -81,14 +81,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={true} isOnlyItem={false} @@ -114,7 +114,7 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={true} isOnlyItem={false} @@ -125,7 +125,7 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } /> @@ -145,14 +145,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={true} isOnlyItem={false} @@ -178,14 +178,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={false} @@ -219,14 +219,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={true} @@ -250,14 +250,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={false} @@ -281,14 +281,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} // if entryItemIndex is not 0, wouldn't make sense for @@ -314,14 +314,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={true} @@ -346,14 +346,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={true} diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx index eda12ce3b0f5a9..e01c28ecb54eb0 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx @@ -9,7 +9,7 @@ import React, { useMemo, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase } from '@kbn/es-query'; import { getFormattedEntries, getUpdatedEntriesOnDelete } from './helpers'; import { FormattedEntry, ThreatMapEntries, Entry } from './types'; import { EntryItem } from './entry_item'; @@ -24,8 +24,8 @@ const MyOverflowContainer = styled(EuiFlexItem)` interface ListItemProps { listItem: ThreatMapEntries; listItemIndex: number; - indexPattern: IndexPattern; - threatIndexPatterns: IndexPattern; + indexPattern: DataViewBase; + threatIndexPatterns: DataViewBase; andLogicIncluded: boolean; isOnlyItem: boolean; onDeleteEntryItem: (item: ThreatMapEntries, index: number) => void; diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts b/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts index 3d3a18b425e010..852e68aa259c8f 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewFieldBase } from '@kbn/es-query'; import { ThreatMap, ThreatMapEntry } from '@kbn/securitysolution-io-ts-alerting-types'; export interface FormattedEntry { id: string; - field: IndexPatternFieldBase | undefined; + field: DataViewFieldBase | undefined; type: 'mapping'; - value: IndexPatternFieldBase | undefined; + value: DataViewFieldBase | undefined; entryIndex: number; } diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index 1eef44d587ed0f..ac9acb62c58340 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -8,15 +8,11 @@ import React, { useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { useGlobalTime } from '../../containers/use_global_time'; import { BrowserFields } from '../../containers/source'; import { useKibana } from '../../lib/kibana'; -import { - esQuery, - Filter, - Query, - IIndexPattern, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { inputsModel, inputsSelectors, State } from '../../store'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { timelineSelectors } from '../../../timelines/store/timeline'; @@ -77,7 +73,7 @@ const connector = connect(makeMapStateToProps); export interface OwnProps { browserFields: BrowserFields; field: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; timelineId?: string; toggleTopN: () => void; onFilterAdded?: () => void; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx index f13a85da6baed4..de3472b7c455f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/selectors.tsx @@ -7,6 +7,7 @@ import { State } from '../../store'; import { sourcererSelectors } from '../../store/selectors'; +import { SourcererScopeName } from '../../store/sourcerer/model'; export interface IndicesSelector { all: string[]; @@ -14,25 +15,17 @@ export interface IndicesSelector { } export const getIndicesSelector = () => { - const getkibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); - const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector(); const getSignalIndexNameSelector = sourcererSelectors.signalIndexNameSelector(); + const getScopeSelector = sourcererSelectors.scopeIdSelector(); - const mapStateToProps = (state: State): IndicesSelector => { - const rawIndices = new Set(getConfigIndexPatternsSelector(state)); - const kibanaIndexPatterns = getkibanaIndexPatternsSelector(state); - const alertIndexName = getSignalIndexNameSelector(state); - kibanaIndexPatterns.forEach(({ title }) => { - if (title !== alertIndexName) { - rawIndices.add(title); - } - }); + return (state: State, scopeId: SourcererScopeName): IndicesSelector => { + const signalIndexName = getSignalIndexNameSelector(state); + const { selectedPatterns } = getScopeSelector(state, scopeId); + const raw: string[] = selectedPatterns.filter((index) => index !== signalIndexName); return { - all: alertIndexName != null ? [...rawIndices, alertIndexName] : [...rawIndices], - raw: [...rawIndices], + all: signalIndexName != null ? [...raw, signalIndexName] : [...raw], + raw, }; }; - - return mapStateToProps; }; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx index f40ee8670171cb..9c99189e1b79e4 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx @@ -11,10 +11,10 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import styled from 'styled-components'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { GlobalTimeArgs } from '../../containers/use_global_time'; import { EventsByDataset } from '../../../overview/components/events_by_dataset'; import { SignalsByCategory } from '../../../overview/components/signals_by_category'; -import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; import { InputsModelId } from '../../store/inputs/constants'; import { TimelineEventsType } from '../../../../common/types/timeline'; @@ -23,6 +23,7 @@ import * as i18n from './translations'; import { getIndicesSelector, IndicesSelector } from './selectors'; import { State } from '../../store'; import { AlertsStackByField } from '../../../detections/components/alerts_kpis/common/types'; +import { SourcererScopeName } from '../../store/sourcerer/model'; const TopNContainer = styled.div` min-width: 600px; @@ -53,7 +54,7 @@ export interface Props extends Pick = ({ ); const indicesSelector = useMemo(getIndicesSelector, []); const { all: allIndices, raw: rawIndices } = useSelector( - (state) => indicesSelector(state), + (state) => + indicesSelector( + state, + timelineId != null + ? defaultView === 'alert' + ? SourcererScopeName.detections + : SourcererScopeName.timeline + : SourcererScopeName.default + ), deepEqual ); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts index 5b6bb0400ccdf2..1ff98528a20b8f 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts @@ -24,7 +24,7 @@ import { NavTab } from '../navigation/types'; import { CONSTANTS, UrlStateType } from './constants'; import { ReplaceStateInLocation, KeyUrlState, ValueUrlState } from './types'; import { sourcererSelectors } from '../../store/sourcerer'; -import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SourcererScopeName, SourcererUrlState } from '../../store/sourcerer/model'; export const isDetectionsPages = (pageName: string) => pageName === SecurityPageName.alerts || @@ -156,9 +156,18 @@ export const makeMapStateToProps = () => { } const sourcerer = getSourcererScopes(state); const activeScopes: SourcererScopeName[] = Object.keys(sourcerer) as SourcererScopeName[]; - const selectedPatterns: SourcererScopePatterns = activeScopes + const selectedPatterns: SourcererUrlState = activeScopes .filter((scope) => scope === SourcererScopeName.default) - .reduce((acc, scope) => ({ ...acc, [scope]: sourcerer[scope]?.selectedPatterns }), {}); + .reduce( + (acc, scope) => ({ + ...acc, + [scope]: { + id: sourcerer[scope]?.selectedDataViewId, + selectedPatterns: sourcerer[scope]?.selectedPatterns, + }, + }), + {} + ); return { urlState: { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx index f04cf30da61f54..11312e942c1ebd 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx @@ -28,7 +28,7 @@ import { queryTimelineById, dispatchUpdateTimeline, } from '../../../timelines/components/open_timeline/helpers'; -import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SourcererScopeName, SourcererUrlState } from '../../store/sourcerer/model'; import { timelineActions } from '../../../timelines/store/timeline'; export const useSetInitialStateFromUrl = () => { @@ -55,16 +55,17 @@ export const useSetInitialStateFromUrl = () => { updateTimerange(newUrlStateString, dispatch); } if (urlKey === CONSTANTS.sourcerer) { - const sourcererState = decodeRisonUrlState(newUrlStateString); + const sourcererState = decodeRisonUrlState(newUrlStateString); if (sourcererState != null) { const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter( (key) => !(key === SourcererScopeName.default && isDetectionsPages(pageName)) ) as SourcererScopeName[]; activeScopes.forEach((scope) => dispatch( - sourcererActions.setSelectedIndexPatterns({ + sourcererActions.setSelectedDataView({ id: scope, - selectedPatterns: sourcererState[scope] ?? [], + selectedDataViewId: sourcererState[scope]?.id ?? '', + selectedPatterns: sourcererState[scope]?.selectedPatterns ?? [], }) ) ); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/normalize_time_range.test.ts b/x-pack/plugins/security_solution/public/common/components/url_state/normalize_time_range.test.ts index b5e4b04d2cd95d..a87b1206c0082b 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/normalize_time_range.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/normalize_time_range.test.ts @@ -13,23 +13,12 @@ import { RelativeTimeRange, isRelativeTimeRange, } from '../../store/inputs/model'; - +import DateMath from '@elastic/datemath'; import { getTimeRangeSettings } from '../../utils/default_date_settings'; const getTimeRangeSettingsMock = getTimeRangeSettings as jest.Mock; jest.mock('../../utils/default_date_settings'); -jest.mock('@elastic/datemath', () => ({ - parse: (date: string) => { - if (date === 'now') { - return { toISOString: () => '2020-07-08T08:20:18.966Z' }; - } - - if (date === 'now-24h') { - return { toISOString: () => '2020-07-07T08:20:18.966Z' }; - } - }, -})); getTimeRangeSettingsMock.mockImplementation(() => ({ from: '2020-07-04T08:20:18.966Z', @@ -39,6 +28,19 @@ getTimeRangeSettingsMock.mockImplementation(() => ({ })); describe('#normalizeTimeRange', () => { + let dateMathSpy: jest.SpyInstance; + beforeAll(() => { + dateMathSpy = jest.spyOn(DateMath, 'parse'); + dateMathSpy.mockImplementation((date: string) => + date === 'now' + ? { toISOString: () => new Date('2020-07-08T08:20:18.966Z') } + : { toISOString: () => new Date('2020-07-07T08:20:18.966Z') } + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); test('Absolute time range returns defaults for empty strings', () => { const dateTimeRange: URLTimeRange = { kind: 'absolute', diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts index f0bd69b21c6173..f722c5ec0da00e 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts @@ -84,9 +84,7 @@ export const defaultProps: UrlStateContainerPropTypes = { indexPattern: { fields: [ { - aggregatable: true, name: '@timestamp', - searchable: true, type: 'date', }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts index 06ed33ac69f6e7..2626f4fb03c23d 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts @@ -5,21 +5,15 @@ * 2.0. */ -import { - Filter, - FilterManager, - IIndexPattern, - Query, - SavedQueryService, -} from 'src/plugins/data/public'; - +import { Filter, FilterManager, Query, SavedQueryService } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { UrlInputsModel } from '../../store/inputs/model'; import { TimelineUrl } from '../../../timelines/store/timeline/model'; import { RouteSpyState } from '../../utils/route/types'; import { SecurityNav } from '../navigation/types'; import { CONSTANTS, UrlStateType } from './constants'; -import { SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SourcererUrlState } from '../../store/sourcerer/model'; export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ CONSTANTS.appQuery, @@ -48,7 +42,7 @@ export interface UrlState { [CONSTANTS.appQuery]?: Query; [CONSTANTS.filters]?: Filter[]; [CONSTANTS.savedQuery]?: string; - [CONSTANTS.sourcerer]: SourcererScopePatterns; + [CONSTANTS.sourcerer]: SourcererUrlState; [CONSTANTS.timerange]: UrlInputsModel; [CONSTANTS.timeline]: TimelineUrl; } @@ -58,7 +52,7 @@ export type ValueUrlState = UrlState[keyof UrlState]; export interface UrlStateProps { navTabs: SecurityNav; - indexPattern?: IIndexPattern; + indexPattern?: DataViewBase; mapToUrlState?: (value: string) => UrlState; onChange?: (urlState: UrlState, previousUrlState: UrlState) => void; onInitialize?: (urlState: UrlState) => void; @@ -89,7 +83,7 @@ export interface UrlStateToRedux { export interface SetInitialStateFromUrl { filterManager: FilterManager; - indexPattern: IIndexPattern | undefined; + indexPattern: DataViewBase | undefined; pageName: string; savedQueries: SavedQueryService; urlStateToUpdate: UrlStateToRedux[]; diff --git a/x-pack/plugins/security_solution/public/common/containers/kuery_autocompletion/index.tsx b/x-pack/plugins/security_solution/public/common/containers/kuery_autocompletion/index.tsx deleted file mode 100644 index 7ed4ba6944eb74..00000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/kuery_autocompletion/index.tsx +++ /dev/null @@ -1,85 +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 React, { useState } from 'react'; -import { QuerySuggestion, IIndexPattern } from '../../../../../../../src/plugins/data/public'; -import { useKibana } from '../../lib/kibana'; - -type RendererResult = React.ReactElement | null; -type RendererFunction = (args: RenderArgs) => Result; - -interface KueryAutocompletionLifecycleProps { - children: RendererFunction<{ - isLoadingSuggestions: boolean; - loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: QuerySuggestion[]; - }>; - indexPattern: IIndexPattern; -} - -interface KueryAutocompletionCurrentRequest { - expression: string; - cursorPosition: number; -} - -export const KueryAutocompletion = React.memo( - ({ children, indexPattern }) => { - const [currentRequest, setCurrentRequest] = useState( - null - ); - const [suggestions, setSuggestions] = useState([]); - const kibana = useKibana(); - const loadSuggestions = async ( - expression: string, - cursorPosition: number, - maxSuggestions?: number - ) => { - const language = 'kuery'; - - if (!kibana.services.data.autocomplete.hasQuerySuggestions(language)) { - return; - } - - const futureRequest = { - expression, - cursorPosition, - }; - setCurrentRequest({ - expression, - cursorPosition, - }); - setSuggestions([]); - - if ( - futureRequest && - futureRequest.expression !== (currentRequest && currentRequest.expression) && - futureRequest.cursorPosition !== (currentRequest && currentRequest.cursorPosition) - ) { - const newSuggestions = - (await kibana.services.data.autocomplete.getQuerySuggestions({ - language: 'kuery', - indexPatterns: [indexPattern], - boolFilter: [], - query: expression, - selectionStart: cursorPosition, - selectionEnd: cursorPosition, - })) || []; - - setCurrentRequest(null); - setSuggestions(maxSuggestions ? newSuggestions.slice(0, maxSuggestions) : newSuggestions); - } - }; - - return children({ - isLoadingSuggestions: currentRequest !== null, - loadSuggestions, - suggestions, - }); - } -); - -KueryAutocompletion.displayName = 'KueryAutocompletion'; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx index 564d47f85da3fc..d33afdf881f978 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx @@ -7,7 +7,31 @@ import { IndexField } from '../../../../common/search_strategy/index_fields'; import { getBrowserFields } from '.'; +import { useDataView } from './use_data_view'; import { mockBrowserFields, mocksSource } from './mock'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { createStore, State } from '../../store'; +import { + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, +} from '../../mock'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { Provider } from 'react-redux'; +import React from 'react'; +import { useKibana } from '../../lib/kibana'; + +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); +jest.mock('../../lib/kibana'); // , () => ({ describe('source/index.tsx', () => { describe('getBrowserFields', () => { @@ -28,4 +52,89 @@ describe('source/index.tsx', () => { expect(fields).toEqual(mockBrowserFields); }); }); + describe('useDataView hook', () => { + const sourcererState = mockGlobalState.sourcerer; + const state: State = { + ...mockGlobalState, + sourcerer: { + ...sourcererState, + kibanaDataViews: [ + ...sourcererState.kibanaDataViews, + { + ...sourcererState.defaultDataView, + id: 'something-random', + title: 'something,random', + patternList: ['something', 'random'], + }, + ], + sourcererScopes: { + ...sourcererState.sourcererScopes, + [SourcererScopeName.default]: { + ...sourcererState.sourcererScopes[SourcererScopeName.default], + }, + [SourcererScopeName.detections]: { + ...sourcererState.sourcererScopes[SourcererScopeName.detections], + }, + [SourcererScopeName.timeline]: { + ...sourcererState.sourcererScopes[SourcererScopeName.timeline], + }, + }, + }, + }; + const mockSearchResponse = { + ...mocksSource, + indicesExist: ['auditbeat-*', sourcererState.signalIndexName], + isRestore: false, + rawResponse: {}, + runtimeMappings: {}, + }; + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + + beforeEach(() => { + jest.clearAllMocks(); + const mock = { + subscribe: ({ next }: { next: Function }) => next(mockSearchResponse), + unsubscribe: jest.fn(), + }; + + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + search: { + search: jest.fn().mockReturnValue({ + subscribe: ({ next }: { next: Function }) => { + next(mockSearchResponse); + return mock; + }, + unsubscribe: jest.fn(), + }), + }, + }, + }, + }); + }); + it('sets field data for data view', async () => { + await act(async () => { + const { rerender, waitForNextUpdate, result } = renderHook< + string, + { indexFieldsSearch: (id: string) => void } + >(() => useDataView(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + rerender(); + act(() => result.current.indexFieldsSearch('neato')); + expect(mockDispatch.mock.calls[0][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { id: 'neato', loading: true }, + }); + const { type: sourceType, payload } = mockDispatch.mock.calls[1][0]; + expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_DATA_VIEW'); + expect(payload.id).toEqual('neato'); + expect(Object.keys(payload.browserFields)).toHaveLength(10); + expect(payload.docValueFields).toEqual([{ field: '@timestamp' }]); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index a894dbdd1dda7c..1bbd484a33d1e6 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -5,30 +5,26 @@ * 2.0. */ -import { keyBy, pick, isEmpty, isEqual, isUndefined } from 'lodash/fp'; +import { isEmpty, isEqual, isUndefined, keyBy, pick } from 'lodash/fp'; import memoizeOne from 'memoize-one'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { DataViewBase } from '@kbn/es-query'; import { Subscription } from 'rxjs'; import { useKibana } from '../../lib/kibana'; import { - IndexField, - IndexFieldsStrategyResponse, - IndexFieldsStrategyRequest, BrowserField, BrowserFields, -} from '../../../../common/search_strategy/index_fields'; -import { isErrorResponse, isCompleteResponse } from '../../../../../../../src/plugins/data/common'; -import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; + DocValueFields, + IndexField, + IndexFieldsStrategyRequest, + IndexFieldsStrategyResponse, +} from '../../../../../timelines/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; -import { SourcererScopeName } from '../../store/sourcerer/model'; -import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; -import { DocValueFields } from '../../../../common/search_strategy/common'; import { useAppToasts } from '../../hooks/use_app_toasts'; -export { BrowserField, BrowserFields, DocValueFields }; +export type { BrowserField, BrowserFields, DocValueFields }; export const getAllBrowserFields = (browserFields: BrowserFields): Array> => Object.values(browserFields).reduce>>( @@ -45,7 +41,7 @@ export const getAllFieldsByName = ( keyBy('name', getAllBrowserFields(browserFields)); export const getIndexFields = memoizeOne( - (title: string, fields: IndexField[]): IIndexPattern => + (title: string, fields: IndexField[]): DataViewBase => fields && fields.length > 0 ? { fields: fields.map((field) => @@ -95,7 +91,6 @@ export const getDocValueFields = memoizeOne( ...accumulator, { field: field.name, - format: field.format ? field.format : undefined, }, ]; } @@ -119,9 +114,13 @@ interface FetchIndexReturn { docValueFields: DocValueFields[]; indexes: string[]; indexExists: boolean; - indexPatterns: IIndexPattern; + indexPatterns: DataViewBase; } +/** + * Independent index fields hook/request + * returns state directly, no redux + */ export const useFetchIndex = ( indexNames: string[], onlyCheckIfIndicesExist: boolean = false @@ -147,7 +146,7 @@ export const useFetchIndex = ( abortCtrl.current = new AbortController(); setLoading(true); searchSubscription$.current = data.search - .search( + .search, IndexFieldsStrategyResponse>( { indices: iNames, onlyCheckIfIndicesExist }, { abortSignal: abortCtrl.current.signal, @@ -161,7 +160,6 @@ export const useFetchIndex = ( previousIndexesName.current = response.indicesExist; setLoading(false); - setState({ browserFields: getBrowserFields(stringifyIndices, response.indexFields), docValueFields: getDocValueFields(stringifyIndices, response.indexFields), @@ -205,95 +203,3 @@ export const useFetchIndex = ( return [isLoading, state]; }; - -export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { - const { data } = useKibana().services; - const abortCtrl = useRef(new AbortController()); - const searchSubscription$ = useRef(new Subscription()); - const dispatch = useDispatch(); - const indexNamesSelectedSelector = useMemo( - () => sourcererSelectors.getIndexNamesSelectedSelector(), - [] - ); - const { indexNames, previousIndexNames } = useDeepEqualSelector<{ - indexNames: string[]; - previousIndexNames: string; - }>((state) => indexNamesSelectedSelector(state, sourcererScopeName)); - const { addError, addWarning } = useAppToasts(); - - const setLoading = useCallback( - (loading: boolean) => { - dispatch(sourcererActions.setSourcererScopeLoading({ id: sourcererScopeName, loading })); - }, - [dispatch, sourcererScopeName] - ); - - const indexFieldsSearch = useCallback( - (indicesName) => { - const asyncSearch = async () => { - abortCtrl.current = new AbortController(); - setLoading(true); - searchSubscription$.current = data.search - .search( - { indices: indicesName, onlyCheckIfIndicesExist: false }, - { - abortSignal: abortCtrl.current.signal, - strategy: 'indexFields', - } - ) - .subscribe({ - next: (response) => { - if (isCompleteResponse(response)) { - const stringifyIndices = response.indicesExist.sort().join(); - dispatch( - sourcererActions.setSource({ - id: sourcererScopeName, - payload: { - browserFields: getBrowserFields(stringifyIndices, response.indexFields), - docValueFields: getDocValueFields(stringifyIndices, response.indexFields), - errorMessage: null, - id: sourcererScopeName, - indexPattern: getIndexFields(stringifyIndices, response.indexFields), - // If checking for DE signals index, lie and say the index is created (it's - // no longer created on startup, but is created lazily before writing). - indicesExist: - sourcererScopeName === SourcererScopeName.detections - ? true - : response.indicesExist.length > 0, - loading: false, - }, - }) - ); - searchSubscription$.current.unsubscribe(); - } else if (isErrorResponse(response)) { - setLoading(false); - addWarning(i18n.ERROR_BEAT_FIELDS); - searchSubscription$.current.unsubscribe(); - } - }, - error: (msg) => { - setLoading(false); - addError(msg, { - title: i18n.FAIL_BEAT_FIELDS, - }); - searchSubscription$.current.unsubscribe(); - }, - }); - }; - searchSubscription$.current.unsubscribe(); - abortCtrl.current.abort(); - asyncSearch(); - }, - [data.search, dispatch, addError, addWarning, setLoading, sourcererScopeName] - ); - - useEffect(() => { - if (!isEmpty(indexNames) && previousIndexNames !== indexNames.sort().join()) { - indexFieldsSearch(indexNames); - } - return () => { - searchSubscription$.current.unsubscribe(); - abortCtrl.current.abort(); - }; - }, [indexNames, indexFieldsSearch, previousIndexNames]); -}; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts index c176e64da1ba2a..e34972f5226f38 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts +++ b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { DocValueFields } from '../../../../common/search_strategy'; import { BrowserFields } from '../../../../common/search_strategy/index_fields'; @@ -22,6 +23,7 @@ export const mocksSource = { searchable: true, type: 'date', aggregatable: true, + readFromDocValues: true, }, { category: 'agent', @@ -330,7 +332,13 @@ export const mocksSource = { }; export const mockIndexFields = [ - { aggregatable: true, name: '@timestamp', searchable: true, type: 'date' }, + { + aggregatable: true, + name: '@timestamp', + searchable: true, + type: 'date', + readFromDocValues: true, + }, { aggregatable: true, name: 'agent.ephemeral_id', searchable: true, type: 'string' }, { aggregatable: true, name: 'agent.hostname', searchable: true, type: 'string' }, { aggregatable: true, name: 'agent.id', searchable: true, type: 'string' }, @@ -459,6 +467,7 @@ export const mockBrowserFields: BrowserFields = { name: '@timestamp', searchable: true, type: 'date', + readFromDocValues: true, }, }, }, @@ -726,3 +735,12 @@ export const mockDocValueFields: DocValueFields[] = [ format: 'date_time', }, ]; + +export const mockRuntimeMappings: MappingRuntimeFields = { + '@a.runtime.field': { + script: { + source: 'emit("Radical dude: " + doc[\'host.name\'].value)', + }, + type: 'keyword', + }, +}; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx new file mode 100644 index 00000000000000..a0178d45a9e07c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx @@ -0,0 +1,120 @@ +/* + * 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, useEffect, useRef } from 'react'; +import { Subscription } from 'rxjs'; +import { useDispatch } from 'react-redux'; +import memoizeOne from 'memoize-one'; +import { pick } from 'lodash/fp'; +import { useKibana } from '../../lib/kibana'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { sourcererActions } from '../../store/sourcerer'; +import { + DELETED_SECURITY_SOLUTION_DATA_VIEW, + IndexField, + IndexFieldsStrategyRequest, + IndexFieldsStrategyResponse, +} from '../../../../../timelines/common'; +import { + FieldSpec, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; +import * as i18n from './translations'; +import { getBrowserFields, getDocValueFields } from './'; + +const getEsFields = memoizeOne( + (fields: IndexField[]): FieldSpec[] => + fields && fields.length > 0 + ? fields.map((field) => + pick(['name', 'searchable', 'type', 'aggregatable', 'esTypes', 'subType'], field) + ) + : [], + (newArgs, lastArgs) => newArgs[0].length === lastArgs[0].length +); + +export const useDataView = (): { indexFieldsSearch: (selectedDataViewId: string) => void } => { + const { data } = useKibana().services; + const abortCtrl = useRef(new AbortController()); + const searchSubscription$ = useRef(new Subscription()); + const dispatch = useDispatch(); + const { addError, addWarning } = useAppToasts(); + + const setLoading = useCallback( + ({ id, loading }: { id: string; loading: boolean }) => { + dispatch(sourcererActions.setDataViewLoading({ id, loading })); + }, + [dispatch] + ); + + const indexFieldsSearch = useCallback( + (selectedDataViewId: string) => { + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading({ id: selectedDataViewId, loading: true }); + searchSubscription$.current = data.search + .search, IndexFieldsStrategyResponse>( + { + dataViewId: selectedDataViewId, + onlyCheckIfIndicesExist: false, + }, + { + abortSignal: abortCtrl.current.signal, + strategy: 'indexFields', + } + ) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + const patternString = response.indicesExist.sort().join(); + + dispatch( + sourcererActions.setDataView({ + browserFields: getBrowserFields(patternString, response.indexFields), + docValueFields: getDocValueFields(patternString, response.indexFields), + id: selectedDataViewId, + indexFields: getEsFields(response.indexFields), + loading: false, + runtimeMappings: response.runtimeMappings, + }) + ); + searchSubscription$.current.unsubscribe(); + } else if (isErrorResponse(response)) { + setLoading({ id: selectedDataViewId, loading: false }); + addWarning(i18n.ERROR_BEAT_FIELDS); + searchSubscription$.current.unsubscribe(); + } + }, + error: (msg) => { + if (msg.message === DELETED_SECURITY_SOLUTION_DATA_VIEW) { + // reload app if security solution data view is deleted + return location.reload(); + } + setLoading({ id: selectedDataViewId, loading: false }); + addError(msg, { + title: i18n.FAIL_BEAT_FIELDS, + }); + searchSubscription$.current.unsubscribe(); + }, + }); + }; + searchSubscription$.current.unsubscribe(); + abortCtrl.current.abort(); + asyncSearch(); + }, + [addError, addWarning, data.search, dispatch, setLoading] + ); + + useEffect(() => { + return () => { + searchSubscription$.current.unsubscribe(); + abortCtrl.current.abort(); + }; + }, []); + + return { indexFieldsSearch }; +}; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/api.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/api.ts new file mode 100644 index 00000000000000..54008848c2f71b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/api.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 { KibanaServices } from '../../lib/kibana'; +import { SOURCERER_API_URL } from '../../../../common/constants'; +import { KibanaDataView } from '../../store/sourcerer/model'; + +export interface GetSourcererDataView { + signal: AbortSignal; + body: { + patternList: string[]; + }; +} + +export interface SecurityDataView { + defaultDataView: KibanaDataView; + kibanaDataViews: KibanaDataView[]; +} + +export const postSourcererDataView = async ({ + body, + signal, +}: GetSourcererDataView): Promise => + KibanaServices.get().http.fetch(SOURCERER_API_URL, { + method: 'POST', + body: JSON.stringify(body), + signal, + }); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx index ec28326f7d170b..2e060973c431f4 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor } from '@testing-library/react'; import { Provider } from 'react-redux'; -import { getScopeFromPath, useInitSourcerer } from '.'; +import { getScopeFromPath, useInitSourcerer, useSourcererDataView } from '.'; import { mockPatterns } from './mocks'; -// import { SourcererScopeName } from '../../store/sourcerer/model'; import { RouteSpyState } from '../../utils/route/types'; -import { SecurityPageName } from '../../../../common/constants'; -import { createStore, State } from '../../store'; +import { DEFAULT_INDEX_PATTERN, SecurityPageName } from '../../../../common/constants'; +import { createStore } from '../../store'; import { useUserInfo, initialState as userInfoState, @@ -24,8 +24,10 @@ import { kibanaObservable, mockGlobalState, SUB_PLUGINS_REDUCER, + mockSourcererState, } from '../../mock'; -import { SourcererScopeName } from '../../store/sourcerer/model'; +import { SelectedDataView, SourcererScopeName } from '../../store/sourcerer/model'; +import { postSourcererDataView } from './api'; const mockRouteSpy: RouteSpyState = { pageName: SecurityPageName.overview, @@ -37,6 +39,7 @@ const mockRouteSpy: RouteSpyState = { const mockDispatch = jest.fn(); const mockUseUserInfo = useUserInfo as jest.Mock; jest.mock('../../../detections/components/user_info'); +jest.mock('./api'); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); @@ -48,6 +51,7 @@ jest.mock('react-redux', () => { jest.mock('../../utils/route/use_route_spy', () => ({ useRouteSpy: () => [mockRouteSpy], })); + jest.mock('../../lib/kibana', () => ({ useToasts: jest.fn().mockReturnValue({ addError: jest.fn(), @@ -84,36 +88,13 @@ jest.mock('../../lib/kibana', () => ({ })); describe('Sourcerer Hooks', () => { - const state: State = { - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - sourcererScopes: { - ...mockGlobalState.sourcerer.sourcererScopes, - [SourcererScopeName.default]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], - indexPattern: { - fields: [], - title: '', - }, - }, - [SourcererScopeName.timeline]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], - indexPattern: { - fields: [], - title: '', - }, - }, - }, - }, - }; const { storage } = createSecuritySolutionStorageMock(); - let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + let store: ReturnType; beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); - store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); mockUseUserInfo.mockImplementation(() => userInfoState); }); it('initializes loading default and timeline index patterns', async () => { @@ -125,34 +106,88 @@ describe('Sourcerer Hooks', () => { rerender(); expect(mockDispatch).toBeCalledTimes(2); expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: 'default', loading: true }, + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { id: 'security-solution', loading: true }, }); expect(mockDispatch.mock.calls[1][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: 'timeline', loading: true }, + type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', + payload: { + id: 'timeline', + selectedDataViewId: 'security-solution', + selectedPatterns: [ + '.siem-signals-spacename', + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + ], + }, }); }); }); it('sets signal index name', async () => { + const mockNewDataViews = { + defaultDataView: mockSourcererState.defaultDataView, + kibanaDataViews: [mockSourcererState.defaultDataView], + }; + (postSourcererDataView as jest.Mock).mockResolvedValue(mockNewDataViews); + + store = createStore( + { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + signalIndexName: null, + defaultDataView: { + ...mockGlobalState.sourcerer.defaultDataView, + title: DEFAULT_INDEX_PATTERN.join(','), + patternList: DEFAULT_INDEX_PATTERN, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); await act(async () => { mockUseUserInfo.mockImplementation(() => ({ ...userInfoState, loading: false, - signalIndexName: 'signals-*', + signalIndexName: mockSourcererState.signalIndexName, })); const { rerender, waitForNextUpdate } = renderHook(() => useInitSourcerer(), { wrapper: ({ children }) => {children}, }); await waitForNextUpdate(); rerender(); - expect(mockDispatch.mock.calls[2][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SIGNAL_INDEX_NAME', - payload: { signalIndexName: 'signals-*' }, - }); - expect(mockDispatch.mock.calls[3][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_INDEX_PATTERNS', - payload: { id: 'timeline', selectedPatterns: ['signals-*'] }, + await waitFor(() => { + expect(mockDispatch.mock.calls[2][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { loading: true }, + }); + expect(mockDispatch.mock.calls[3][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SIGNAL_INDEX_NAME', + payload: { signalIndexName: mockSourcererState.signalIndexName }, + }); + expect(mockDispatch.mock.calls[4][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { + id: mockSourcererState.defaultDataView.id, + loading: true, + }, + }); + expect(mockDispatch.mock.calls[5][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_DATA_VIEWS', + payload: mockNewDataViews, + }); + expect(mockDispatch.mock.calls[6][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { loading: false }, + }); }); }); }); @@ -160,7 +195,7 @@ describe('Sourcerer Hooks', () => { await act(async () => { mockUseUserInfo.mockImplementation(() => ({ ...userInfoState, - signalIndexName: 'signals-*', + signalIndexName: mockSourcererState.signalIndexName, isSignalIndexExists: true, })); const { rerender, waitForNextUpdate } = renderHook( @@ -171,9 +206,130 @@ describe('Sourcerer Hooks', () => { ); await waitForNextUpdate(); rerender(); - expect(mockDispatch.mock.calls[1][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_INDEX_PATTERNS', - payload: { id: 'detections', selectedPatterns: ['signals-*'] }, + expect(mockDispatch.mock.calls[2][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', + payload: { + id: 'detections', + selectedDataViewId: mockSourcererState.defaultDataView.id, + selectedPatterns: [mockSourcererState.signalIndexName], + }, + }); + }); + }); + + describe('useSourcererDataView', () => { + it('Should exclude elastic cloud alias when selected patterns include "logs-*" as an alias', async () => { + await act(async () => { + const { result, rerender, waitForNextUpdate } = renderHook< + SourcererScopeName, + SelectedDataView + >(() => useSourcererDataView(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + rerender(); + expect(result.current.selectedPatterns).toEqual([ + '-*elastic-cloud-logs-*', + ...mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns, + ]); + }); + }); + + it('Should NOT exclude elastic cloud alias when selected patterns does NOT include "logs-*" as an alias', async () => { + await act(async () => { + store = createStore( + { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + selectedPatterns: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + ], + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const { result, rerender, waitForNextUpdate } = renderHook< + SourcererScopeName, + SelectedDataView + >(() => useSourcererDataView(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + rerender(); + expect(result.current.selectedPatterns).toEqual([ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + ]); + }); + }); + + it('Should NOT exclude elastic cloud alias when selected patterns include "logs-endpoint.event-*" as an alias', async () => { + await act(async () => { + store = createStore( + { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + selectedPatterns: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + 'logs-endpoint.event-*', + ], + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const { result, rerender, waitForNextUpdate } = renderHook< + SourcererScopeName, + SelectedDataView + >(() => useSourcererDataView(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + rerender(); + expect(result.current.selectedPatterns).toEqual([ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-endpoint.event-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + ]); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index d804f350a7f797..4ca8bf037261af 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -5,133 +5,298 @@ * 2.0. */ -import { useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; - +import { i18n } from '@kbn/i18n'; import { matchPath } from 'react-router-dom'; import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; -import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useIndexFields } from '../source'; +import { SelectedDataView, SourcererScopeName } from '../../store/sourcerer/model'; import { useUserInfo } from '../../../detections/components/user_info'; import { timelineSelectors } from '../../../timelines/store/timeline'; -import { ALERTS_PATH, RULES_PATH, UEBA_PATH } from '../../../../common/constants'; +import { ALERTS_PATH, CASES_PATH, RULES_PATH, UEBA_PATH } from '../../../../common/constants'; import { TimelineId } from '../../../../common'; import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { getScopePatternListSelection } from '../../store/sourcerer/helpers'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { postSourcererDataView } from './api'; +import { useDataView } from '../source/use_data_view'; export const useInitSourcerer = ( scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default ) => { const dispatch = useDispatch(); + const abortCtrl = useRef(new AbortController()); const initialTimelineSourcerer = useRef(true); const initialDetectionSourcerer = useRef(true); const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo(); - const getConfigIndexPatternsSelector = useMemo( - () => sourcererSelectors.configIndexPatternsSelector(), + const getDefaultDataViewSelector = useMemo( + () => sourcererSelectors.defaultDataViewSelector(), [] ); - const ConfigIndexPatterns = useDeepEqualSelector(getConfigIndexPatternsSelector); + const defaultDataView = useDeepEqualSelector(getDefaultDataViewSelector); + + const { addError } = useAppToasts(); + + useEffect(() => { + if (defaultDataView.error != null) { + addError(defaultDataView.error, { + title: i18n.translate('xpack.securitySolution.sourcerer.permissions.title', { + defaultMessage: 'Write role required to generate data', + }), + toastMessage: i18n.translate('xpack.securitySolution.sourcerer.permissions.toastMessage', { + defaultMessage: + 'Users with write permission need to access the Elastic Security app to initialize the app source data.', + }), + }); + } + }, [addError, defaultDataView.error]); const getSignalIndexNameSelector = useMemo( () => sourcererSelectors.signalIndexNameSelector(), [] ); - const signalIndexNameSelector = useDeepEqualSelector(getSignalIndexNameSelector); + const signalIndexNameSourcerer = useDeepEqualSelector(getSignalIndexNameSelector); const getTimelineSelector = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const activeTimeline = useDeepEqualSelector((state) => getTimelineSelector(state, TimelineId.active) ); + const scopeIdSelector = useMemo(() => sourcererSelectors.scopeIdSelector(), []); + const { selectedDataViewId: scopeDataViewId } = useDeepEqualSelector((state) => + scopeIdSelector(state, scopeId) + ); + const { selectedDataViewId: timelineDataViewId } = useDeepEqualSelector((state) => + scopeIdSelector(state, SourcererScopeName.timeline) + ); + const activeDataViewIds = useMemo( + () => [...new Set([scopeDataViewId, timelineDataViewId])], + [scopeDataViewId, timelineDataViewId] + ); + const { indexFieldsSearch } = useDataView(); - useIndexFields(scopeId); - useIndexFields(SourcererScopeName.timeline); - - useEffect(() => { - if (!loadingSignalIndex && signalIndexName != null && signalIndexNameSelector == null) { - dispatch(sourcererActions.setSignalIndexName({ signalIndexName })); - } - }, [dispatch, loadingSignalIndex, signalIndexName, signalIndexNameSelector]); + useEffect( + () => activeDataViewIds.forEach((id) => id != null && id.length > 0 && indexFieldsSearch(id)), + [activeDataViewIds, indexFieldsSearch] + ); // Related to timeline useEffect(() => { if ( !loadingSignalIndex && signalIndexName != null && - signalIndexNameSelector == null && + signalIndexNameSourcerer == null && (activeTimeline == null || activeTimeline.savedObjectId == null) && - initialTimelineSourcerer.current + initialTimelineSourcerer.current && + defaultDataView.id.length > 0 ) { initialTimelineSourcerer.current = false; dispatch( - sourcererActions.setSelectedIndexPatterns({ + sourcererActions.setSelectedDataView({ id: SourcererScopeName.timeline, - selectedPatterns: [...ConfigIndexPatterns, signalIndexName], + selectedDataViewId: defaultDataView.id, + selectedPatterns: getScopePatternListSelection( + defaultDataView, + SourcererScopeName.timeline, + signalIndexName, + true + ), }) ); } else if ( - signalIndexNameSelector != null && + signalIndexNameSourcerer != null && (activeTimeline == null || activeTimeline.savedObjectId == null) && - initialTimelineSourcerer.current + initialTimelineSourcerer.current && + defaultDataView.id.length > 0 ) { initialTimelineSourcerer.current = false; dispatch( - sourcererActions.setSelectedIndexPatterns({ + sourcererActions.setSelectedDataView({ id: SourcererScopeName.timeline, - selectedPatterns: [...ConfigIndexPatterns, signalIndexNameSelector], + selectedDataViewId: defaultDataView.id, + selectedPatterns: getScopePatternListSelection( + defaultDataView, + SourcererScopeName.timeline, + signalIndexNameSourcerer, + true + ), }) ); } }, [ activeTimeline, - ConfigIndexPatterns, + defaultDataView, dispatch, loadingSignalIndex, signalIndexName, - signalIndexNameSelector, + signalIndexNameSourcerer, ]); + const updateSourcererDataView = useCallback( + (newSignalsIndex: string) => { + const asyncSearch = async (newPatternList: string[]) => { + abortCtrl.current = new AbortController(); + + dispatch(sourcererActions.setSourcererScopeLoading({ loading: true })); + try { + const response = await postSourcererDataView({ + body: { patternList: newPatternList }, + signal: abortCtrl.current.signal, + }); + + if (response.defaultDataView.patternList.includes(newSignalsIndex)) { + // first time signals is defined and validated in the sourcerer + // redo indexFieldsSearch + indexFieldsSearch(response.defaultDataView.id); + } + dispatch(sourcererActions.setSourcererDataViews(response)); + dispatch(sourcererActions.setSourcererScopeLoading({ loading: false })); + } catch (err) { + addError(err, { + title: i18n.translate('xpack.securitySolution.sourcerer.error.title', { + defaultMessage: 'Error updating Security Data View', + }), + toastMessage: i18n.translate('xpack.securitySolution.sourcerer.error.toastMessage', { + defaultMessage: 'Refresh the page', + }), + }); + dispatch(sourcererActions.setSourcererScopeLoading({ loading: false })); + } + }; + + if (defaultDataView.title.indexOf(newSignalsIndex) === -1) { + abortCtrl.current.abort(); + asyncSearch([...defaultDataView.title.split(','), newSignalsIndex]); + } + }, + [defaultDataView.title, dispatch, indexFieldsSearch, addError] + ); + useEffect(() => { + if ( + !loadingSignalIndex && + signalIndexName != null && + signalIndexNameSourcerer == null && + defaultDataView.id.length > 0 + ) { + // update signal name also updates sourcerer + // we hit this the first time signal index is created + updateSourcererDataView(signalIndexName); + dispatch(sourcererActions.setSignalIndexName({ signalIndexName })); + } + }, [ + defaultDataView.id, + dispatch, + indexFieldsSearch, + isSignalIndexExists, + loadingSignalIndex, + signalIndexName, + signalIndexNameSourcerer, + updateSourcererDataView, + ]); // Related to the detection page useEffect(() => { if ( scopeId === SourcererScopeName.detections && isSignalIndexExists && signalIndexName != null && - initialDetectionSourcerer.current + initialDetectionSourcerer.current && + defaultDataView.id.length > 0 ) { initialDetectionSourcerer.current = false; dispatch( - sourcererActions.setSelectedIndexPatterns({ - id: scopeId, - selectedPatterns: [signalIndexName], + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.detections, + selectedDataViewId: defaultDataView.id, + selectedPatterns: getScopePatternListSelection( + defaultDataView, + SourcererScopeName.detections, + signalIndexName, + true + ), }) ); } else if ( scopeId === SourcererScopeName.detections && - signalIndexNameSelector != null && - initialTimelineSourcerer.current + signalIndexNameSourcerer != null && + initialTimelineSourcerer.current && + defaultDataView.id.length > 0 ) { initialDetectionSourcerer.current = false; - dispatch( - sourcererActions.setSelectedIndexPatterns({ - id: scopeId, - selectedPatterns: [signalIndexNameSelector], - }) - ); + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.detections, + selectedDataViewId: defaultDataView.id, + selectedPatterns: getScopePatternListSelection( + defaultDataView, + SourcererScopeName.detections, + signalIndexNameSourcerer, + true + ), + }); } - }, [dispatch, isSignalIndexExists, scopeId, signalIndexName, signalIndexNameSelector]); + }, [ + defaultDataView, + dispatch, + isSignalIndexExists, + scopeId, + signalIndexName, + signalIndexNameSourcerer, + ]); }; -export const useSourcererScope = (scope: SourcererScopeName = SourcererScopeName.default) => { +const LOGS_WILDCARD_INDEX = 'logs-*'; +export const EXCLUDE_ELASTIC_CLOUD_INDEX = '-*elastic-cloud-logs-*'; + +export const useSourcererDataView = ( + scopeId: SourcererScopeName = SourcererScopeName.default +): SelectedDataView => { const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); - return useDeepEqualSelector((state) => sourcererScopeSelector(state, scope)); + const { + signalIndexName, + sourcererDataView: selectedDataView, + sourcererScope: { selectedPatterns: scopeSelectedPatterns, loading }, + }: sourcererSelectors.SourcererScopeSelector = useDeepEqualSelector((state) => + sourcererScopeSelector(state, scopeId) + ); + + const selectedPatterns = useMemo( + () => + scopeSelectedPatterns.some((index) => index === LOGS_WILDCARD_INDEX) + ? [...scopeSelectedPatterns, EXCLUDE_ELASTIC_CLOUD_INDEX] + : scopeSelectedPatterns, + [scopeSelectedPatterns] + ); + + return useMemo( + () => ({ + browserFields: selectedDataView.browserFields, + dataViewId: selectedDataView.id, + docValueFields: selectedDataView.docValueFields, + indexPattern: { + fields: selectedDataView.indexFields, + title: selectedPatterns.join(','), + }, + indicesExist: + scopeId === SourcererScopeName.detections + ? selectedDataView.patternList.includes(`${signalIndexName}`) + : scopeId === SourcererScopeName.default + ? selectedDataView.patternList.filter((i) => i !== signalIndexName).length > 0 + : selectedDataView.patternList.length > 0, + loading: loading || selectedDataView.loading, + runtimeMappings: selectedDataView.runtimeMappings, + // all active & inactive patterns in DATA_VIEW + patternList: selectedDataView.title.split(','), + // selected patterns in DATA_VIEW + selectedPatterns: selectedPatterns.sort(), + }), + [loading, selectedPatterns, signalIndexName, scopeId, selectedDataView] + ); }; export const getScopeFromPath = ( pathname: string -): SourcererScopeName.default | SourcererScopeName.detections => { - return matchPath(pathname, { - path: [ALERTS_PATH, `${RULES_PATH}/id/:id`, `${UEBA_PATH}/:id`], +): SourcererScopeName.default | SourcererScopeName.detections => + matchPath(pathname, { + path: [ALERTS_PATH, `${RULES_PATH}/id/:id`, `${UEBA_PATH}/:id`, `${CASES_PATH}/:detailName`], strict: false, }) == null ? SourcererScopeName.default : SourcererScopeName.detections; -}; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.test.tsx new file mode 100644 index 00000000000000..a387bd2211a663 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + TestProviders, +} from '../../mock'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { useSignalHelpers } from './use_signal_helpers'; +import { createStore, State } from '../../store'; + +describe('useSignalHelpers', () => { + const wrapperContainer: React.FC<{ children?: React.ReactNode }> = ({ children }) => ( + {children} + ); + + test('Default state, does not need init and does not need poll', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { + wrapper: wrapperContainer, + }); + await waitForNextUpdate(); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).toEqual(undefined); + }); + }); + test('Needs init and does not need poll when signal index is not yet in default data view', async () => { + const state: State = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + defaultDataView: { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + kibanaDataViews: [ + { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + ], + }, + }; + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + expect(result.current.signalIndexNeedsInit).toEqual(true); + expect(result.current.pollForSignalIndex).toEqual(undefined); + }); + }); + test('Init happened and signal index does not have data yet, poll function becomes available', async () => { + const state: State = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + defaultDataView: { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*,${mockGlobalState.sourcerer.signalIndexName}`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + kibanaDataViews: [ + { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*,${mockGlobalState.sourcerer.signalIndexName}`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + ], + }, + }; + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).not.toEqual(undefined); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.tsx new file mode 100644 index 00000000000000..602997361f38b9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.tsx @@ -0,0 +1,92 @@ +/* + * 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, useMemo, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useDispatch } from 'react-redux'; +import { sourcererSelectors } from '../../store'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { useSourcererDataView } from '.'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { postSourcererDataView } from './api'; +import { sourcererActions } from '../../store/sourcerer'; +import { useDataView } from '../source/use_data_view'; +import { useAppToasts } from '../../hooks/use_app_toasts'; + +export const useSignalHelpers = (): { + /* when defined, signal index has been initiated but does not exist */ + pollForSignalIndex?: () => void; + /* when false, signal index has been initiated */ + signalIndexNeedsInit: boolean; +} => { + const { indicesExist } = useSourcererDataView(SourcererScopeName.detections); + const { indexFieldsSearch } = useDataView(); + const dispatch = useDispatch(); + const { addError } = useAppToasts(); + const abortCtrl = useRef(new AbortController()); + + const getDefaultDataViewSelector = useMemo( + () => sourcererSelectors.defaultDataViewSelector(), + [] + ); + const getSignalIndexNameSelector = useMemo( + () => sourcererSelectors.signalIndexNameSelector(), + [] + ); + const signalIndexNameSourcerer = useDeepEqualSelector(getSignalIndexNameSelector); + const defaultDataView = useDeepEqualSelector(getDefaultDataViewSelector); + + const signalIndexNeedsInit = useMemo( + () => !defaultDataView.title.includes(`${signalIndexNameSourcerer}`), + [defaultDataView.title, signalIndexNameSourcerer] + ); + const shouldWePollForIndex = useMemo( + () => !indicesExist && !signalIndexNeedsInit, + [indicesExist, signalIndexNeedsInit] + ); + + const pollForSignalIndex = useCallback(() => { + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + try { + const response = await postSourcererDataView({ + body: { patternList: defaultDataView.title.split(',') }, + signal: abortCtrl.current.signal, + }); + + if ( + signalIndexNameSourcerer !== null && + response.defaultDataView.patternList.includes(signalIndexNameSourcerer) + ) { + // first time signals is defined and validated in the sourcerer + // redo indexFieldsSearch + indexFieldsSearch(response.defaultDataView.id); + dispatch(sourcererActions.setSourcererDataViews(response)); + } + } catch (err) { + addError(err, { + title: i18n.translate('xpack.securitySolution.sourcerer.error.title', { + defaultMessage: 'Error updating Security Data View', + }), + toastMessage: i18n.translate('xpack.securitySolution.sourcerer.error.toastMessage', { + defaultMessage: 'Refresh the page', + }), + }); + } + }; + + if (signalIndexNameSourcerer !== null) { + abortCtrl.current.abort(); + asyncSearch(); + } + }, [addError, defaultDataView.title, dispatch, indexFieldsSearch, signalIndexNameSourcerer]); + + return { + ...(shouldWePollForIndex ? { pollForSignalIndex } : {}), + signalIndexNeedsInit, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts index 1e7574cdae26d6..b9f2f9a297bce9 100644 --- a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts @@ -14,12 +14,12 @@ import { buildEsQuery, toElasticsearchQuery, fromKueryExpression, - IndexPatternBase, + DataViewBase, } from '@kbn/es-query'; export const convertKueryToElasticSearchQuery = ( kueryExpression: string, - indexPattern?: IndexPatternBase + indexPattern?: DataViewBase ) => { try { return kueryExpression @@ -30,10 +30,7 @@ export const convertKueryToElasticSearchQuery = ( } }; -export const convertKueryToDslFilter = ( - kueryExpression: string, - indexPattern: IndexPatternBase -) => { +export const convertKueryToDslFilter = (kueryExpression: string, indexPattern: DataViewBase) => { try { return kueryExpression ? toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern) @@ -75,7 +72,7 @@ export const convertToBuildEsQuery = ({ filters, }: { config: EsQueryConfig; - indexPattern: IndexPatternBase; + indexPattern: DataViewBase; queries: Query[]; filters: Filter[]; }): [string, undefined] | [undefined, Error] => { diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts index 6185be0663a9a5..b0765f3abaf5eb 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts @@ -161,7 +161,7 @@ export const httpHandlerMockFactory = ['responseProvider'] = mocks.reduce( (providers, routeMock) => { // FIXME: find a way to remove the ignore below. May need to limit the calling signature of `RouteMock['handler']` - // @ts-ignore + // @ts-expect-error TS2322 const routeResponseCallbackMock: SingleResponseProvider = jest.fn( routeMock.handler ); @@ -210,7 +210,7 @@ export const httpHandlerMockFactory = ({ KibanaServices: { get: jest.fn(() => ({ uiSettings: { get: () => ({ from: 'now-24h', to: 'now' }) } })), @@ -21,26 +26,38 @@ describe('createInitialState', () => { SecuritySubPlugins['store']['initialState'], 'app' | 'dragAndDrop' | 'inputs' | 'sourcerer' >; - test('indicesExist should be TRUE if configIndexPatterns is NOT empty', () => { - const initState = createInitialState(mockPluginState, { - kibanaIndexPatterns: [{ id: '1234567890987654321', title: 'mock-kibana' }], - configIndexPatterns: ['auditbeat-*', 'filebeat'], - signalIndexName: 'siem-signals-default', - enableExperimental: parseExperimentalConfigValue([]), - }); + const defaultState = { + defaultDataView: mockSourcererState.defaultDataView, + enableExperimental: parseExperimentalConfigValue([]), + kibanaDataViews: [mockSourcererState.defaultDataView], + signalIndexName: 'siem-signals-default', + }; + const initState = createInitialState(mockPluginState, defaultState); + beforeEach(() => { + (useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(initState)); + }); + afterEach(() => { + (useDeepEqualSelector as jest.Mock).mockClear(); + }); - expect(initState.sourcerer?.sourcererScopes.default.indicesExist).toEqual(true); + test('indicesExist should be TRUE if configIndexPatterns is NOT empty', async () => { + const { result } = renderHook(() => useSourcererDataView()); + expect(result.current.indicesExist).toEqual(true); }); test('indicesExist should be FALSE if configIndexPatterns is empty', () => { - const initState = createInitialState(mockPluginState, { - kibanaIndexPatterns: [{ id: '1234567890987654321', title: 'mock-kibana' }], - configIndexPatterns: [], - signalIndexName: 'siem-signals-default', - enableExperimental: parseExperimentalConfigValue([]), + const state = createInitialState(mockPluginState, { + ...defaultState, + defaultDataView: { + ...defaultState.defaultDataView, + id: '', + title: '', + patternList: [], + }, }); - - expect(initState.sourcerer?.sourcererScopes.default.indicesExist).toEqual(false); + (useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(state)); + const { result } = renderHook(() => useSourcererDataView()); + expect(result.current.indicesExist).toEqual(false); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts index 4a6009b2e41a8b..9faa00ae20bb81 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts @@ -21,15 +21,15 @@ import { SecuritySubPlugins } from '../../app/types'; import { ManagementPluginReducer } from '../../management'; import { State } from './types'; import { AppAction } from './actions'; -import { KibanaIndexPatterns } from './sourcerer/model'; +import { initDataView, SourcererModel, SourcererScopeName } from './sourcerer/model'; import { ExperimentalFeatures } from '../../../common/experimental_features'; +import { getScopePatternListSelection } from './sourcerer/helpers'; export type SubPluginsInitReducer = HostsPluginReducer & UebaPluginReducer & NetworkPluginReducer & TimelinePluginReducer & ManagementPluginReducer; - /** * Factory for the 'initialState' that is used to preload state into the Security App's redux store. */ @@ -39,17 +39,38 @@ export const createInitialState = ( 'app' | 'dragAndDrop' | 'inputs' | 'sourcerer' >, { - kibanaIndexPatterns, - configIndexPatterns, + defaultDataView, + kibanaDataViews, signalIndexName, enableExperimental, }: { - kibanaIndexPatterns: KibanaIndexPatterns; - configIndexPatterns: string[]; - signalIndexName: string | null; + defaultDataView: SourcererModel['defaultDataView']; + kibanaDataViews: SourcererModel['kibanaDataViews']; + signalIndexName: SourcererModel['signalIndexName']; enableExperimental: ExperimentalFeatures; } ): State => { + const initialPatterns = { + [SourcererScopeName.default]: getScopePatternListSelection( + defaultDataView, + SourcererScopeName.default, + signalIndexName, + true + ), + [SourcererScopeName.detections]: getScopePatternListSelection( + defaultDataView, + SourcererScopeName.detections, + signalIndexName, + true + ), + [SourcererScopeName.timeline]: getScopePatternListSelection( + defaultDataView, + SourcererScopeName.timeline, + signalIndexName, + true + ), + }; + const preloadedState: State = { ...pluginsInitState, app: { ...initialAppState, enableExperimental }, @@ -59,13 +80,24 @@ export const createInitialState = ( ...sourcererModel.initialSourcererState, sourcererScopes: { ...sourcererModel.initialSourcererState.sourcererScopes, - default: { + [SourcererScopeName.default]: { ...sourcererModel.initialSourcererState.sourcererScopes.default, - indicesExist: configIndexPatterns.length > 0, + selectedDataViewId: defaultDataView.id, + selectedPatterns: initialPatterns[SourcererScopeName.default], + }, + [SourcererScopeName.detections]: { + ...sourcererModel.initialSourcererState.sourcererScopes.detections, + selectedDataViewId: defaultDataView.id, + selectedPatterns: initialPatterns[SourcererScopeName.detections], + }, + [SourcererScopeName.timeline]: { + ...sourcererModel.initialSourcererState.sourcererScopes.timeline, + selectedDataViewId: defaultDataView.id, + selectedPatterns: initialPatterns[SourcererScopeName.timeline], }, }, - kibanaIndexPatterns, - configIndexPatterns, + defaultDataView, + kibanaDataViews: kibanaDataViews.map((dataView) => ({ ...initDataView, ...dataView })), signalIndexName, }, }; diff --git a/x-pack/plugins/security_solution/public/common/store/routing/index.ts b/x-pack/plugins/security_solution/public/common/store/routing/index.ts index 1b13b70063f55b..2ea4a93a817583 100644 --- a/x-pack/plugins/security_solution/public/common/store/routing/index.ts +++ b/x-pack/plugins/security_solution/public/common/store/routing/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { RoutingAction } from './action'; +export type { RoutingAction } from './action'; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts index 5162776d3ddf5e..aa0689de9cca3e 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts @@ -8,35 +8,39 @@ import actionCreatorFactory from 'typescript-fsa'; import { TimelineEventsType } from '../../../../common/types/timeline'; -import { KibanaIndexPatterns, ManageScopeInit, SourcererScopeName } from './model'; +import { SourcererDataView, SourcererScopeName } from './model'; +import { SecurityDataView } from '../../containers/sourcerer/api'; const actionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer'); -export const setSource = actionCreator<{ - id: SourcererScopeName; - payload: ManageScopeInit; -}>('SET_SOURCE'); +export const setDataView = actionCreator<{ + browserFields: SourcererDataView['browserFields']; + docValueFields: SourcererDataView['docValueFields']; + id: SourcererDataView['id']; + indexFields: SourcererDataView['indexFields']; + loading: SourcererDataView['loading']; + runtimeMappings: SourcererDataView['runtimeMappings']; +}>('SET_DATA_VIEW'); -export const setIndexPatternsList = actionCreator<{ - kibanaIndexPatterns: KibanaIndexPatterns; - configIndexPatterns: string[]; -}>('SET_INDEX_PATTERNS_LIST'); +export const setDataViewLoading = actionCreator<{ + id: string; + loading: boolean; +}>('SET_DATA_VIEW_LOADING'); export const setSignalIndexName = actionCreator<{ signalIndexName: string }>('SET_SIGNAL_INDEX_NAME'); -export const setSourcererScopeLoading = actionCreator<{ id: SourcererScopeName; loading: boolean }>( - 'SET_SOURCERER_SCOPE_LOADING' -); +export const setSourcererDataViews = actionCreator('SET_SOURCERER_DATA_VIEWS'); -export const setSelectedIndexPatterns = actionCreator<{ - id: SourcererScopeName; - selectedPatterns: string[]; - eventType?: TimelineEventsType; -}>('SET_SELECTED_INDEX_PATTERNS'); +export const setSourcererScopeLoading = actionCreator<{ + id?: SourcererScopeName; + loading: boolean; +}>('SET_SOURCERER_SCOPE_LOADING'); -export const initTimelineIndexPatterns = actionCreator<{ +export interface SelectedDataViewPayload { id: SourcererScopeName; + selectedDataViewId: string; selectedPatterns: string[]; eventType?: TimelineEventsType; -}>('INIT_TIMELINE_INDEX_PATTERNS'); +} +export const setSelectedDataView = actionCreator('SET_SELECTED_DATA_VIEW'); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts index f498cb640317a9..83a556442f849b 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts @@ -5,58 +5,242 @@ * 2.0. */ -import { createDefaultIndexPatterns, Args } from './helpers'; -import { initialSourcererState, SourcererScopeName } from './model'; +import { mockGlobalState } from '../../mock'; +import { SourcererScopeName } from './model'; +import { + defaultDataViewByEventType, + getScopePatternListSelection, + validateSelectedPatterns, +} from './helpers'; -let defaultArgs: Args = { - eventType: 'all', - id: SourcererScopeName.default, - selectedPatterns: ['auditbeat-*', 'packetbeat-*'], - state: { - ...initialSourcererState, - configIndexPatterns: ['filebeat-*', 'auditbeat-*', 'packetbeat-*'], - kibanaIndexPatterns: [{ id: '123', title: 'journalbeat-*' }], - signalIndexName: 'signals-*', - }, +const signalIndexName = mockGlobalState.sourcerer.signalIndexName; + +const dataView = { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*,${signalIndexName}`, + patternList: ['packetbeat-*', 'auditbeat-*', `${signalIndexName}`], }; -const eventTypes: Array = ['all', 'raw', 'alert', 'signal', 'custom']; -const ids: Array = [ - SourcererScopeName.default, - SourcererScopeName.detections, - SourcererScopeName.timeline, -]; -describe('createDefaultIndexPatterns', () => { - ids.forEach((id) => { - eventTypes.forEach((et) => { - describe(`id: ${id}, eventType: ${et}`, () => { - beforeEach(() => { - defaultArgs = { - ...defaultArgs, - id, - eventType: et, - }; +const patternListNoSignals = mockGlobalState.sourcerer.defaultDataView.patternList + .filter((p) => p !== signalIndexName) + .sort(); +const patternListSignals = [ + signalIndexName, + ...mockGlobalState.sourcerer.defaultDataView.patternList.filter((p) => p !== signalIndexName), +].sort(); + +describe('sourcerer store helpers', () => { + describe('getScopePatternListSelection', () => { + it('is not a default data view, returns patternList sorted', () => { + const result = getScopePatternListSelection( + { + ...dataView, + id: '1234', + }, + SourcererScopeName.default, + signalIndexName, + false + ); + expect(result).toEqual([`${signalIndexName}`, 'auditbeat-*', 'packetbeat-*']); + }); + it('default data view, SourcererScopeName.timeline, returns patternList sorted', () => { + const result = getScopePatternListSelection( + dataView, + SourcererScopeName.timeline, + signalIndexName, + true + ); + expect(result).toEqual([signalIndexName, 'auditbeat-*', 'packetbeat-*']); + }); + it('default data view, SourcererScopeName.default, returns patternList sorted without signals index', () => { + const result = getScopePatternListSelection( + dataView, + SourcererScopeName.default, + signalIndexName, + true + ); + expect(result).toEqual(['auditbeat-*', 'packetbeat-*']); + }); + it('default data view, SourcererScopeName.detections, returns patternList with only signals index', () => { + const result = getScopePatternListSelection( + dataView, + SourcererScopeName.detections, + signalIndexName, + true + ); + expect(result).toEqual([signalIndexName]); + }); + }); + describe('validateSelectedPatterns', () => { + const payload = { + id: SourcererScopeName.default, + selectedDataViewId: dataView.id, + selectedPatterns: ['auditbeat-*'], + }; + it('sets selectedPattern', () => { + const result = validateSelectedPatterns(mockGlobalState.sourcerer, payload); + expect(result).toEqual({ + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + selectedPatterns: ['auditbeat-*'], + }, + }); + }); + it('sets to default when empty array is passed and scope is default', () => { + const result = validateSelectedPatterns(mockGlobalState.sourcerer, { + ...payload, + selectedPatterns: [], + }); + expect(result).toEqual({ + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + selectedPatterns: patternListNoSignals, + }, + }); + }); + it('sets to default when empty array is passed and scope is detections', () => { + const result = validateSelectedPatterns(mockGlobalState.sourcerer, { + ...payload, + id: SourcererScopeName.detections, + selectedPatterns: [], + }); + expect(result).toEqual({ + [SourcererScopeName.detections]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.detections], + selectedDataViewId: dataView.id, + selectedPatterns: [signalIndexName], + }, + }); + }); + it('sets to default when empty array is passed and scope is timeline', () => { + const result = validateSelectedPatterns(mockGlobalState.sourcerer, { + ...payload, + id: SourcererScopeName.timeline, + selectedPatterns: [], + }); + expect(result).toEqual({ + [SourcererScopeName.timeline]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], + selectedDataViewId: dataView.id, + selectedPatterns: [ + signalIndexName, + ...mockGlobalState.sourcerer.defaultDataView.patternList.filter( + (p) => p !== signalIndexName + ), + ].sort(), + }, + }); + }); + describe('handles missing dataViewId, 7.16 -> 8.0', () => { + it('selectedPatterns.length > 0 & all selectedPatterns exist in defaultDataView, set dataViewId to defaultDataView.id', () => { + const result = validateSelectedPatterns(mockGlobalState.sourcerer, { + ...payload, + id: SourcererScopeName.timeline, + selectedDataViewId: '', + selectedPatterns: [ + mockGlobalState.sourcerer.defaultDataView.patternList[3], + mockGlobalState.sourcerer.defaultDataView.patternList[4], + ], }); - it('Selected patterns', () => { - const result = createDefaultIndexPatterns(defaultArgs); - expect(result).toEqual(['auditbeat-*', 'packetbeat-*']); + expect(result).toEqual({ + [SourcererScopeName.timeline]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], + selectedDataViewId: dataView.id, + selectedPatterns: [ + mockGlobalState.sourcerer.defaultDataView.patternList[3], + mockGlobalState.sourcerer.defaultDataView.patternList[4], + ], + }, }); - it('No selected patterns', () => { - const newArgs = { - ...defaultArgs, - selectedPatterns: [], - }; - const result = createDefaultIndexPatterns(newArgs); - if ( - id === SourcererScopeName.detections || - (id === SourcererScopeName.timeline && (et === 'alert' || et === 'signal')) - ) { - expect(result).toEqual(['signals-*']); - } else if (id === SourcererScopeName.timeline && et === 'all') { - expect(result).toEqual(['filebeat-*', 'auditbeat-*', 'packetbeat-*', 'signals-*']); - } else { - expect(result).toEqual(['filebeat-*', 'auditbeat-*', 'packetbeat-*']); - } + }); + it('selectedPatterns.length > 0 & a pattern in selectedPatterns does not exist in defaultDataView, set dataViewId to null', () => { + const result = validateSelectedPatterns(mockGlobalState.sourcerer, { + ...payload, + id: SourcererScopeName.timeline, + selectedDataViewId: '', + selectedPatterns: [ + mockGlobalState.sourcerer.defaultDataView.patternList[3], + 'journalbeat-*', + ], }); + expect(result).toEqual({ + [SourcererScopeName.timeline]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], + selectedDataViewId: null, + selectedPatterns: [ + mockGlobalState.sourcerer.defaultDataView.patternList[3], + 'journalbeat-*', + ], + }, + }); + }); + }); + }); + describe('defaultDataViewByEventType', () => { + it('defaults with no eventType', () => { + const result = defaultDataViewByEventType({ state: mockGlobalState.sourcerer }); + expect(result).toEqual({ + selectedDataViewId: dataView.id, + selectedPatterns: patternListSignals, + }); + }); + it('defaults with eventType: all', () => { + const result = defaultDataViewByEventType({ + state: mockGlobalState.sourcerer, + eventType: 'all', + }); + expect(result).toEqual({ + selectedDataViewId: dataView.id, + selectedPatterns: patternListSignals, + }); + }); + it('defaults with eventType: raw', () => { + const result = defaultDataViewByEventType({ + state: mockGlobalState.sourcerer, + eventType: 'raw', + }); + expect(result).toEqual({ + selectedDataViewId: dataView.id, + selectedPatterns: patternListNoSignals, + }); + }); + it('defaults with eventType: alert', () => { + const result = defaultDataViewByEventType({ + state: mockGlobalState.sourcerer, + eventType: 'alert', + }); + expect(result).toEqual({ + selectedDataViewId: dataView.id, + selectedPatterns: [signalIndexName], + }); + }); + it('defaults with eventType: signal', () => { + const result = defaultDataViewByEventType({ + state: mockGlobalState.sourcerer, + eventType: 'signal', + }); + expect(result).toEqual({ + selectedDataViewId: dataView.id, + selectedPatterns: [signalIndexName], + }); + }); + it('defaults with eventType: custom', () => { + const result = defaultDataViewByEventType({ + state: mockGlobalState.sourcerer, + eventType: 'custom', + }); + expect(result).toEqual({ + selectedDataViewId: dataView.id, + selectedPatterns: patternListSignals, + }); + }); + it('defaults with eventType: eql', () => { + const result = defaultDataViewByEventType({ + state: mockGlobalState.sourcerer, + eventType: 'eql', + }); + expect(result).toEqual({ + selectedDataViewId: dataView.id, + selectedPatterns: patternListSignals, }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts index fd3a6f1fab3c11..1b4efa72127f3e 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts @@ -6,8 +6,9 @@ */ import { isEmpty } from 'lodash'; -import { SourcererModel, SourcererScopeName } from './model'; -import { TimelineEventsType } from '../../../../common/types/timeline'; +import { SourcererDataView, SourcererModel, SourcererScopeById, SourcererScopeName } from './model'; +import { TimelineEventsType } from '../../../../common'; +import { SelectedDataViewPayload } from './actions'; export interface Args { eventType?: TimelineEventsType; @@ -15,40 +16,131 @@ export interface Args { selectedPatterns: string[]; state: SourcererModel; } -export const createDefaultIndexPatterns = ({ eventType, id, selectedPatterns, state }: Args) => { - const kibanaIndexPatterns = state.kibanaIndexPatterns.map((kip) => kip.title); - const newSelectedPatterns = selectedPatterns.filter( - (sp) => - state.configIndexPatterns.includes(sp) || - kibanaIndexPatterns.includes(sp) || - (!isEmpty(state.signalIndexName) && state.signalIndexName === sp) - ); - if (isEmpty(newSelectedPatterns)) { - let defaultIndexPatterns = state.configIndexPatterns; - if (id === SourcererScopeName.timeline && isEmpty(newSelectedPatterns)) { - defaultIndexPatterns = defaultIndexPatternByEventType({ state, eventType }); - } else if (id === SourcererScopeName.detections && isEmpty(newSelectedPatterns)) { - defaultIndexPatterns = [state.signalIndexName ?? '']; + +export const getScopePatternListSelection = ( + theDataView: SourcererDataView | undefined, + sourcererScope: SourcererScopeName, + signalIndexName: SourcererModel['signalIndexName'], + isDefaultDataView: boolean +): string[] => { + const patternList: string[] = + theDataView != null && theDataView.id !== null ? theDataView.patternList : []; + + if (!isDefaultDataView) { + return patternList.sort(); + } + // when our SIEM data view is set, here are the defaults + switch (sourcererScope) { + case SourcererScopeName.default: + return patternList.filter((index) => index !== signalIndexName).sort(); + case SourcererScopeName.detections: + // set to signalIndexName whether or not it exists yet in the patternList + return (signalIndexName != null ? [signalIndexName] : []).sort(); + case SourcererScopeName.timeline: + return ( + signalIndexName != null + ? [ + // remove signalIndexName in case its already in there and add it whether or not it exists yet in the patternList + ...patternList.filter((index) => index !== signalIndexName), + signalIndexName, + ] + : patternList + ).sort(); + } +}; + +export const validateSelectedPatterns = ( + state: SourcererModel, + payload: SelectedDataViewPayload +): Partial => { + const { id, eventType, ...rest } = payload; + let dataView = state.kibanaDataViews.find((p) => p.id === rest.selectedDataViewId); + // dedupe because these could come from a silly url or pre 8.0 timeline + const dedupePatterns = [...new Set(rest.selectedPatterns)]; + let selectedPatterns = + dataView != null + ? dedupePatterns.filter( + (pattern) => + // Typescript is being mean and telling me dataView could be undefined here + // so redoing the dataView != null check + (dataView != null && dataView.patternList.includes(pattern)) || + // this is a hack, but sometimes signal index is deleted and is getting regenerated. it gets set before it is put in the dataView + state.signalIndexName == null + ) + : // 7.16 -> 8.0 this will get hit because dataView == null + dedupePatterns; + if (selectedPatterns.length > 0 && dataView == null) { + // we have index patterns, but not a data view id + // find out if we have these index patterns in the defaultDataView + const areAllPatternsInDefault = selectedPatterns.every( + (pattern) => state.defaultDataView.title.indexOf(pattern) > -1 + ); + if (areAllPatternsInDefault) { + dataView = state.defaultDataView; + selectedPatterns = selectedPatterns.filter( + (pattern) => dataView != null && dataView.patternList.includes(pattern) + ); } - return defaultIndexPatterns; } - return newSelectedPatterns; + + // TO DO: Steph/sourcerer If dataView is still undefined here, create temporary dataView + // and prompt user to go create this dataView + // currently UI will take the undefined dataView and default to defaultDataView anyways + // this is a "strategically merged" bug ;) + // https://github.com/elastic/security-team/issues/1921 + + return { + [id]: { + ...state.sourcererScopes[id], + ...rest, + selectedDataViewId: dataView?.id ?? null, + selectedPatterns, + ...(isEmpty(selectedPatterns) + ? id === SourcererScopeName.timeline + ? defaultDataViewByEventType({ state, eventType }) + : { + selectedPatterns: getScopePatternListSelection( + dataView ?? state.defaultDataView, + id, + state.signalIndexName, + (dataView ?? state.defaultDataView).id === state.defaultDataView.id + ), + } + : {}), + loading: false, + }, + }; }; -export const defaultIndexPatternByEventType = ({ +// TODO: Steph/sourcerer eventType will be alerts only, when ui updates delete raw +export const defaultDataViewByEventType = ({ state, eventType, }: { state: SourcererModel; eventType?: TimelineEventsType; }) => { - let defaultIndexPatterns = state.configIndexPatterns; - if (eventType === 'all' && !isEmpty(state.signalIndexName)) { - defaultIndexPatterns = [...state.configIndexPatterns, state.signalIndexName ?? '']; + const { + signalIndexName, + defaultDataView: { id, patternList }, + } = state; + if (signalIndexName != null && (eventType === 'signal' || eventType === 'alert')) { + return { + selectedPatterns: [signalIndexName], + selectedDataViewId: id, + }; } else if (eventType === 'raw') { - defaultIndexPatterns = state.configIndexPatterns; - } else if (!isEmpty(state.signalIndexName) && (eventType === 'signal' || eventType === 'alert')) { - defaultIndexPatterns = [state.signalIndexName ?? '']; + return { + selectedPatterns: patternList.filter((index) => index !== signalIndexName).sort(), + selectedDataViewId: id, + }; } - return defaultIndexPatterns; + return { + selectedPatterns: [ + // remove signalIndexName in case its already in there and add it whether or not it exists yet in the patternList + ...patternList.filter((index) => index !== signalIndexName), + signalIndexName, + ].sort(), + selectedDataViewId: id, + }; }; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts index 8f7fbd99f932d1..a22a04d025d199 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts @@ -5,72 +5,133 @@ * 2.0. */ -import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; -import { DocValueFields } from '../../../../common/search_strategy/common'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { BrowserFields, + DocValueFields, EMPTY_BROWSER_FIELDS, EMPTY_DOCVALUE_FIELD, - EMPTY_INDEX_PATTERN, -} from '../../../../common/search_strategy/index_fields'; - -export type ErrorModel = Error[]; - + EMPTY_INDEX_FIELDS, +} from '../../../../../timelines/common'; +import { SecuritySolutionDataViewBase } from '../../types'; +/** Uniquely identifies a Sourcerer Scope */ export enum SourcererScopeName { default = 'default', detections = 'detections', timeline = 'timeline', } -export interface ManageScope { - browserFields: BrowserFields; - docValueFields: DocValueFields[]; - errorMessage: string | null; +/** + * Data related to each sourcerer scope + */ +export interface SourcererScope { + /** Uniquely identifies a Sourcerer Scope */ id: SourcererScopeName; - indexPattern: Omit; - indicesExist: boolean | undefined | null; + /** is an update being made to the sourcerer data view */ loading: boolean; + /** selected data view id */ + selectedDataViewId: string; + /** selected patterns within the data view */ selectedPatterns: string[]; } -export interface ManageScopeInit extends Partial { - id: SourcererScopeName; +export type SourcererScopeById = Record; + +export interface KibanaDataView { + /** Uniquely identifies a Kibana Data View */ + id: string; + /** list of active patterns that return data */ + patternList: string[]; + /** + * title of Kibana Data View + * title also serves as "all pattern list", including inactive + * comma separated string + */ + title: string; } -export type SourcererScopeById = Record; +/** + * DataView from Kibana + timelines/index_fields enhanced field data + */ +export interface SourcererDataView extends KibanaDataView { + /** we need this for @timestamp data */ + browserFields: BrowserFields; + /** we need this for @timestamp data */ + docValueFields: DocValueFields[]; + /** comes from dataView.fields.toSpec() */ + indexFields: SecuritySolutionDataViewBase['fields']; + /** set when data view fields are fetched */ + loading: boolean; + /** + * Needed to pass to search strategy + * Remove once issue resolved: https://github.com/elastic/kibana/issues/111762 + */ + runtimeMappings: MappingRuntimeFields; +} -export type KibanaIndexPatterns = Array<{ id: string; title: string }>; +/** + * Combined data from SourcererDataView and SourcererScope to create + * selected data view state + */ +export interface SelectedDataView { + browserFields: SourcererDataView['browserFields']; + dataViewId: SourcererDataView['id']; + docValueFields: SourcererDataView['docValueFields']; + /** + * DataViewBase with enhanced index fields used in timelines + */ + indexPattern: SecuritySolutionDataViewBase; + /** do the selected indices exist */ + indicesExist: boolean; + /** is an update being made to the data view */ + loading: boolean; + /** all active & inactive patterns from SourcererDataView['title'] */ + patternList: string[]; + runtimeMappings: SourcererDataView['runtimeMappings']; + /** all selected patterns from SourcererScope['selectedPatterns'] */ + selectedPatterns: string[]; +} -// ManageSourcerer +/** + * sourcerer model for redux + */ export interface SourcererModel { - kibanaIndexPatterns: KibanaIndexPatterns; - configIndexPatterns: string[]; + /** default security-solution data view */ + defaultDataView: SourcererDataView & { error?: unknown }; + /** all Kibana data views, including security-solution */ + kibanaDataViews: SourcererDataView[]; + /** security solution signals index name */ signalIndexName: string | null; + /** sourcerer scope data by id */ sourcererScopes: SourcererScopeById; } -export const initSourcererScope: Pick< - ManageScope, - | 'browserFields' - | 'docValueFields' - | 'errorMessage' - | 'indexPattern' - | 'indicesExist' - | 'loading' - | 'selectedPatterns' -> = { +export type SourcererUrlState = Partial<{ + [id in SourcererScopeName]: { + id: string; + selectedPatterns: string[]; + }; +}>; + +export const initSourcererScope: Omit = { + loading: false, + selectedDataViewId: '', + selectedPatterns: [], +}; +export const initDataView = { browserFields: EMPTY_BROWSER_FIELDS, docValueFields: EMPTY_DOCVALUE_FIELD, - errorMessage: null, - indexPattern: EMPTY_INDEX_PATTERN, - indicesExist: true, + id: '', + indexFields: EMPTY_INDEX_FIELDS, loading: false, - selectedPatterns: [], + patternList: [], + runtimeMappings: {}, + title: '', }; export const initialSourcererState: SourcererModel = { - kibanaIndexPatterns: [], - configIndexPatterns: [], + defaultDataView: initDataView, + kibanaDataViews: [], signalIndexName: null, sourcererScopes: { [SourcererScopeName.default]: { @@ -87,8 +148,3 @@ export const initialSourcererState: SourcererModel = { }, }, }; - -export type FSourcererScopePatterns = { - [id in SourcererScopeName]: string[]; -}; -export type SourcererScopePatterns = Partial; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts index 662671e1dda3ac..e1747a6786cdbe 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts @@ -5,83 +5,89 @@ * 2.0. */ -import { isEmpty } from 'lodash/fp'; import { reducerWithInitialState } from 'typescript-fsa-reducers'; import { - setIndexPatternsList, + setSourcererDataViews, setSourcererScopeLoading, - setSelectedIndexPatterns, + setSelectedDataView, setSignalIndexName, - setSource, - initTimelineIndexPatterns, + setDataView, + setDataViewLoading, } from './actions'; -import { initialSourcererState, SourcererModel } from './model'; -import { createDefaultIndexPatterns, defaultIndexPatternByEventType } from './helpers'; +import { initDataView, initialSourcererState, SourcererModel, SourcererScopeName } from './model'; +import { validateSelectedPatterns } from './helpers'; export type SourcererState = SourcererModel; export const sourcererReducer = reducerWithInitialState(initialSourcererState) - .case(setIndexPatternsList, (state, { kibanaIndexPatterns, configIndexPatterns }) => ({ - ...state, - kibanaIndexPatterns, - configIndexPatterns, - })) .case(setSignalIndexName, (state, { signalIndexName }) => ({ ...state, signalIndexName, })) + .case(setDataViewLoading, (state, { id, loading }) => ({ + ...state, + ...(id === state.defaultDataView.id + ? { + defaultDataView: { ...state.defaultDataView, loading }, + } + : {}), + kibanaDataViews: state.kibanaDataViews.map((dv) => (dv.id === id ? { ...dv, loading } : dv)), + })) + .case(setSourcererDataViews, (state, { defaultDataView, kibanaDataViews }) => ({ + ...state, + defaultDataView: { + ...state.defaultDataView, + ...defaultDataView, + }, + kibanaDataViews: kibanaDataViews.map((dataView) => ({ + ...(state.kibanaDataViews.find(({ id }) => id === dataView.id) ?? initDataView), + ...dataView, + })), + })) .case(setSourcererScopeLoading, (state, { id, loading }) => ({ ...state, sourcererScopes: { ...state.sourcererScopes, - [id]: { - ...state.sourcererScopes[id], - loading, - }, + ...(id != null + ? { + [id]: { + ...state.sourcererScopes[id], + loading, + }, + } + : { + [SourcererScopeName.default]: { + ...state.sourcererScopes[SourcererScopeName.default], + loading, + }, + [SourcererScopeName.detections]: { + ...state.sourcererScopes[SourcererScopeName.detections], + loading, + }, + [SourcererScopeName.timeline]: { + ...state.sourcererScopes[SourcererScopeName.timeline], + loading, + }, + }), }, })) - .case(setSelectedIndexPatterns, (state, { id, selectedPatterns, eventType }) => { - return { - ...state, - sourcererScopes: { - ...state.sourcererScopes, - [id]: { - ...state.sourcererScopes[id], - selectedPatterns: createDefaultIndexPatterns({ eventType, id, selectedPatterns, state }), - }, - }, - }; - }) - .case(initTimelineIndexPatterns, (state, { id, selectedPatterns, eventType }) => { - return { - ...state, - sourcererScopes: { - ...state.sourcererScopes, - [id]: { - ...state.sourcererScopes[id], - selectedPatterns: isEmpty(selectedPatterns) - ? defaultIndexPatternByEventType({ state, eventType }) - : selectedPatterns, - }, - }, - }; - }) - - .case(setSource, (state, { id, payload }) => { - const { ...sourcererScopes } = payload; - return { - ...state, - sourcererScopes: { - ...state.sourcererScopes, - [id]: { - ...state.sourcererScopes[id], - ...sourcererScopes, - ...(state.sourcererScopes[id].selectedPatterns.length === 0 - ? { selectedPatterns: state.configIndexPatterns } - : {}), - }, - }, - }; - }) + .case(setSelectedDataView, (state, payload) => ({ + ...state, + sourcererScopes: { + ...state.sourcererScopes, + ...validateSelectedPatterns(state, payload), + }, + })) + .case(setDataView, (state, dataView) => ({ + ...state, + ...(dataView.id === state.defaultDataView.id + ? { + defaultDataView: { ...state.defaultDataView, ...dataView }, + } + : {}), + kibanaDataViews: state.kibanaDataViews.map((dv) => + dv.id === dataView.id ? { ...dv, ...dataView } : dv + ), + })) .build(); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts deleted file mode 100644 index 1edda4f997cdf2..00000000000000 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts +++ /dev/null @@ -1,75 +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 { cloneDeep } from 'lodash/fp'; -import { mockGlobalState } from '../../mock'; -import { SourcererScopeName } from './model'; -import { getSourcererScopeSelector } from './selectors'; - -describe('Sourcerer selectors', () => { - describe('getSourcererScopeSelector', () => { - it('Should exclude elastic cloud alias when selected patterns include "logs-*" as an alias', () => { - const mapStateToProps = getSourcererScopeSelector(); - expect(mapStateToProps(mockGlobalState, SourcererScopeName.default).selectedPatterns).toEqual( - [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - '-*elastic-cloud-logs-*', - ] - ); - }); - - it('Should NOT exclude elastic cloud alias when selected patterns does NOT include "logs-*" as an alias', () => { - const mapStateToProps = getSourcererScopeSelector(); - const myMockGlobalState = cloneDeep(mockGlobalState); - myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns = - myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns.filter( - (index) => !index.includes('logs-*') - ); - expect( - mapStateToProps(myMockGlobalState, SourcererScopeName.default).selectedPatterns - ).toEqual([ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - ]); - }); - - it('Should NOT exclude elastic cloud alias when selected patterns include "logs-endpoint.event-*" as an alias', () => { - const mapStateToProps = getSourcererScopeSelector(); - const myMockGlobalState = cloneDeep(mockGlobalState); - myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns = [ - ...myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns.filter( - (index) => !index.includes('logs-*') - ), - 'logs-endpoint.event-*', - ]; - expect( - mapStateToProps(myMockGlobalState, SourcererScopeName.default).selectedPatterns - ).toEqual([ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-endpoint.event-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts index 53702bcf23b894..b72d7bfde2dcc0 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts @@ -5,24 +5,34 @@ * 2.0. */ -import memoizeOne from 'memoize-one'; import { createSelector } from 'reselect'; import { State } from '../types'; -import { SourcererScopeById, ManageScope, KibanaIndexPatterns, SourcererScopeName } from './model'; - -export const sourcererKibanaIndexPatternsSelector = ({ sourcerer }: State): KibanaIndexPatterns => - sourcerer.kibanaIndexPatterns; +import { + SourcererDataView, + SourcererModel, + SourcererScope, + SourcererScopeById, + SourcererScopeName, +} from './model'; + +export const sourcererKibanaDataViewsSelector = ({ + sourcerer, +}: State): SourcererModel['kibanaDataViews'] => sourcerer.kibanaDataViews; export const sourcererSignalIndexNameSelector = ({ sourcerer }: State): string | null => sourcerer.signalIndexName; -export const sourcererConfigIndexPatternsSelector = ({ sourcerer }: State): string[] => - sourcerer.configIndexPatterns; +export const sourcererDefaultDataViewSelector = ({ + sourcerer, +}: State): SourcererModel['defaultDataView'] => sourcerer.defaultDataView; + +export const dataViewSelector = ({ sourcerer }: State, id: string): SourcererDataView => + sourcerer.kibanaDataViews.find((dataView) => dataView.id === id) ?? sourcerer.defaultDataView; export const sourcererScopeIdSelector = ( { sourcerer }: State, scopeId: SourcererScopeName -): ManageScope => sourcerer.sourcererScopes[scopeId]; +): SourcererScope => sourcerer.sourcererScopes[scopeId]; export const scopeIdSelector = () => createSelector(sourcererScopeIdSelector, (scope) => scope); @@ -31,85 +41,43 @@ export const sourcererScopesSelector = ({ sourcerer }: State): SourcererScopeByI export const scopesSelector = () => createSelector(sourcererScopesSelector, (scopes) => scopes); -export const kibanaIndexPatternsSelector = () => - createSelector( - sourcererKibanaIndexPatternsSelector, - (kibanaIndexPatterns) => kibanaIndexPatterns - ); +export const kibanaDataViewsSelector = () => + createSelector(sourcererKibanaDataViewsSelector, (dataViews) => dataViews); export const signalIndexNameSelector = () => createSelector(sourcererSignalIndexNameSelector, (signalIndexName) => signalIndexName); -export const configIndexPatternsSelector = () => - createSelector( - sourcererConfigIndexPatternsSelector, - (configIndexPatterns) => configIndexPatterns - ); +export const defaultDataViewSelector = () => + createSelector(sourcererDefaultDataViewSelector, (dataViews) => dataViews); -export const getIndexNamesSelectedSelector = () => { - const getScopeSelector = scopeIdSelector(); - const getConfigIndexPatternsSelector = configIndexPatternsSelector(); +export const sourcererDataViewSelector = () => + createSelector(dataViewSelector, (dataView) => dataView); - const mapStateToProps = ( - state: State, - scopeId: SourcererScopeName - ): { indexNames: string[]; previousIndexNames: string } => { - const scope = getScopeSelector(state, scopeId); - const configIndexPatterns = getConfigIndexPatternsSelector(state); - return { - indexNames: - scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns, - previousIndexNames: scope.indexPattern.title, - }; - }; - return mapStateToProps; -}; +export interface SourcererScopeSelector extends Omit { + sourcererDataView: SourcererDataView; + sourcererScope: SourcererScope; +} -export const getAllExistingIndexNamesSelector = () => { +export const getSourcererScopeSelector = () => { + const getKibanaDataViewsSelector = kibanaDataViewsSelector(); + const getDefaultDataViewSelector = defaultDataViewSelector(); const getSignalIndexNameSelector = signalIndexNameSelector(); - const getConfigIndexPatternsSelector = configIndexPatternsSelector(); + const getSourcererDataViewSelector = sourcererDataViewSelector(); + const getScopeSelector = scopeIdSelector(); - const mapStateToProps = (state: State): string[] => { + return (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { + const kibanaDataViews = getKibanaDataViewsSelector(state); + const defaultDataView = getDefaultDataViewSelector(state); const signalIndexName = getSignalIndexNameSelector(state); - const configIndexPatterns = getConfigIndexPatternsSelector(state); - - return signalIndexName != null - ? [...configIndexPatterns, signalIndexName] - : configIndexPatterns; - }; - - return mapStateToProps; -}; - -const EXCLUDE_ELASTIC_CLOUD_INDEX = '-*elastic-cloud-logs-*'; - -export const getSourcererScopeSelector = () => { - const getScopeIdSelector = scopeIdSelector(); - const getSelectedPatterns = memoizeOne((selectedPatternsStr: string): string[] => { - const selectedPatterns = selectedPatternsStr.length > 0 ? selectedPatternsStr.split(',') : []; - return selectedPatterns.some((index) => index === 'logs-*') - ? [...selectedPatterns, EXCLUDE_ELASTIC_CLOUD_INDEX] - : selectedPatterns; - }); - - const getIndexPattern = memoizeOne( - (indexPattern, title) => ({ - ...indexPattern, - title, - }), - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length - ); - - const mapStateToProps = (state: State, scopeId: SourcererScopeName): ManageScope => { - const scope = getScopeIdSelector(state, scopeId); - const selectedPatterns = getSelectedPatterns(scope.selectedPatterns.sort().join()); - const indexPattern = getIndexPattern(scope.indexPattern, selectedPatterns.join()); + const scope = getScopeSelector(state, scopeId); + const sourcererDataView = getSourcererDataViewSelector(state, scope.selectedDataViewId); return { - ...scope, - selectedPatterns, - indexPattern, + defaultDataView, + kibanaDataViews, + signalIndexName, + sourcererDataView, + sourcererScope: scope, }; }; - return mapStateToProps; }; diff --git a/x-pack/plugins/security_solution/public/common/types.ts b/x-pack/plugins/security_solution/public/common/types.ts index 7d7dab6e4fc3c9..83a93769603e72 100644 --- a/x-pack/plugins/security_solution/public/common/types.ts +++ b/x-pack/plugins/security_solution/public/common/types.ts @@ -6,6 +6,9 @@ */ import { ResponseErrorAttributes } from 'kibana/server'; +import { DataViewBase } from '@kbn/es-query'; +import { FieldSpec } from '../../../../../src/plugins/data_views/common'; + export interface ServerApiError { statusCode: number; error: string; @@ -16,3 +19,10 @@ export interface ServerApiError { export interface SecuritySolutionUiConfigType { enableExperimental: string[]; } + +/** + * DataViewBase with enhanced index fields used in timelines + */ +export interface SecuritySolutionDataViewBase extends DataViewBase { + fields: FieldSpec[]; +} diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx index 981f7e9e876ea6..01dbfbed6a0c2d 100644 --- a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx @@ -16,7 +16,7 @@ jest.mock('../route/use_route_spy', () => ({ .mockImplementationOnce(() => [{ pageName: 'network' }]), })); jest.mock('../../../common/containers/sourcerer', () => ({ - useSourcererScope: jest + useSourcererDataView: jest .fn() .mockImplementationOnce(() => [{ indicesExist: false }]) .mockImplementationOnce(() => [{ indicesExist: false }]) diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx index 10cc4be10a61b8..f863cecffe3d69 100644 --- a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx @@ -8,7 +8,7 @@ import { useState, useEffect } from 'react'; import { useRouteSpy } from '../route/use_route_spy'; import { SecurityPageName } from '../../../app/types'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../containers/sourcerer'; // Used to detect if we're on a top level page that is empty and set page background color to match the subdued Empty State const isPageNameWithEmptyView = (currentName: string) => { @@ -23,7 +23,7 @@ const isPageNameWithEmptyView = (currentName: string) => { export const useShowPagesWithEmptyView = () => { const [{ pageName }] = useRouteSpy(); - const { indicesExist } = useSourcererScope(); + const { indicesExist } = useSourcererDataView(); const shouldShowEmptyState = isPageNameWithEmptyView(pageName) && !indicesExist; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index c8d45ca67068ad..29324d186784ea 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -94,7 +94,7 @@ export const AlertsCountPanel = memo( {i18n.COUNT_TABLE_TITLE}} + title={i18n.COUNT_TABLE_TITLE} titleSize="s" hideSubtitle > diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 147d41e8533df8..9095f181b00c1a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -257,11 +257,7 @@ export const AlertsHistogramPanel = memo( }, [showLinkToAlerts, goToDetectionEngine, formatUrl]); const titleText = useMemo( - () => ( - - {onlyField == null ? title : i18n.TOP(onlyField)} - - ), + () => (onlyField == null ? title : i18n.TOP(onlyField)), [onlyField, title] ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 835784031e0657..a4660356b86308 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -142,6 +142,7 @@ describe('alert actions', () => { ], defaultColumns: defaultHeaders, dataProviders: [], + dataViewId: '', dateRange: { end: '2018-11-05T19:03:25.937Z', start: '2018-11-05T18:58:25.937Z', diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index fb958c775e68c3..213f8c78e3b8d9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -7,12 +7,21 @@ /* eslint-disable complexity */ -import dateMath from '@elastic/datemath'; -import { getOr, isEmpty } from 'lodash/fp'; +import { get, getOr, isEmpty } from 'lodash/fp'; import moment from 'moment'; -import { i18n } from '@kbn/i18n'; + +import dateMath from '@elastic/datemath'; import { FilterStateStore, Filter } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { ALERT_RULE_FROM, ALERT_RULE_TYPE, ALERT_RULE_NOTE } from '@kbn/rule-data-utils'; + +import { + ALERT_ORIGINAL_TIME, + ALERT_GROUP_ID, + ALERT_RULE_TIMELINE_ID, + ALERT_THRESHOLD_RESULT, +} from '../../../../common/field_maps/field_names'; import { KueryFilterQueryKind, TimelineId, @@ -40,6 +49,7 @@ import { formatTimelineResultToModel, } from '../../../timelines/components/open_timeline/helpers'; import { convertKueryToElasticSearchQuery } from '../../../common/lib/keury'; +import { getField } from '../../../helpers'; import { replaceTemplateFieldFromQuery, replaceTemplateFieldFromMatchFilters, @@ -68,10 +78,18 @@ export const getUpdateAlertsQuery = (eventIds: Readonly) => { export const getFilterAndRuleBounds = ( data: TimelineNonEcsData[][] ): [string[], number, number] => { - const stringFilter = data?.[0].filter((d) => d.field === 'signal.rule.filters')?.[0]?.value ?? []; + const stringFilter = + data?.[0].filter( + (d) => d.field === 'signal.rule.filters' || d.field === 'kibana.alert.rule.filters' + )?.[0]?.value ?? []; const eventTimes = data - .flatMap((alert) => alert.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) + .flatMap( + (alert) => + alert.filter( + (d) => d.field === 'signal.original_time' || d.field === 'kibana.alert.original_time' + )?.[0]?.value ?? [] + ) .map((d) => moment(d)); return [stringFilter, moment.min(eventTimes).valueOf(), moment.max(eventTimes).valueOf()]; @@ -134,10 +152,9 @@ export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => { }; } const ecsData = ecs as Ecs; + const ruleFrom = getField(ecsData, ALERT_RULE_FROM); const elapsedTimeRule = moment.duration( - moment().diff( - dateMath.parse(ecsData?.signal?.rule?.from != null ? ecsData.signal?.rule?.from[0] : 'now-0s') - ) + moment().diff(dateMath.parse(ruleFrom != null ? ruleFrom[0] : 'now-0s')) ); const from = moment(ecsData?.timestamp ?? new Date()) .subtract(elapsedTimeRule) @@ -161,6 +178,7 @@ export const getThresholdAggregationData = ( ecsData: Ecs | Ecs[], nonEcsData: TimelineNonEcsData[] ): ThresholdAggregationData => { + // TODO: AAD fields const thresholdEcsData: Ecs[] = Array.isArray(ecsData) ? ecsData : [ecsData]; return thresholdEcsData.reduce( (outerAcc, thresholdData) => { @@ -177,9 +195,14 @@ export const getThresholdAggregationData = ( }; try { - thresholdResult = JSON.parse((thresholdData.signal?.threshold_result as string[])[0]); + try { + thresholdResult = JSON.parse((thresholdData.signal?.threshold_result as string[])[0]); + } catch (err) { + thresholdResult = JSON.parse((get(ALERT_THRESHOLD_RESULT, thresholdData) as string[])[0]); + } aggField = JSON.parse(threshold[0]).field; } catch (err) { + // Legacy support thresholdResult = { terms: [ { @@ -192,13 +215,15 @@ export const getThresholdAggregationData = ( }; } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const originalTime = moment(thresholdData.signal?.original_time![0]); - const now = moment(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const ruleFrom = dateMath.parse(thresholdData.signal?.rule?.from![0]!); - const ruleInterval = moment.duration(now.diff(ruleFrom)); + // Legacy support + const ruleFromStr = getField(thresholdData, ALERT_RULE_FROM)[0]; + const ruleFrom = dateMath.parse(ruleFromStr) ?? moment(); // The fallback here will essentially ensure 0 results + const originalTimeStr = getField(thresholdData, ALERT_ORIGINAL_TIME)[0]; + const originalTime = originalTimeStr != null ? moment(originalTimeStr) : ruleFrom; + const ruleInterval = moment.duration(moment().diff(ruleFrom)); const fromOriginalTime = originalTime.clone().subtract(ruleInterval); // This is the default... can overshoot + // End legacy support + const aggregationFields = Array.isArray(aggField) ? aggField : [aggField]; return { @@ -255,16 +280,19 @@ export const getThresholdAggregationData = ( ); }; -export const isEqlRuleWithGroupId = (ecsData: Ecs) => - ecsData.signal?.rule?.type?.length && - ecsData.signal?.rule?.type[0] === 'eql' && - ecsData.signal?.group?.id?.length; +export const isEqlRuleWithGroupId = (ecsData: Ecs) => { + const ruleType = getField(ecsData, ALERT_RULE_TYPE); + const groupId = getField(ecsData, ALERT_GROUP_ID); + return ruleType?.length && ruleType[0] === 'eql' && groupId?.length; +}; -export const isThresholdRule = (ecsData: Ecs) => - ecsData.signal?.rule?.type?.length && ecsData.signal?.rule?.type[0] === 'threshold'; +export const isThresholdRule = (ecsData: Ecs) => { + const ruleType = getField(ecsData, ALERT_RULE_TYPE); + return Array.isArray(ruleType) && ruleType.length && ruleType[0] === 'threshold'; +}; export const buildAlertsKqlFilter = ( - key: '_id' | 'signal.group.id', + key: '_id' | 'signal.group.id' | 'kibana.alert.group.id', alertIds: string[] ): Filter[] => { return [ @@ -283,7 +311,7 @@ export const buildAlertsKqlFilter = ( negate: false, disabled: false, type: 'phrases', - key, + key: key.replace('signal.', 'kibana.alert.'), value: alertIds.join(), params: alertIds, }, @@ -383,9 +411,11 @@ export const sendAlertToTimelineAction = async ({ */ const ecsData: Ecs = Array.isArray(ecs) && ecs.length > 0 ? ecs[0] : (ecs as Ecs); const alertIds = Array.isArray(ecs) ? ecs.map((d) => d._id) : []; - const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; + const ruleNote = getField(ecsData, ALERT_RULE_NOTE); + const noteContent = Array.isArray(ruleNote) && ruleNote.length > 0 ? ruleNote[0] : ''; + const ruleTimelineId = getField(ecsData, ALERT_RULE_TIMELINE_ID); const timelineId = - ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; + Array.isArray(ruleTimelineId) && ruleTimelineId.length > 0 ? ruleTimelineId[0] : ''; const { to, from } = determineToAndFrom({ ecs }); // For now we do not want to populate the template timeline if we have alertIds 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 4bd516d0b13380..7777d075a4b9c7 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 @@ -18,7 +18,7 @@ import { displaySuccessToast, useStateToaster, } from '../../../common/components/toasters'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; 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'; @@ -100,7 +100,7 @@ export const AlertsTableComponent: React.FC = ({ indexPattern: indexPatterns, loading: indexPatternsLoading, selectedPatterns, - } = useSourcererScope(SourcererScopeName.detections); + } = useSourcererDataView(SourcererScopeName.detections); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); const { addWarning } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx index e14c3e916a35e8..a342b01b038cae 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx @@ -8,7 +8,7 @@ import { useMemo } from 'react'; import { useGetUserCasesPermissions, useKibana } from '../../../../common/lib/kibana'; import { TimelineId, TimelineNonEcsData } from '../../../../../common'; -import { APP_UI_ID } from '../../../../../common/constants'; +import { APP_ID } from '../../../../../common/constants'; import { useInsertTimeline } from '../../../../cases/components/use_insert_timeline'; import { Ecs } from '../../../../../common/ecs'; @@ -39,7 +39,7 @@ export const useAddToCaseActions = ({ event: { data: nonEcsData ?? [], ecs: ecsData, _id: ecsData?._id }, useInsertTimeline: insertTimelineHook, casePermissions, - appId: APP_UI_ID, + appId: APP_ID, onClose: afterCaseSelection, } : null, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx index 22fabcd15b1946..91dbadbb209e15 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx @@ -8,14 +8,14 @@ import React, { useCallback, useMemo } from 'react'; import { EuiFormRow } from '@elastic/eui'; import { FieldComponent } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; interface AutocompleteFieldProps { dataTestSubj: string; field: FieldHook; idAria: string; - indices: IndexPatternBase; + indices: DataViewBase; isDisabled: boolean; fieldType: string; placeholder?: string; @@ -31,7 +31,7 @@ export const AutocompleteField = ({ placeholder, }: AutocompleteFieldProps) => { const handleFieldChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { // TODO: Update onChange type in FieldComponent as newField can be undefined field.setValue(newField?.name ?? ''); }, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index 62e787952aa73a..1ab9f92bc6d803 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -14,8 +14,8 @@ import { esFilters, FilterManager, UI_SETTINGS, - IndexPattern, } from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { SeverityBadge } from '../severity_badge'; import * as i18n from './translations'; @@ -146,7 +146,7 @@ describe('helpers', () => { fields: [{ name: 'event.category', type: 'test type' }], title: 'test title', getFormatterForField: () => ({ convert: (val: unknown) => val }), - } as unknown as IndexPattern, + } as unknown as DataViewBase, }); const wrapper = shallow(result[0].description as React.ReactElement); const filterLabelComponent = wrapper.find(esFilters.FilterLabel).at(0); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index ab84f1a65cc4b0..e403da9e490909 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -11,12 +11,8 @@ import React, { memo, useState } from 'react'; import styled from 'styled-components'; import { ThreatMapping, Threats, Type } from '@kbn/securitysolution-io-ts-alerting-types'; -import { - IIndexPattern, - Filter, - esFilters, - FilterManager, -} from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; +import { Filter, esFilters, FilterManager } from '../../../../../../../../src/plugins/data/public'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; import { useKibana } from '../../../../common/lib/kibana'; import { AboutStepRiskScore, AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; @@ -55,7 +51,7 @@ const DescriptionListContainer = styled(EuiDescriptionList)` interface StepRuleDescriptionProps { columns?: 'multi' | 'single' | 'singleSplit'; data: unknown; - indexPatterns?: IIndexPattern; + indexPatterns?: DataViewBase; schema: FormSchema; } @@ -129,7 +125,7 @@ export const buildListItems = ( data: unknown, schema: FormSchema, filterManager: FilterManager, - indexPatterns?: IIndexPattern + indexPatterns?: DataViewBase ): ListItems[] => Object.keys(schema).reduce( (acc, field) => [ @@ -161,7 +157,7 @@ export const getDescriptionItem = ( label: string, data: unknown, filterManager: FilterManager, - indexPatterns?: IIndexPattern + indexPatterns?: DataViewBase ): ListItems[] => { if (field === 'queryBar') { const filters = addFilterStateIfNotThere(get('queryBar.filters', data) ?? []); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts index dc5e7fea8e771f..02c25e5c12bee9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts @@ -7,12 +7,8 @@ import { ReactNode } from 'react'; import { Threats } from '@kbn/securitysolution-io-ts-alerting-types'; - -import { - IIndexPattern, - Filter, - FilterManager, -} from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; +import { Filter, FilterManager } from '../../../../../../../../src/plugins/data/public'; export interface ListItems { title: NonNullable; @@ -25,7 +21,7 @@ export interface BuildQueryBarDescription { filterManager: FilterManager; query: string; savedId: string; - indexPatterns?: IIndexPattern; + indexPatterns?: DataViewBase; queryLabel?: string; } diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index 6bda4a0e0f6b8a..42f92f8cb6ce13 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -10,14 +10,8 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; - -import { - Filter, - IIndexPattern, - Query, - FilterManager, - SavedQuery, -} from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; +import { FilterManager, SavedQuery } from '../../../../../../../../src/plugins/data/public'; import { BrowserFields } from '../../../../common/containers/source'; import { OpenTimelineModal } from '../../../../timelines/components/open_timeline/open_timeline_modal'; @@ -43,7 +37,7 @@ interface QueryBarDefineRuleProps { field: FieldHook; idAria: string; isLoading: boolean; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; onCloseTimelineSearch: () => void; openTimelineSearch: boolean; resizeParentContainer?: (height: number) => void; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx index e9cbcb6c38268a..fbeea64cae7dab 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx @@ -21,7 +21,7 @@ import styled from 'styled-components'; import { noop } from 'lodash/fp'; import { RiskScoreMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { FieldComponent } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { AboutStepRiskScore } from '../../../pages/detection_engine/rules/types'; @@ -46,7 +46,7 @@ interface RiskScoreFieldProps { dataTestSubj: string; field: FieldHook; idAria: string; - indices: IndexPatternBase; + indices: DataViewBase; isDisabled: boolean; placeholder?: string; } @@ -78,7 +78,7 @@ export const RiskScoreField = ({ ); const handleRiskScoreMappingChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { setValue({ value, isMappingChecked, @@ -231,8 +231,8 @@ export const RiskScoreField = ({ }; /** - * Looks for field metadata (IndexPatternFieldBase) in existing index pattern. - * If specified field doesn't exist, returns a stub IndexPatternFieldBase created based on the mapping -- + * Looks for field metadata (DataViewFieldBase) in existing index pattern. + * If specified field doesn't exist, returns a stub DataViewFieldBase created based on the mapping -- * because the field might not have been indexed yet, but we still need to display the mapping. * * @param mapping Mapping of a specified field name to risk score. @@ -240,8 +240,8 @@ export const RiskScoreField = ({ */ const getFieldTypeByMapping = ( mapping: RiskScoreMapping, - pattern: IndexPatternBase -): IndexPatternFieldBase => { + pattern: DataViewBase +): DataViewFieldBase => { const field = mapping?.[0]?.field ?? ''; const [knownFieldType] = pattern.fields.filter(({ name }) => field != null && field === name); return knownFieldType ?? { name: field, type: 'number' }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx index 4eca6f32d0c1fe..f6578fab0c5fb9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx @@ -29,7 +29,7 @@ import { AutocompleteFieldMatchComponent, } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { SeverityOptionItem } from '../step_about_rule/data'; @@ -56,7 +56,7 @@ interface SeverityFieldProps { dataTestSubj: string; field: FieldHook; idAria: string; - indices: IndexPatternBase; + indices: DataViewBase; isDisabled: boolean; options: SeverityOptionItem[]; } @@ -85,7 +85,7 @@ export const SeverityField = ({ ); const handleFieldChange = useCallback( - (index: number, severity: Severity, [newField]: IndexPatternFieldBase[]): void => { + (index: number, severity: Severity, [newField]: DataViewFieldBase[]): void => { const newMappingItems: SeverityMapping = [ { ...mapping[index], @@ -295,8 +295,8 @@ export const SeverityField = ({ }; /** - * Looks for field metadata (IndexPatternFieldBase) in existing index pattern. - * If specified field doesn't exist, returns a stub IndexPatternFieldBase created based on the mapping -- + * Looks for field metadata (DataViewFieldBase) in existing index pattern. + * If specified field doesn't exist, returns a stub DataViewFieldBase created based on the mapping -- * because the field might not have been indexed yet, but we still need to display the mapping. * * @param mapping Mapping of a specified field name + value to a certain severity value. @@ -304,8 +304,8 @@ export const SeverityField = ({ */ const getFieldTypeByMapping = ( mapping: SeverityMappingItem, - pattern: IndexPatternBase -): IndexPatternFieldBase => { + pattern: DataViewBase +): DataViewFieldBase => { const { field } = mapping; const [knownFieldType] = pattern.fields.filter(({ name }) => field === name); return knownFieldType ?? { name: field, type: 'string' }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 65528d86a40278..6471faf657fa05 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -10,7 +10,6 @@ import React, { FC, memo, useCallback, useState, useEffect, useMemo } from 'reac import styled from 'styled-components'; import { isEqual } from 'lodash'; -import { IndexPattern } from 'src/plugins/data/public'; import { DEFAULT_INDEX_KEY, DEFAULT_THREAT_INDEX_KEY, @@ -324,10 +323,10 @@ const StepDefineRuleComponent: FC = ({ ({ threatMapping }) => ( { describe('validateSingleAction', () => { it('should validate single action', async () => { - (isUuid as jest.Mock).mockReturnValue(true); (validateActionParams as jest.Mock).mockReturnValue([]); (validateMustache as jest.Mock).mockReturnValue([]); @@ -34,7 +33,6 @@ describe('stepRuleActions schema', () => { }); it('should validate single action with invalid mustache template', async () => { - (isUuid as jest.Mock).mockReturnValue(true); (validateActionParams as jest.Mock).mockReturnValue([]); (validateMustache as jest.Mock).mockReturnValue(['Message is not valid mustache template']); @@ -54,8 +52,7 @@ describe('stepRuleActions schema', () => { expect(errors[0]).toEqual('Message is not valid mustache template'); }); - it('should validate single action with incorrect id', async () => { - (isUuid as jest.Mock).mockReturnValue(false); + it('should validate single action with non-uuid formatted id', async () => { (validateMustache as jest.Mock).mockReturnValue([]); (validateActionParams as jest.Mock).mockReturnValue([]); @@ -68,8 +65,7 @@ describe('stepRuleActions schema', () => { }, actionTypeRegistry ); - expect(errors).toHaveLength(1); - expect(errors[0]).toEqual('No connector selected'); + expect(errors).toHaveLength(0); }); }); @@ -89,40 +85,8 @@ describe('stepRuleActions schema', () => { expect(result).toEqual(undefined); }); - it('should validate incorrect rule actions field', async () => { - (getActionTypeName as jest.Mock).mockReturnValue('Slack'); - const validator = validateRuleActionsField(actionTypeRegistry); - - const result = await validator({ - path: '', - value: [ - { - id: '3', - group: 'default', - actionTypeId: '.slack', - params: {}, - }, - ], - form: {} as FormHook, - formData: jest.fn(), - errors: [], - customData: { value: null, provider: () => Promise.resolve(null) }, - }); - - expect(result).toEqual({ - code: 'ERR_FIELD_FORMAT', - message: ` -**Slack:** -* No connector selected -`, - path: '', - }); - }); - it('should validate multiple incorrect rule actions field', async () => { - (isUuid as jest.Mock).mockReturnValueOnce(false); (getActionTypeName as jest.Mock).mockReturnValueOnce('Slack'); - (isUuid as jest.Mock).mockReturnValueOnce(true); (getActionTypeName as jest.Mock).mockReturnValueOnce('Pagerduty'); (validateActionParams as jest.Mock).mockReturnValue(['Summary is required']); (validateMustache as jest.Mock).mockReturnValue(['Component is not valid mustache template']); @@ -156,7 +120,8 @@ describe('stepRuleActions schema', () => { code: 'ERR_FIELD_FORMAT', message: ` **Slack:** -* No connector selected +* Summary is required +* Component is not valid mustache template **Pagerduty:** diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx index 58202929c49a3d..955c3576736892 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx @@ -20,17 +20,12 @@ import { ValidationError, } from '../../../../shared_imports'; import { ActionsStepRule } from '../../../pages/detection_engine/rules/types'; -import * as I18n from './translations'; -import { isUuid, getActionTypeName, validateMustache, validateActionParams } from './utils'; +import { getActionTypeName, validateMustache, validateActionParams } from './utils'; export const validateSingleAction = async ( actionItem: AlertAction, actionTypeRegistry: ActionTypeRegistryContract ): Promise => { - if (!isUuid(actionItem.id)) { - return [I18n.NO_CONNECTOR_SELECTED]; - } - const actionParamsErrors = await validateActionParams(actionItem, actionTypeRegistry); const mustacheErrors = validateMustache(actionItem.params); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.test.ts index 7c4ea71c983c88..575791afd110bc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.test.ts @@ -6,19 +6,9 @@ */ import { actionTypeRegistryMock } from '../../../../../../triggers_actions_ui/public/application/action_type_registry.mock'; -import { isUuid, getActionTypeName, validateMustache, validateActionParams } from './utils'; +import { getActionTypeName, validateMustache, validateActionParams } from './utils'; describe('stepRuleActions utils', () => { - describe('isUuidv4', () => { - it('should validate proper uuid v4 value', () => { - expect(isUuid('817b8bca-91d1-4729-8ee1-3a83aaafd9d4')).toEqual(true); - }); - - it('should validate incorrect uuid v4 value', () => { - expect(isUuid('ad9d4')).toEqual(false); - }); - }); - describe('getActionTypeName', () => { it('should return capitalized action type name', () => { expect(getActionTypeName('.slack')).toEqual('Slack'); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.ts index 22363df5164a60..fc9562af835250 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/utils.ts @@ -14,10 +14,6 @@ import { } from '../../../../../../triggers_actions_ui/public'; import * as I18n from './translations'; -const UUID_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i; - -export const isUuid = (id: AlertAction['id']) => !!id.match(UUID_REGEX); - export const getActionTypeName = (actionTypeId: AlertAction['actionTypeId']) => { if (!actionTypeId) return ''; const actionType = actionTypeId.split('.')[1]; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/threatmatch_input/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/threatmatch_input/index.tsx index 73e9dcdd94c033..91158979a82c5f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/threatmatch_input/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/threatmatch_input/index.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiFormRow } from '@elastic/eui'; - +import { DataViewBase } from '@kbn/es-query'; import { ThreatMapEntries } from '../../../../common/components/threat_match/types'; import { ThreatMatchComponent } from '../../../../common/components/threat_match'; import { BrowserField } from '../../../../common/containers/source'; @@ -21,7 +21,6 @@ import { import { DefineStepRule } from '../../../pages/detection_engine/rules/types'; import { schema } from '../step_define_rule/schema'; import { QueryBarDefineRule } from '../query_bar'; -import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; import * as i18n from '../step_define_rule/translations'; import { MyLabelButton } from '../step_define_rule'; @@ -30,8 +29,8 @@ const CommonUseField = getUseField({ component: Field }); interface ThreatMatchInputProps { threatMapping: FieldHook; threatBrowserFields: Readonly>>; - threatIndexPatterns: IndexPattern; - indexPatterns: IndexPattern; + threatIndexPatterns: DataViewBase; + indexPatterns: DataViewBase; threatIndexPatternsLoading: boolean; threatIndexModified: boolean; handleResetThreatIndices: () => void; diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx index 3d81735122e731..dde438302efd69 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx @@ -107,9 +107,11 @@ export const ValueListsModalComponent: React.FC = ({ useEffect(() => { if (!isEmpty(deleteError)) { const references: string[] = - // @ts-ignore-next-line deleteError response unknown message.error.references + // deleteError response unknown message.error.references + // @ts-expect-error TS2571 deleteError?.body?.message?.error?.references?.map( - // @ts-ignore-next-line response not typed + // response not typed + // @ts-expect-error TS7006 (ref) => ref?.exception_list.name ) ?? []; const uniqueExceptionListReferences = Array.from(new Set(references)); @@ -118,7 +120,8 @@ export const ValueListsModalComponent: React.FC = ({ contentText: i18n.referenceErrorMessage(uniqueExceptionListReferences.length), exceptionListReferences: uniqueExceptionListReferences, isLoading: false, - // @ts-ignore-next-line deleteError response unknown + // deleteError response unknown + // @ts-expect-error TS2571 valueListId: deleteError?.body?.message?.error?.value_list_id, }); } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts index e5b9808bf1e46a..0045f69968b2a6 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts @@ -669,8 +669,8 @@ describe('Detections Rules API', () => { test('check parameter url, query', async () => { await getRuleStatusById({ id: 'mySuperRuleId', signal: abortCtrl.signal }); - expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_find_statuses', { - body: '{"ids":["mySuperRuleId"]}', + expect(fetchMock).toHaveBeenCalledWith('/internal/detection_engine/rules/_find_status', { + body: '{"ruleId":"mySuperRuleId"}', method: 'POST', signal: abortCtrl.signal, }); 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 f5f7fbd8466235..5f9ad7fdd2bf58 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 @@ -19,6 +19,7 @@ import { DETECTION_ENGINE_TAGS_URL, DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_PREVIEW, + INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, } from '../../../../../common/constants'; import { UpdateRulesProps, @@ -372,9 +373,9 @@ export const getRuleStatusById = async ({ id: string; signal: AbortSignal; }): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_STATUS_URL, { + KibanaServices.get().http.fetch(INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, { method: 'POST', - body: JSON.stringify({ ids: [id] }), + body: JSON.stringify({ ruleId: id }), signal, }); 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 0d0c51bc540b47..eeb22a2aa071c5 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 @@ -19,7 +19,7 @@ import { } from '../../../common/mock'; import { DetectionEnginePage } from './detection_engine'; import { useUserData } from '../../components/user_info'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../common/mock/router'; import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; @@ -110,7 +110,7 @@ describe('DetectionEnginePageComponent', () => { canUserREAD: true, }, ]); - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index ebda4f2b4232f5..204387f4a241bf 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -66,7 +66,9 @@ import { buildShowBuildingBlockFilterRuleRegistry, buildThreatMatchFilter, } from '../../components/alerts_table/default_config'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { useSignalHelpers } from '../../../common/containers/sourcerer/use_signal_helpers'; + import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { NeedAdminForUpdateRulesCallOut } from '../../components/callouts/need_admin_for_update_callout'; import { MissingPrivilegesCallOut } from '../../components/callouts/missing_privileges_callout'; @@ -78,7 +80,6 @@ import { FILTER_OPEN, } from '../../components/alerts_table/alerts_filter_group'; import { EmptyPage } from '../../../common/components/empty_page'; - /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. */ @@ -235,7 +236,9 @@ const DetectionEnginePageComponent: React.FC = ({ [setShowOnlyThreatIndicatorAlerts] ); - const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); + const { indexPattern } = useSourcererDataView(SourcererScopeName.detections); + + const { signalIndexNeedsInit, pollForSignalIndex } = useSignalHelpers(); const onSkipFocusBeforeEventsTable = useCallback(() => { focusUtilityBarAction(containerElement.current); @@ -291,35 +294,38 @@ const DetectionEnginePageComponent: React.FC = ({ ); } - if (!loading && (indicesExist === false || needsListsConfiguration)) { + if ((!loading && signalIndexNeedsInit) || needsListsConfiguration) { return ( ); } - return ( <> {hasEncryptionKey != null && !hasEncryptionKey && } - {indicesExist && (hasIndexRead === false || canUserREAD === false) ? ( + {!signalIndexNeedsInit && (hasIndexRead === false || canUserREAD === false) ? ( - ) : indicesExist && hasIndexRead && canUserREAD ? ( + ) : !signalIndexNeedsInit && hasIndexRead && canUserREAD ? ( - + ), - truncateText: true, width: '20%', sortable: true, }, @@ -186,14 +185,12 @@ export const getColumns = ({ {value} ), - truncateText: true, width: '10%', }, { field: 'severity', name: i18n.COLUMN_SEVERITY, render: (value: Rule['severity']) => , - truncateText: true, width: '12%', }, { @@ -208,7 +205,6 @@ export const getColumns = ({ ); }, - truncateText: true, width: '14%', }, { @@ -224,7 +220,6 @@ export const getColumns = ({ ); }, width: '12%', - truncateText: true, }, { field: 'updated_at', @@ -239,7 +234,6 @@ export const getColumns = ({ ); }, sortable: true, - truncateText: true, width: '14%', }, { @@ -254,7 +248,6 @@ export const getColumns = ({ ); }, - truncateText: true, width: '8%', }, { @@ -266,7 +259,6 @@ export const getColumns = ({ } return getEmptyTagValue(); }, - truncateText: true, width: '20%', }, { @@ -344,7 +336,6 @@ export const getMonitoringColumns = ( ); }, - truncateText: true, width: '24%', }, { @@ -362,7 +353,6 @@ export const getMonitoringColumns = ( {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} ), - truncateText: true, width: '14%', }, { @@ -380,7 +370,6 @@ export const getMonitoringColumns = ( {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} ), - truncateText: true, width: '14%', }, { @@ -412,7 +401,6 @@ export const getMonitoringColumns = ( {value ?? getEmptyTagValue()} ), - truncateText: true, width: '14%', }, { @@ -427,7 +415,6 @@ export const getMonitoringColumns = ( ); }, - truncateText: true, width: '20%', }, { @@ -443,7 +430,6 @@ export const getMonitoringColumns = ( ); }, width: '16%', - truncateText: true, }, { field: 'activate', diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx index 006e497a20a7bc..9227de3462d0e0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx @@ -23,7 +23,7 @@ import { useUserData } from '../../../../components/user_info'; import { useRuleStatus } from '../../../../containers/detection_engine/rules'; import { useRuleWithFallback } from '../../../../containers/detection_engine/rules/use_rule_with_fallback'; -import { useSourcererScope } from '../../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; import { useParams } from 'react-router-dom'; import { mockHistory, Router } from '../../../../../common/mock/router'; @@ -128,7 +128,7 @@ describe('RuleDetailsPageComponent', () => { beforeAll(() => { (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index ca71ac6783f59b..9f56467043dd29 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -60,7 +60,6 @@ import { DetectionEngineHeaderPage } from '../../../../components/detection_engi import { AlertsHistogramPanel } from '../../../../components/alerts_kpis/alerts_histogram_panel'; import { AlertsTable } from '../../../../components/alerts_table'; import { useUserData } from '../../../../components/user_info'; -import { OverviewEmpty } from '../../../../../overview/components/overview_empty'; import { StepDefineRule } from '../../../../components/rules/step_define_rule'; import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; import { @@ -104,7 +103,7 @@ import { } from '../../../../../timelines/components/timeline/helpers'; import { timelineActions, timelineSelectors } from '../../../../../timelines/store/timeline'; import { timelineDefaults } from '../../../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; import { getToolTipContent, @@ -127,6 +126,7 @@ import { AlertsTableFilterGroup, FILTER_OPEN, } from '../../../../components/alerts_table/alerts_filter_group'; +import { useSignalHelpers } from '../../../../../common/containers/sourcerer/use_signal_helpers'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -218,6 +218,7 @@ const RuleDetailsPageComponent: React.FC = ({ loading: ruleLoading, isExistingRule, } = useRuleWithFallback(ruleId); + const { pollForSignalIndex } = useSignalHelpers(); const [loadingStatus, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId); const [currentStatus, setCurrentStatus] = useState( ruleStatus?.current_status ?? null @@ -623,8 +624,7 @@ const RuleDetailsPageComponent: React.FC = ({ [setShowOnlyThreatIndicatorAlerts] ); - const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); - + const { indexPattern } = useSourcererDataView(SourcererScopeName.detections); const exceptionLists = useMemo((): { lists: ExceptionListIdentifiers[]; allowedExceptionListTypes: ExceptionListTypeEnum[]; @@ -696,192 +696,188 @@ const RuleDetailsPageComponent: React.FC = ({ <> - {indicesExist ? ( - - - - - - - - - - - - {statusI18n.STATUS} - {':'} - - {ruleStatusInfo} - - - } - title={title} - badgeOptions={badgeOptions} - > - - - - - - {i18n.ACTIVATE_RULE} - - - - - - - {editRule} - - - + + + + + + + + + + + + {statusI18n.STATUS} + {':'} + + {ruleStatusInfo} + + + } + title={title} + badgeOptions={badgeOptions} + > + + + + + + {i18n.ACTIVATE_RULE} - - - - {ruleError} - {getLegacyUrlConflictCallout} - - - - + - - - - - {defineRuleData != null && ( - + + + {editRule} + + - - - - - {scheduleRuleData != null && ( - - )} - + /> - - {tabs} - - - {ruleDetailTab === RuleDetailTabs.alerts && hasIndexRead && ( - <> - - - + + {ruleError} + {getLegacyUrlConflictCallout} + + + + + + + + + + + {defineRuleData != null && ( + + )} + - - {updatedAt && - timelinesUi.getLastUpdated({ - updatedAt: updatedAt || Date.now(), - showUpdating, - })} + + + + {scheduleRuleData != null && ( + + )} + - - - - - - {ruleId != null && ( - + + + {tabs} + + + {ruleDetailTab === RuleDetailTabs.alerts && hasIndexRead && ( + <> + + + - )} - - )} - {ruleDetailTab === RuleDetailTabs.exceptions && ( - - )} - {ruleDetailTab === RuleDetailTabs.failures && } - - - ) : ( - - - - + + + {updatedAt && + timelinesUi.getLastUpdated({ + updatedAt: updatedAt || Date.now(), + showUpdating, + })} + + + + + + + + {ruleId != null && ( + + )} + + )} + {ruleDetailTab === RuleDetailTabs.exceptions && ( + + )} + {ruleDetailTab === RuleDetailTabs.failures && } - )} + diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 53efe28cba49c9..15ff91cac50966 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -579,19 +579,21 @@ export const SUCCESSFULLY_IMPORTED_RULES = (totalRules: number) => } ); -export const IMPORT_FAILED = i18n.translate( - 'xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedTitle', - { - defaultMessage: 'Failed to import rules', - } -); +export const IMPORT_FAILED = (totalRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedTitle', + { + values: { totalRules }, + defaultMessage: 'Failed to import {totalRules} {totalRules, plural, =1 {rule} other {rules}}', + } + ); -export const IMPORT_FAILED_DETAILED = (ruleId: string, statusCode: number, message: string) => +export const IMPORT_FAILED_DETAILED = (message: string) => i18n.translate( 'xpack.securitySolution.detectionEngine.components.importRuleModal.importFailedDetailedTitle', { - values: { ruleId, statusCode, message }, - defaultMessage: 'Rule ID: {ruleId}\n Status Code: {statusCode}\n Message: {message}', + values: { message }, + defaultMessage: '{message}', } ); diff --git a/x-pack/plugins/security_solution/public/helpers.tsx b/x-pack/plugins/security_solution/public/helpers.tsx index 066e6a4cb4684e..f160d293dd475f 100644 --- a/x-pack/plugins/security_solution/public/helpers.tsx +++ b/x-pack/plugins/security_solution/public/helpers.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { isEmpty } from 'lodash/fp'; +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { get, isEmpty } from 'lodash/fp'; import React from 'react'; import { matchPath, RouteProps, Redirect } from 'react-router-dom'; @@ -22,6 +23,7 @@ import { OVERVIEW_PATH, CASES_PATH, } from '../common/constants'; +import { Ecs } from '../common/ecs'; import { FactoryQueryTypes, StrategyResponseType, @@ -208,3 +210,27 @@ export const RedirectRoute = React.memo<{ capabilities: Capabilities }>(({ capab return ; }); RedirectRoute.displayName = 'RedirectRoute'; + +const siemSignalsFieldMappings: Record = { + [ALERT_RULE_UUID]: 'signal.rule.id', +}; + +const alertFieldMappings: Record = { + 'signal.rule.id': ALERT_RULE_UUID, +}; + +/* + * Deprecation notice: This functionality should be removed when support for signal.* is no longer + * supported. + * + * Selectively returns the AAD field value (kibana.alert.*) or the legacy field value + * (signal.*), whichever is present. For backwards compatibility. + */ +export const getField = (ecsData: Ecs, field: string) => { + const aadField = (alertFieldMappings[field] ?? field).replace('signal', 'kibana.alert'); + const siemSignalsField = (siemSignalsFieldMappings[field] ?? field).replace( + 'kibana.alert', + 'signal' + ); + return get(aadField, ecsData) ?? get(siemSignalsField, ecsData); +}; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 9bf046f40edb45..55c799f3bda434 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -47,11 +47,11 @@ import { Display } from '../display'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { ID, useHostDetails } from '../../containers/hosts/details'; import { manageQuery } from '../../../common/components/page/manage_query'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; const HostOverviewManage = manageQuery(HostOverview); @@ -97,7 +97,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta [dispatch] ); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indexPattern, indicesExist, selectedPatterns } = useSourcererDataView(); const [loading, { inspect, hostDetails: hostOverview, id, refetch }] = useHostDetails({ endDate: to, startDate: from, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts index 21924fe9293207..cd9ee9ce01869d 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts @@ -6,7 +6,7 @@ */ import { ActionCreator } from 'typescript-fsa'; -import { Query, IIndexPattern, Filter } from 'src/plugins/data/public'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { InputsModelId } from '../../../common/store/inputs/constants'; import { HostsTableType } from '../../store/model'; import { HostsQueryProps } from '../types'; @@ -62,7 +62,7 @@ export type HostDetailsTabsProps = HostBodyComponentDispatchProps & indexNames: string[]; pageFilters?: Filter[]; filterQuery?: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; type: hostsModel.HostsType; }; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index d05b091381cca7..51dd9d230324f0 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -23,7 +23,7 @@ import { inputsActions } from '../../common/store/inputs'; import { State, createStore } from '../../common/store'; import { Hosts } from './hosts'; import { HostsTabs } from './hosts_tabs'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; jest.mock('../../common/containers/sourcerer'); @@ -57,10 +57,10 @@ const mockHistory = { createHref: jest.fn(), listen: jest.fn(), }; -const mockUseSourcererScope = useSourcererScope as jest.Mock; +const mockUseSourcererDataView = useSourcererDataView as jest.Mock; describe('Hosts - rendering', () => { test('it renders the Setup Instructions text when no index is available', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: false, }); @@ -75,7 +75,7 @@ describe('Hosts - rendering', () => { }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -90,7 +90,7 @@ describe('Hosts - rendering', () => { }); test('it should render tab navigation', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -137,7 +137,7 @@ describe('Hosts - rendering', () => { }, }, ]; - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: true, indexPattern: { fields: [], title: 'title' }, }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index b1c7c25c794c17..8417e8f3ae8b35 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -50,7 +50,7 @@ import { } from '../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; import { ID } from '../containers/hosts'; @@ -111,7 +111,7 @@ const HostsComponent = () => { }, [dispatch] ); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const [filterQuery, kqlError] = useMemo( () => convertToBuildEsQuery({ diff --git a/x-pack/plugins/security_solution/public/index.ts b/x-pack/plugins/security_solution/public/index.ts index 3d2412b326b549..5f2f6014822aba 100644 --- a/x-pack/plugins/security_solution/public/index.ts +++ b/x-pack/plugins/security_solution/public/index.ts @@ -12,4 +12,5 @@ export type { TimelineModel } from './timelines/store/timeline/model'; export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context); -export { Plugin, PluginSetup }; +export type { PluginSetup }; +export { Plugin }; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx index 36362463b5ea21..289a8e407000f1 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx @@ -65,12 +65,11 @@ describe.each([ ); }); - // FLAKY https://github.com/elastic/kibana/issues/113892 - it.skip('should display dates in expected format', () => { + it('should display dates in expected format', () => { render(); expect(renderResult.getByTestId('testCard-header-updated').textContent).toEqual( - expect.stringMatching(/Last updated(\s seconds? ago|now)/) + expect.stringMatching(/Last updated(?:(\s*\d+ seconds? ago)|now)/) ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx index e974557c36e0a3..89d2f029e95381 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx @@ -6,10 +6,9 @@ */ import React, { memo } from 'react'; -import { CommonProps, EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui'; +import { CommonProps, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { CardHeader, CardHeaderProps } from './components/card_header'; import { CardSubHeader } from './components/card_sub_header'; -import { getEmptyValue } from '../../../common/components/empty_value'; import { CriteriaConditions, CriteriaConditionsProps } from './components/criteria_conditions'; import { AnyArtifact, MenuItemPropsByPolicyId } from './types'; import { useNormalizedArtifact } from './hooks/use_normalized_artifact'; @@ -19,6 +18,7 @@ import { CardSectionPanel } from './components/card_section_panel'; import { CardComments } from './components/card_comments'; import { usePolicyNavLinks } from './hooks/use_policy_nav_links'; import { MaybeImmutable } from '../../../../common/endpoint/types'; +import { DescriptionField } from './components/description_field'; export interface CommonArtifactEntryCardProps extends CommonProps { item: MaybeImmutable; @@ -80,17 +80,20 @@ export const ArtifactEntryCard = memo( data-test-subj={getTestId('subHeader')} /> - + {!hideDescription && ( + <> + + + {artifact.description} + + + )} - {!hideDescription ? ( - -

- {artifact.description || getEmptyValue()} -

-
- ) : null} {!hideComments ? ( - + <> + + + ) : null} diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx index 5fb53e44a0ba78..bfcd8c58f34479 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx @@ -9,7 +9,6 @@ import React, { memo, useCallback, useState, useMemo } from 'react'; import { CommonProps, EuiPanel, - EuiText, EuiAccordion, EuiTitle, EuiCheckbox, @@ -19,11 +18,12 @@ import { EuiButtonEmpty, } from '@elastic/eui'; import styled from 'styled-components'; -import { getEmptyValue } from '../../../common/components/empty_value'; import { CriteriaConditions, CriteriaConditionsProps } from './components/criteria_conditions'; import { AnyArtifact } from './types'; import { useNormalizedArtifact } from './hooks/use_normalized_artifact'; import { useTestIdGenerator } from '../hooks/use_test_id_generator'; +import { DESCRIPTION_LABEL } from './components/translations'; +import { DescriptionField } from './components/description_field'; const CardContainerPanel = styled(EuiSplitPanel.Outer)` &.artifactEntryCardMinified + &.artifactEntryCardMinified { @@ -103,13 +103,11 @@ export const ArtifactEntryCardMinified = memo( -
{'Description'}
+
{DESCRIPTION_LABEL}
- -

- {artifact.description || getEmptyValue()} -

-
+ + {artifact.description} +
diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx index 84860b1144f080..89d972c47ffebf 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx @@ -75,6 +75,25 @@ describe.each([ expect(renderResult.getByTestId('testCard-criteriaConditions')).not.toBeNull(); }); + it('should display tooltip if collapsed', () => { + render(); + + expect(renderResult.baseElement.querySelectorAll('.euiToolTipAnchor')).toHaveLength(2); + }); + + it('should display tooltip when collapsed but only if not empty', () => { + item.description = ''; + render(); + + expect(renderResult.baseElement.querySelectorAll('.euiToolTipAnchor')).toHaveLength(1); + }); + + it('should NOT display a tooltip if expanded', () => { + render({ expanded: true }); + + expect(renderResult.baseElement.querySelectorAll('.euiToolTipAnchor')).toHaveLength(0); + }); + it('should call `onExpandCollapse` callback when button is clicked', () => { render(); act(() => { diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_compressed_header.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_compressed_header.tsx index a4928ffe406742..629c6ec9cb6922 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_compressed_header.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_compressed_header.tsx @@ -17,7 +17,7 @@ import { ArtifactEntryCollapsibleCardProps } from '../artifact_entry_collapsible import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; import { useCollapsedCssClassNames } from '../hooks/use_collapsed_css_class_names'; import { usePolicyNavLinks } from '../hooks/use_policy_nav_links'; -import { getEmptyValue } from '../../../../common/components/empty_value'; +import { DescriptionField } from './description_field'; export interface CardCompressedHeaderProps extends Pick, @@ -56,14 +56,14 @@ export const CardCompressedHeader = memo( /> } name={ - + {artifact.name} } description={ - - {artifact.description || getEmptyValue()} - + + {artifact.description} + } effectScope={ diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx index 743eac7a15458c..048b79c3548033 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx @@ -6,7 +6,14 @@ */ import React, { memo, useCallback, useMemo } from 'react'; -import { CommonProps, EuiExpression, EuiToken, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + CommonProps, + EuiExpression, + EuiToken, + EuiFlexGroup, + EuiFlexItem, + EuiBadge, +} from '@elastic/eui'; import styled from 'styled-components'; import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { @@ -21,6 +28,8 @@ import { CONDITION_OPERATOR_TYPE_MATCH_ANY, CONDITION_OPERATOR_TYPE_EXISTS, CONDITION_OPERATOR_TYPE_LIST, + CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY, + CONDITION_OPERATOR_TYPE_NOT_MATCH, } from './translations'; import { ArtifactInfo, ArtifactInfoEntry } from '../types'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; @@ -32,7 +41,7 @@ const OS_LABELS = Object.freeze({ windows: OS_WINDOWS, }); -const OPERATOR_TYPE_LABELS = Object.freeze({ +const OPERATOR_TYPE_LABELS_INCLUDED = Object.freeze({ [ListOperatorTypeEnum.NESTED]: CONDITION_OPERATOR_TYPE_NESTED, [ListOperatorTypeEnum.MATCH_ANY]: CONDITION_OPERATOR_TYPE_MATCH_ANY, [ListOperatorTypeEnum.MATCH]: CONDITION_OPERATOR_TYPE_MATCH, @@ -41,8 +50,13 @@ const OPERATOR_TYPE_LABELS = Object.freeze({ [ListOperatorTypeEnum.LIST]: CONDITION_OPERATOR_TYPE_LIST, }); +const OPERATOR_TYPE_LABELS_EXCLUDED = Object.freeze({ + [ListOperatorTypeEnum.MATCH_ANY]: CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY, + [ListOperatorTypeEnum.MATCH]: CONDITION_OPERATOR_TYPE_NOT_MATCH, +}); + const EuiFlexGroupNested = styled(EuiFlexGroup)` - margin-left: ${({ theme }) => theme.eui.spacerSizes.l}; + margin-left: ${({ theme }) => theme.eui.spacerSizes.xl}; `; const EuiFlexItemNested = styled(EuiFlexItem)` @@ -67,11 +81,30 @@ export const CriteriaConditions = memo( .join(', '); }, [os]); + const getEntryValue = (type: string, value: string | string[]) => { + if (type === 'match_any' && Array.isArray(value)) { + return value.map((currentValue) => {currentValue}); + } + return value; + }; + + const getEntryOperator = (type: string, operator: string) => { + if (type === 'nested') return ''; + return operator === 'included' + ? OPERATOR_TYPE_LABELS_INCLUDED[type as keyof typeof OPERATOR_TYPE_LABELS_INCLUDED] ?? type + : OPERATOR_TYPE_LABELS_EXCLUDED[type as keyof typeof OPERATOR_TYPE_LABELS_EXCLUDED] ?? type; + }; + const getNestedEntriesContent = useCallback( (type: string, nestedEntries: ArtifactInfoEntry[]) => { if (type === 'nested' && nestedEntries.length) { return nestedEntries.map( - ({ field: nestedField, type: nestedType, value: nestedValue }) => { + ({ + field: nestedField, + type: nestedType, + value: nestedValue, + operator: nestedOperator, + }) => { return ( ( @@ -113,7 +143,7 @@ export const CriteriaConditions = memo(

- {entries.map(({ field, type, value, entries: nestedEntries = [] }) => { + {entries.map(({ field, type, value, operator, entries: nestedEntries = [] }) => { return (
( color="subdued" /> {getNestedEntriesContent(type, nestedEntries)}
diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/description_field.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/description_field.tsx new file mode 100644 index 00000000000000..220fac4a717def --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/description_field.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, PropsWithChildren } from 'react'; +import { CommonProps } from '@elastic/eui'; +import { getEmptyValue } from '../../../../common/components/empty_value'; +import { TextValueDisplay, TextValueDisplayProps } from './text_value_display'; + +export type DescriptionFieldProps = PropsWithChildren<{}> & + Pick & + Pick; + +export const DescriptionField = memo( + ({ truncate, children, 'data-test-subj': dataTestSubj, withTooltip }) => { + return ( + + {children || getEmptyValue()} + + ); + } +); +DescriptionField.displayName = 'ArtifactDescription'; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx index b7e085a1f43c26..aaef120fc566d8 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx @@ -6,30 +6,54 @@ */ import React, { memo, PropsWithChildren, useMemo } from 'react'; -import { EuiText } from '@elastic/eui'; +import { CommonProps, EuiText, EuiToolTip } from '@elastic/eui'; import classNames from 'classnames'; +import { getEmptyValue } from '../../../../common/components/empty_value'; -export type TextValueDisplayProps = PropsWithChildren<{ - bold?: boolean; - truncate?: boolean; - size?: 'xs' | 's' | 'm' | 'relative'; -}>; +export type TextValueDisplayProps = Pick & + PropsWithChildren<{ + bold?: boolean; + truncate?: boolean; + size?: 'xs' | 's' | 'm' | 'relative'; + withTooltip?: boolean; + }>; /** * Common component for displaying consistent text across the card. Changes here could impact all of * display of values on the card */ export const TextValueDisplay = memo( - ({ bold, truncate, size = 's', children }) => { + ({ + bold, + truncate, + size = 's', + withTooltip = false, + 'data-test-subj': dataTestSubj, + children, + }) => { const cssClassNames = useMemo(() => { return classNames({ 'eui-textTruncate': truncate, + 'eui-textBreakWord': true, }); }, [truncate]); + const textContent = useMemo(() => { + return bold ? {children} : children; + }, [bold, children]); + return ( - - {bold ? {children} : children} + + {withTooltip && + 'string' === typeof children && + children.length > 0 && + children !== getEmptyValue() ? ( + + <>{textContent} + + ) : ( + textContent + )} ); } diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts index 4cdae5238a1ac9..3290a52c1c37d4 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts @@ -54,6 +54,13 @@ export const CONDITION_OPERATOR_TYPE_MATCH = i18n.translate( } ); +export const CONDITION_OPERATOR_TYPE_NOT_MATCH = i18n.translate( + 'xpack.securitySolution.artifactCard.conditions.matchOperator.not', + { + defaultMessage: 'IS NOT', + } +); + export const CONDITION_OPERATOR_TYPE_WILDCARD = i18n.translate( 'xpack.securitySolution.artifactCard.conditions.wildcardOperator', { @@ -71,7 +78,14 @@ export const CONDITION_OPERATOR_TYPE_NESTED = i18n.translate( export const CONDITION_OPERATOR_TYPE_MATCH_ANY = i18n.translate( 'xpack.securitySolution.artifactCard.conditions.matchAnyOperator', { - defaultMessage: 'is any', + defaultMessage: 'is one of', + } +); + +export const CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY = i18n.translate( + 'xpack.securitySolution.artifactCard.conditions.matchAnyOperator.not', + { + defaultMessage: 'is not one of', } ); @@ -134,3 +148,10 @@ export const HIDE_COMMENTS_LABEL = (count: number = 0) => defaultMessage: 'Hide comments ({count})', values: { count }, }); + +export const DESCRIPTION_LABEL = i18n.translate( + 'xpack.securitySolution.artifactMinifiedCard.descriptionLabel', + { + defaultMessage: 'Description', + } +); diff --git a/x-pack/plugins/security_solution/public/management/components/policies_selector/index.ts b/x-pack/plugins/security_solution/public/management/components/policies_selector/index.ts index 1b25a8af9d3fcc..7ebfcb8c9f4abf 100644 --- a/x-pack/plugins/security_solution/public/management/components/policies_selector/index.ts +++ b/x-pack/plugins/security_solution/public/management/components/policies_selector/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { PoliciesSelector, PolicySelectionItem, PoliciesSelectorProps } from './policies_selector'; +export type { PolicySelectionItem, PoliciesSelectorProps } from './policies_selector'; +export { PoliciesSelector } from './policies_selector'; diff --git a/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.test.tsx b/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.test.tsx index f93c7399cac335..12a0e461863e8d 100644 --- a/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.test.tsx @@ -6,7 +6,9 @@ */ import { I18nProvider } from '@kbn/i18n/react'; -import { render, act, fireEvent, RenderResult } from '@testing-library/react'; +import { render, RenderResult } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + import React from 'react'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; @@ -46,13 +48,11 @@ describe('Policies selector', () => { const defaultIncludedPolicies = 'abc123'; const defaultExcludedPolicies = 'global'; const element = getElement({ defaultExcludedPolicies, defaultIncludedPolicies }); - act(() => { - fireEvent.click(element.getByTestId('policiesSelectorButton')); - }); + + userEvent.click(element.getByTestId('policiesSelectorButton')); expect(element.getByText(policy.name)).toHaveTextContent(policy.name); - act(() => { - fireEvent.click(element.getByText('Unassigned entries')); - }); + + userEvent.click(element.getByText('Unassigned entries')); expect(onChangeSelectionMock).toHaveBeenCalledWith([ { checked: 'on', id: 'abc123', name: 'test policy A' }, { checked: 'off', id: 'global', name: 'Global entries' }, @@ -65,12 +65,10 @@ describe('Policies selector', () => { const defaultIncludedPolicies = 'abc123'; const defaultExcludedPolicies = 'global'; const element = getElement({ defaultExcludedPolicies, defaultIncludedPolicies }); - act(() => { - fireEvent.click(element.getByTestId('policiesSelectorButton')); - }); - act(() => { - fireEvent.click(element.getByText(policy.name)); - }); + + userEvent.click(element.getByTestId('policiesSelectorButton')); + + userEvent.click(element.getByText(policy.name)); expect(onChangeSelectionMock).toHaveBeenCalledWith([ { checked: 'off', id: 'abc123', name: 'test policy A' }, { checked: 'off', id: 'global', name: 'Global entries' }, @@ -82,12 +80,10 @@ describe('Policies selector', () => { const defaultIncludedPolicies = 'abc123'; const defaultExcludedPolicies = 'global'; const element = getElement({ defaultExcludedPolicies, defaultIncludedPolicies }); - act(() => { - fireEvent.click(element.getByTestId('policiesSelectorButton')); - }); - act(() => { - fireEvent.click(element.getByText('Global entries')); - }); + + userEvent.click(element.getByTestId('policiesSelectorButton')); + + userEvent.click(element.getByText('Global entries')); expect(onChangeSelectionMock).toHaveBeenCalledWith([ { checked: 'on', id: 'abc123', name: 'test policy A' }, { checked: undefined, id: 'global', name: 'Global entries' }, @@ -99,27 +95,29 @@ describe('Policies selector', () => { describe('When filter policy', () => { it('should filter policy by name', () => { const element = getElement({}); - act(() => { - fireEvent.click(element.getByTestId('policiesSelectorButton')); - }); - act(() => { - fireEvent.change(element.getByTestId('policiesSelectorSearch'), { - target: { value: policy.name }, - }); - }); + + userEvent.click(element.getByTestId('policiesSelectorButton')); + + userEvent.type(element.getByTestId('policiesSelectorSearch'), policy.name); expect(element.queryAllByText('Global entries')).toStrictEqual([]); expect(element.getByText(policy.name)).toHaveTextContent(policy.name); }); it('should filter with no results', () => { const element = getElement({}); - act(() => { - fireEvent.click(element.getByTestId('policiesSelectorButton')); - }); - act(() => { - fireEvent.change(element.getByTestId('policiesSelectorSearch'), { - target: { value: 'no results' }, - }); - }); + + userEvent.click(element.getByTestId('policiesSelectorButton')); + + userEvent.type(element.getByTestId('policiesSelectorSearch'), 'no results'); + expect(element.queryAllByText('Global entries')).toStrictEqual([]); + expect(element.queryAllByText('Unassigned entries')).toStrictEqual([]); + expect(element.queryAllByText(policy.name)).toStrictEqual([]); + }); + it('should filter with special chars', () => { + const element = getElement({}); + + userEvent.click(element.getByTestId('policiesSelectorButton')); + + userEvent.type(element.getByTestId('policiesSelectorSearch'), '*'); expect(element.queryAllByText('Global entries')).toStrictEqual([]); expect(element.queryAllByText('Unassigned entries')).toStrictEqual([]); expect(element.queryAllByText(policy.name)).toStrictEqual([]); diff --git a/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.tsx b/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.tsx index b86c8f6de7abd1..556be843d050fc 100644 --- a/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policies_selector/policies_selector.tsx @@ -135,7 +135,7 @@ export const PoliciesSelector = memo( const dropdownItems = useMemo( () => itemsList.map((item, index) => - item.name.match(new RegExp(query, 'i')) ? ( + item.name.toLowerCase().includes(query.toLowerCase()) ? ( { EndpointsContainer.displayName = 'EndpointsContainer'; export { endpointListFleetApisHttpMock } from './mocks'; -export { EndpointListFleetApisHttpMockInterface } from './mocks'; +export type { EndpointListFleetApisHttpMockInterface } from './mocks'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 05d28c1cbf27f5..6e9c1f69cb0604 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -7,6 +7,7 @@ import { Action } from 'redux'; import { EuiSuperDatePickerRecentRange } from '@elastic/eui'; +import { DataViewBase } from '@kbn/es-query'; import { HostResultList, HostInfo, @@ -17,7 +18,6 @@ import { import { ServerApiError } from '../../../../common/types'; import { GetPolicyListResponse } from '../../policy/types'; import { EndpointState } from '../types'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; export interface ServerReturnedEndpointList { type: 'serverReturnedEndpointList'; @@ -96,7 +96,7 @@ export interface ServerReturnedEndpointExistValue { export interface ServerReturnedMetadataPatterns { type: 'serverReturnedMetadataPatterns'; - payload: IIndexPattern[]; + payload: DataViewBase[]; } export interface ServerFailedToReturnMetadataPatterns { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts index 81c4dc6f2f7dec..ba08d0d2d0dcdf 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts @@ -256,7 +256,7 @@ describe('endpoint list middleware', () => { dispatch({ type: 'endpointDetailsActivityLogChanged', // Ignore will be fixed with when AsyncResourceState is refactored (#830) - // @ts-ignore + // @ts-expect-error TS2345 payload: createLoadingResourceState({ previousState: createUninitialisedResourceState() }), }); }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 7a45ff06c496b2..ec9672b645ca39 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -9,8 +9,10 @@ import { Dispatch } from 'redux'; import semverGte from 'semver/functions/gte'; import { CoreStart, HttpStart } from 'kibana/public'; +import { DataViewBase, Query } from '@kbn/es-query'; import { ActivityLog, + GetHostPolicyResponse, HostInfo, HostIsolationRequestBody, HostIsolationResponse, @@ -66,7 +68,6 @@ import { metadataCurrentIndexPattern, METADATA_UNITED_INDEX, } from '../../../../../common/endpoint/constants'; -import { IIndexPattern, Query } from '../../../../../../../../src/plugins/data/public'; import { createFailedResourceState, createLoadedResourceState, @@ -93,7 +94,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory - ): Promise { + ): Promise { const packageVersion = endpointPackageVersion(state) ?? ''; const parsedPackageVersion = packageVersion.includes('-') ? packageVersion.substring(0, packageVersion.indexOf('-')) @@ -107,7 +108,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory(endpointPackageInfo(state)), }); @@ -396,7 +397,7 @@ async function endpointDetailsListMiddleware({ }: { store: ImmutableMiddlewareAPI; coreStart: CoreStart; - fetchIndexPatterns: (state: ImmutableObject) => Promise; + fetchIndexPatterns: (state: ImmutableObject) => Promise; }) { const { getState, dispatch } = store; @@ -577,9 +578,10 @@ async function loadEndpointDetails({ // call the policy response api try { - const policyResponse = await coreStart.http.get(BASE_POLICY_RESPONSE_ROUTE, { - query: { agentId: selectedEndpoint }, - }); + const policyResponse = await coreStart.http.get( + BASE_POLICY_RESPONSE_ROUTE, + { query: { agentId: selectedEndpoint } } + ); dispatch({ type: 'serverReturnedEndpointPolicyResponse', payload: policyResponse, @@ -610,7 +612,7 @@ async function endpointDetailsMiddleware({ if (listData(getState()).length === 0) { const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(getState()); try { - const response = await coreStart.http.post(HOST_METADATA_LIST_ROUTE, { + const response = await coreStart.http.post(HOST_METADATA_LIST_ROUTE, { body: JSON.stringify({ paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], }), diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 0fa96fe00fd2ca..1a87223cdd01f1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -6,6 +6,7 @@ */ import { EuiSuperDatePickerRecentRange } from '@elastic/eui'; +import { DataViewBase } from '@kbn/es-query'; import { ActivityLog, HostInfo, @@ -20,7 +21,6 @@ import { } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; import { GetPackagesResponse } from '../../../../../fleet/common'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { AsyncResourceState } from '../../state'; import { TRANSFORM_STATES } from '../../../../common/constants'; @@ -86,7 +86,7 @@ export interface EndpointState { /** Tracks whether hosts exist and helps control if onboarding should be visible */ endpointsExist: boolean; /** index patterns for query bar */ - patterns: IIndexPattern[]; + patterns: DataViewBase[]; /** api error from retrieving index patters for query bar */ patternsError?: ServerApiError; /** Is auto-refresh enabled */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx index 56ff523e2c887b..fad042919bd831 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx @@ -44,11 +44,11 @@ describe('When using the Endpoint Details Actions Menu', () => { const setEndpointMetadataResponse = (isolation: boolean = false) => { const endpointHost = httpMocks.responseProvider.metadataDetails(); // Safe to mutate this mocked data - // @ts-ignore + // @ts-expect-error TS2540 endpointHost.metadata.Endpoint.state.isolation = isolation; - // @ts-ignore + // @ts-expect-error TS2540 endpointHost.metadata.host.os.name = 'Windows'; - // @ts-ignore + // @ts-expect-error TS2540 endpointHost.metadata.agent.version = '7.14.0'; httpMocks.responseProvider.metadataDetails.mockReturnValue(endpointHost); }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx index 79af2ecb354fdb..87de2d42a9844f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx @@ -129,9 +129,9 @@ const useLogEntryUIProps = ( if (isIsolateAction) { if (isCompleted) { if (isSuccessful) { - return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndSuccessful; + return i18.ACTIVITY_LOG.LogEntry.response.isolationCompletedAndSuccessful; } - return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndUnsuccessful; + return i18.ACTIVITY_LOG.LogEntry.response.isolationCompletedAndUnsuccessful; } else if (isSuccessful) { return i18.ACTIVITY_LOG.LogEntry.response.isolationSuccessful; } else { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts index be3de3017d1f3f..7947f5b011ff6c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts @@ -61,7 +61,7 @@ describe('event filters selectors', () => { previousStateWhileLoading = previousState; // will be fixed when AsyncResourceState is refactored (#830) - // @ts-ignore + // @ts-expect-error TS2345 initialState.listPage.data = createLoadingResourceState(previousState); }; @@ -204,8 +204,8 @@ describe('event filters selectors', () => { expect(getListPageDoesDataExist(initialState)).toBe(false); // Set DataExists to Loading - // ts-ignore will be fixed when AsyncResourceState is refactored (#830) - // @ts-ignore + // will be fixed when AsyncResourceState is refactored (#830) + // @ts-expect-error TS2345 initialState.listPage.dataExist = createLoadingResourceState(initialState.listPage.dataExist); expect(getListPageDoesDataExist(initialState)).toBe(false); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts index 957fd2d4485bc7..3b796d6aff0b3b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts @@ -42,8 +42,8 @@ export async function getHostIsolationExceptionItems({ http, perPage, page, - sortField, - sortOrder, + sortField = 'created_at', + sortOrder = 'desc', filter, }: { http: HttpStart; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts index 2587fff5bfafd8..ad99e86abb231a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts @@ -239,10 +239,18 @@ async function updateHostIsolationExceptionsItem( http, entry ); + + // notify the update was correct dispatch({ type: 'hostIsolationExceptionsFormStateChanged', payload: createLoadedResourceState(response), }); + + // clear the form + dispatch({ + type: 'hostIsolationExceptionsFormEntryChanged', + payload: undefined, + }); } catch (error) { dispatch({ type: 'hostIsolationExceptionsFormStateChanged', diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts index 3eca524d830d5c..996978f96fcb5f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts @@ -48,6 +48,13 @@ export const getListItems: HostIsolationExceptionsSelector> = createSelector( + getListApiSuccessResponse, + (apiResponseData) => { + return apiResponseData?.total || 0; + } +); + export const getListPagination: HostIsolationExceptionsSelector = createSelector( getListApiSuccessResponse, // memoized via `reselect` until the API response changes diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx index e87ac2adeab497..2eeddbaeeb0f38 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx @@ -94,7 +94,7 @@ export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { type: 'hostIsolationExceptionsMarkToEdit', payload: { id: location.id }, }); - } else { + } else if (exception === undefined) { setException(exceptionToEdit); } } diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.test.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.test.ts index 6a4e0cb8401495..2c23c437348696 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.test.ts @@ -4,33 +4,35 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useLicense } from '../../../../common/hooks/use_license'; import { useCanSeeHostIsolationExceptionsMenu } from './hooks'; import { renderHook } from '@testing-library/react-hooks'; import { TestProviders } from '../../../../common/mock'; import { getHostIsolationExceptionSummary } from '../service'; +import { useEndpointPrivileges } from '../../../../common/components/user_privileges/endpoint'; jest.mock('../../../../common/hooks/use_license'); jest.mock('../service'); +jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges'); const getHostIsolationExceptionSummaryMock = getHostIsolationExceptionSummary as jest.Mock; describe('host isolation exceptions hooks', () => { - const isPlatinumPlusMock = useLicense().isPlatinumPlus as jest.Mock; + const useEndpointPrivilegesMock = useEndpointPrivileges as jest.Mock; describe('useCanSeeHostIsolationExceptionsMenu', () => { beforeEach(() => { - isPlatinumPlusMock.mockReset(); + useEndpointPrivilegesMock.mockReset(); }); - it('should return true if the license is platinum plus', () => { - isPlatinumPlusMock.mockReturnValue(true); + + it('should return true if has the correct privileges', () => { + useEndpointPrivilegesMock.mockReturnValue({ canIsolateHost: true }); const { result } = renderHook(() => useCanSeeHostIsolationExceptionsMenu(), { wrapper: TestProviders, }); expect(result.current).toBe(true); }); - it('should return false if the license is lower platinum plus and there are not existing host isolation items', () => { - isPlatinumPlusMock.mockReturnValue(false); + it('should return false if does not have privileges and there are not existing host isolation items', () => { + useEndpointPrivilegesMock.mockReturnValue({ canIsolateHost: false }); getHostIsolationExceptionSummaryMock.mockReturnValueOnce({ total: 0 }); const { result } = renderHook(() => useCanSeeHostIsolationExceptionsMenu(), { wrapper: TestProviders, @@ -38,8 +40,8 @@ describe('host isolation exceptions hooks', () => { expect(result.current).toBe(false); }); - it('should return true if the license is lower platinum plus and there are existing host isolation items', async () => { - isPlatinumPlusMock.mockReturnValue(false); + it('should return true if does not have privileges and there are existing host isolation items', async () => { + useEndpointPrivilegesMock.mockReturnValue({ canIsolateHost: false }); getHostIsolationExceptionSummaryMock.mockReturnValueOnce({ total: 11 }); const { result, waitForNextUpdate } = renderHook( () => useCanSeeHostIsolationExceptionsMenu(), diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts index 4b6129785c84ac..50ae96305c4b3f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts @@ -8,7 +8,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { useHttp } from '../../../../common/lib/kibana/hooks'; -import { useLicense } from '../../../../common/hooks/use_license'; +import { useEndpointPrivileges } from '../../../../common/components/user_privileges/endpoint'; import { State } from '../../../../common/store'; import { MANAGEMENT_STORE_GLOBAL_NAMESPACE, @@ -42,30 +42,30 @@ export function useHostIsolationExceptionsNavigateCallback() { /** * Checks if the current user should be able to see the host isolation exceptions - * menu item based on their current license level and existing excepted items. + * menu item based on their current privileges */ export function useCanSeeHostIsolationExceptionsMenu() { - const license = useLicense(); const http = useHttp(); + const privileges = useEndpointPrivileges(); - const [hasExceptions, setHasExceptions] = useState(license.isPlatinumPlus()); + const [canSeeMenu, setCanSeeMenu] = useState(privileges.canIsolateHost); useEffect(() => { async function checkIfHasExceptions() { try { const summary = await getHostIsolationExceptionSummary(http); if (summary?.total > 0) { - setHasExceptions(true); + setCanSeeMenu(true); } } catch (error) { // an error will ocurr if the exception list does not exist - setHasExceptions(false); + setCanSeeMenu(false); } } - if (!license.isPlatinumPlus()) { + if (!privileges.canIsolateHost) { checkIfHasExceptions(); } - }, [http, license]); + }, [http, privileges.canIsolateHost]); - return hasExceptions; + return canSeeMenu; } diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx index bf71cde6b6c763..2911264d7061a0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx @@ -14,11 +14,11 @@ import { AppContextTestRender, createAppRootMockRenderer } from '../../../../com import { isFailedResourceState, isLoadedResourceState } from '../../../state'; import { getHostIsolationExceptionItems } from '../service'; import { HostIsolationExceptionsList } from './host_isolation_exceptions_list'; -import { useLicense } from '../../../../common/hooks/use_license'; +import { useEndpointPrivileges } from '../../../../common/components/user_privileges/endpoint'; -jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges'); jest.mock('../service'); jest.mock('../../../../common/hooks/use_license'); +jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges'); const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock; @@ -29,7 +29,7 @@ describe('When on the host isolation exceptions page', () => { let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; let mockedContext: AppContextTestRender; - const isPlatinumPlusMock = useLicense().isPlatinumPlus as jest.Mock; + const useEndpointPrivilegesMock = useEndpointPrivileges as jest.Mock; beforeEach(() => { getHostIsolationExceptionItemsMock.mockReset(); @@ -94,10 +94,13 @@ describe('When on the host isolation exceptions page', () => { expect(renderResult.container.querySelector('.euiProgress')).toBeNull(); }); - it('should display the search bar', async () => { + it('should display the search bar and item count', async () => { render(); await dataReceived(); expect(renderResult.getByTestId('searchExceptions')).toBeTruthy(); + expect(renderResult.getByTestId('hostIsolationExceptions-totalCount').textContent).toBe( + 'Showing 1 exception' + ); }); it('should show items on the list', async () => { @@ -124,37 +127,72 @@ describe('When on the host isolation exceptions page', () => { renderResult.getByTestId('hostIsolationExceptionsContent-error').textContent ).toEqual(' Server is too far away'); }); + + it('should show the searchbar when no results from search', async () => { + // render the page with data + render(); + await dataReceived(); + + // check if the searchbar is there + expect(renderResult.getByTestId('searchExceptions')).toBeTruthy(); + + // simulate a no-data scenario + getHostIsolationExceptionItemsMock.mockReturnValueOnce({ + data: [], + page: 1, + per_page: 10, + total: 0, + }); + + // type something to search and press the button + userEvent.type(renderResult.getByTestId('searchField'), 'this does not exists'); + userEvent.click(renderResult.getByTestId('searchButton')); + + // wait for the page render + await dataReceived(); + + // check the url changed + expect(mockedContext.history.location.search).toBe('?filter=this%20does%20not%20exists'); + + // check the searchbar is still there + expect(renderResult.getByTestId('searchExceptions')).toBeTruthy(); + }); }); - describe('is license platinum plus', () => { - beforeEach(() => { - isPlatinumPlusMock.mockReturnValue(true); + describe('has canIsolateHost privileges', () => { + beforeEach(async () => { + useEndpointPrivilegesMock.mockReturnValue({ canIsolateHost: true }); + getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock); }); + it('should show the create flyout when the add button is pressed', async () => { render(); await dataReceived(); - act(() => { - userEvent.click(renderResult.getByTestId('hostIsolationExceptionsEmptyStateAddButton')); - }); + userEvent.click(renderResult.getByTestId('hostIsolationExceptionsListAddButton')); + await dataReceived(); expect(renderResult.getByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeTruthy(); }); - it('should show the create flyout when the show location is create', () => { + + it('should show the create flyout when the show location is create', async () => { history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=create`); render(); + await dataReceived(); expect(renderResult.getByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeTruthy(); expect(renderResult.queryByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeTruthy(); }); }); - describe('is not license platinum plus', () => { + describe('does not have canIsolateHost privileges', () => { beforeEach(() => { - isPlatinumPlusMock.mockReturnValue(false); + useEndpointPrivilegesMock.mockReturnValue({ canIsolateHost: false }); }); + it('should not show the create flyout if the user navigates to the create url', () => { history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=create`); render(); expect(renderResult.queryByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeFalsy(); }); + it('should not show the create flyout if the user navigates to the edit url', () => { history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=edit`); render(); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx index d9b667947517e8..815d790b5b4af2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx @@ -8,12 +8,11 @@ import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { i18n } from '@kbn/i18n'; import React, { Dispatch, useCallback, useEffect } from 'react'; -import { EuiButton } from '@elastic/eui'; +import { EuiButton, EuiText, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { ExceptionItem } from '../../../../common/components/exceptions/viewer/exception_item'; -import { useLicense } from '../../../../common/hooks/use_license'; import { getCurrentLocation, getItemToDelete, @@ -21,6 +20,7 @@ import { getListIsLoading, getListItems, getListPagination, + getTotalListItems, } from '../store/selector'; import { useHostIsolationExceptionsNavigateCallback, @@ -40,6 +40,7 @@ import { EDIT_HOST_ISOLATION_EXCEPTION_LABEL, } from './components/translations'; import { getEndpointListPath } from '../../../common/routing'; +import { useEndpointPrivileges } from '../../../../common/components/user_privileges/endpoint'; type HostIsolationExceptionPaginatedContent = PaginatedContentProps< Immutable, @@ -48,6 +49,7 @@ type HostIsolationExceptionPaginatedContent = PaginatedContentProps< export const HostIsolationExceptionsList = () => { const listItems = useHostIsolationExceptionsSelector(getListItems); + const totalCountListItems = useHostIsolationExceptionsSelector(getTotalListItems); const pagination = useHostIsolationExceptionsSelector(getListPagination); const isLoading = useHostIsolationExceptionsSelector(getListIsLoading); const fetchError = useHostIsolationExceptionsSelector(getListFetchError); @@ -56,14 +58,15 @@ export const HostIsolationExceptionsList = () => { const itemToDelete = useHostIsolationExceptionsSelector(getItemToDelete); const navigateCallback = useHostIsolationExceptionsNavigateCallback(); const history = useHistory(); - const license = useLicense(); - const showFlyout = license.isPlatinumPlus() && !!location.show; + const privileges = useEndpointPrivileges(); + const showFlyout = privileges.canIsolateHost && !!location.show; + const hasDataToShow = !isLoading && (!!location.filter || listItems.length > 0); useEffect(() => { - if (!isLoading && listItems.length === 0 && !license.isPlatinumPlus()) { + if (!isLoading && listItems.length === 0 && !privileges.canIsolateHost) { history.replace(getEndpointListPath({ name: 'endpointList' })); } - }, [history, isLoading, license, listItems.length]); + }, [history, isLoading, listItems.length, privileges.canIsolateHost]); const handleOnSearch = useCallback( (query: string) => { @@ -74,7 +77,7 @@ export const HostIsolationExceptionsList = () => { function handleItemComponentProps(element: ExceptionListItemSchema): ArtifactEntryCardProps { const editAction = { - icon: 'trash', + icon: 'controlsHorizontal', onClick: () => { navigateCallback({ show: 'edit', @@ -98,7 +101,7 @@ export const HostIsolationExceptionsList = () => { return { item: element, 'data-test-subj': `hostIsolationExceptionsCard`, - actions: license.isPlatinumPlus() ? [editAction, deleteAction] : [deleteAction], + actions: privileges.canIsolateHost ? [editAction, deleteAction] : [deleteAction], }; } @@ -130,8 +133,14 @@ export const HostIsolationExceptionsList = () => { defaultMessage="Host isolation exceptions" /> } + subtitle={ + + } actions={ - license.isPlatinumPlus() ? ( + privileges.canIsolateHost && hasDataToShow ? ( { [] ) } - hideHeader={isLoading || listItems.length === 0} + hideHeader={!hasDataToShow} > {showFlyout && } {itemToDelete ? : null} - {!isLoading && listItems.length ? ( - + {hasDataToShow ? ( + <> + + + + + + + ) : null} @@ -177,7 +197,9 @@ export const HostIsolationExceptionsList = () => { pagination={pagination} contentClassName="host-isolation-exceptions-container" data-test-subj="hostIsolationExceptionsContent" - noItemsMessage={} + noItemsMessage={ + !hasDataToShow && + } /> ); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts index bc9e42ddf7f52e..c771cef28e73df 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts @@ -20,4 +20,4 @@ export interface EndpointPolicyDetailsStatePluginState { export interface EndpointPolicyDetailsStatePluginReducer { policyDetails: ImmutableReducer; } -export { PolicyDetailsAction } from './action'; +export type { PolicyDetailsAction } from './action'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts index f50eb342acba11..782c659b3d7652 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts @@ -114,7 +114,7 @@ const checkIfThereAreAssignableTrustedApps = async ( store.dispatch({ type: 'policyArtifactsAssignableListExistDataChanged', // Ignore will be fixed with when AsyncResourceState is refactored (#830) - // @ts-ignore + // @ts-expect-error TS2345 payload: createLoadingResourceState({ previousState: createUninitialisedResourceState() }), }); try { @@ -132,7 +132,7 @@ const checkIfThereAreAssignableTrustedApps = async ( store.dispatch({ type: 'policyArtifactsAssignableListExistDataChanged', // Ignore will be fixed with when AsyncResourceState is refactored (#830) - // @ts-ignore + // @ts-expect-error TS2741 payload: createFailedResourceState(err.body ?? err), }); } @@ -181,7 +181,7 @@ const searchTrustedApps = async ( store.dispatch({ type: 'policyArtifactsAssignableListPageDataChanged', // Ignore will be fixed with when AsyncResourceState is refactored (#830) - // @ts-ignore + // @ts-expect-error TS2345 payload: createLoadingResourceState({ previousState: createUninitialisedResourceState() }), }); @@ -213,7 +213,7 @@ const searchTrustedApps = async ( store.dispatch({ type: 'policyArtifactsAssignableListPageDataChanged', // Ignore will be fixed with when AsyncResourceState is refactored (#830) - // @ts-ignore + // @ts-expect-error TS2322 payload: createFailedResourceState(err.body ?? err), }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx index 55e844df2afae5..30c95472e4d6d4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx @@ -66,10 +66,10 @@ describe('Fleet event filters card', () => { {children} ); - // @ts-ignore + // @ts-expect-error TS2739 const component = reactTestingLibrary.render(, { wrapper: Wrapper }); try { - // @ts-ignore + // @ts-expect-error TS2769 await reactTestingLibrary.act(() => promise); } catch (err) { return component; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx index aa4b36d5486047..c61f109c75a1e9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx @@ -66,12 +66,12 @@ describe('Fleet trusted apps card', () => { {children} ); - // @ts-ignore + // @ts-expect-error TS2739 const component = reactTestingLibrary.render(, { wrapper: Wrapper, }); try { - // @ts-ignore + // @ts-expect-error TS2769 await reactTestingLibrary.act(() => promise); } catch (err) { return component; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx index 0a912598c57223..0717ca5193beed 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useEffect, useState, useMemo } from 'react'; -import { EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiCallOut, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch } from 'react-redux'; @@ -23,7 +23,11 @@ import { getPolicyDetailPath, getPolicyTrustedAppsPath } from '../../../../commo import { PolicyDetailsForm } from '../policy_details_form'; import { AppAction } from '../../../../../common/store/actions'; import { usePolicyDetailsSelector } from '../policy_hooks'; -import { policyDetailsForUpdate } from '../../store/policy_details/selectors'; +import { + apiError, + policyDetails, + policyDetailsForUpdate, +} from '../../store/policy_details/selectors'; import { FleetTrustedAppsCard } from './endpoint_package_custom_extension/components/fleet_trusted_apps_card'; import { LinkWithIcon } from './endpoint_package_custom_extension/components/link_with_icon'; /** @@ -48,6 +52,8 @@ const WrappedPolicyDetailsForm = memo<{ }>(({ policyId, onChange }) => { const dispatch = useDispatch<(a: AppAction) => void>(); const updatedPolicy = usePolicyDetailsSelector(policyDetailsForUpdate); + const endpointPolicyDetails = usePolicyDetailsSelector(policyDetails); + const endpointDetailsLoadingError = usePolicyDetailsSelector(apiError); const { getAppUrl } = useAppUrl(); const [, setLastUpdatedPolicy] = useState(updatedPolicy); // TODO: Remove this and related code when removing FF @@ -185,7 +191,25 @@ const WrappedPolicyDetailsForm = memo<{ - + {endpointDetailsLoadingError ? ( + + } + iconType="alert" + color="warning" + data-test-subj="endpiontPolicySettingsLoadingError" + > + {endpointDetailsLoadingError.message} + + ) : !endpointPolicyDetails ? ( + + ) : ( + + )}